From 09ba2293532a8a7cacbafd24b7e51ed4324dbc3c Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 17 Aug 2025 18:56:17 +0200 Subject: [PATCH] Initial procedural 3D engine setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated README.md with modern project structure and features - Cleaned up Android build files (not needed for desktop engine) - Restructured as procedural 3D engine with ImGui integration - Based on Sascha Willems Vulkan framework with dynamic rendering - Added comprehensive build instructions and camera system docs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 13 + CLAUDE.md | 147 ++ CMakeLists.txt | 5 +- README.md | 664 ++---- android/.gitignore | 69 - android/build.gradle | 47 - android/common/res/drawable/icon.png | Bin 25498 -> 0 bytes android/examples/_template/CMakeLists.txt | 36 - android/examples/_template/build.gradle | 54 - .../_template/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/base/CMakeLists.txt | 40 - android/examples/bloom/CMakeLists.txt | 37 - android/examples/bloom/build.gradle | 84 - .../bloom/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../bufferdeviceaddress/CMakeLists.txt | 37 - .../examples/bufferdeviceaddress/build.gradle | 71 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/computecloth/CMakeLists.txt | 37 - android/examples/computecloth/build.gradle | 72 - .../computecloth/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/computecullandlod/CMakeLists.txt | 37 - .../examples/computecullandlod/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/computeheadless/CMakeLists.txt | 36 - android/examples/computeheadless/build.gradle | 60 - .../src/main/AndroidManifest.xml | 26 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/computenbody/CMakeLists.txt | 36 - android/examples/computenbody/build.gradle | 72 - .../computenbody/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/computeparticles/CMakeLists.txt | 36 - .../examples/computeparticles/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/computeraytracing/CMakeLists.txt | 36 - .../examples/computeraytracing/build.gradle | 60 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/computeshader/CMakeLists.txt | 36 - android/examples/computeshader/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/conditionalrender/CMakeLists.txt | 37 - .../examples/conditionalrender/build.gradle | 64 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../conservativeraster/CMakeLists.txt | 36 - .../examples/conservativeraster/build.gradle | 60 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/debugprintf/CMakeLists.txt | 37 - android/examples/debugprintf/build.gradle | 65 - .../debugprintf/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/debugutils/CMakeLists.txt | 37 - android/examples/debugutils/build.gradle | 72 - .../debugutils/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/deferred/CMakeLists.txt | 37 - android/examples/deferred/build.gradle | 84 - .../deferred/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../deferredmultisampling/CMakeLists.txt | 37 - .../deferredmultisampling/build.gradle | 84 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/deferredshadows/CMakeLists.txt | 37 - android/examples/deferredshadows/build.gradle | 84 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/descriptorbuffer/CMakeLists.txt | 37 - .../examples/descriptorbuffer/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../descriptorindexing/CMakeLists.txt | 37 - .../examples/descriptorindexing/build.gradle | 58 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/descriptorsets/CMakeLists.txt | 37 - android/examples/descriptorsets/build.gradle | 78 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/displacement/CMakeLists.txt | 37 - android/examples/displacement/build.gradle | 72 - .../displacement/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../distancefieldfonts/CMakeLists.txt | 36 - .../examples/distancefieldfonts/build.gradle | 78 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/dynamicrendering/CMakeLists.txt | 37 - .../examples/dynamicrendering/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../CMakeLists.txt | 37 - .../build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/dynamicstate/CMakeLists.txt | 37 - android/examples/dynamicstate/build.gradle | 65 - .../dynamicstate/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../dynamicuniformbuffer/CMakeLists.txt | 36 - .../dynamicuniformbuffer/build.gradle | 60 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/gears/CMakeLists.txt | 36 - android/examples/gears/build.gradle | 60 - .../gears/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/geometryshader/CMakeLists.txt | 37 - android/examples/geometryshader/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/gltfloading/CMakeLists.txt | 37 - android/examples/gltfloading/build.gradle | 66 - .../gltfloading/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../gltfscenerendering/CMakeLists.txt | 37 - .../examples/gltfscenerendering/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/gltfskinning/CMakeLists.txt | 37 - android/examples/gltfskinning/build.gradle | 66 - .../gltfskinning/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/gradle/outputfilename.gradle | 7 - .../graphicspipelinelibrary/CMakeLists.txt | 37 - .../graphicspipelinelibrary/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/hdr/CMakeLists.txt | 37 - android/examples/hdr/build.gradle | 96 - .../examples/hdr/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/hostimagecopy/CMakeLists.txt | 37 - android/examples/hostimagecopy/build.gradle | 71 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/imgui/CMakeLists.txt | 38 - android/examples/imgui/build.gradle | 78 - .../imgui/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/indirectdraw/CMakeLists.txt | 37 - android/examples/indirectdraw/build.gradle | 90 - .../indirectdraw/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../inlineuniformblocks/CMakeLists.txt | 37 - .../examples/inlineuniformblocks/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/inputattachments/CMakeLists.txt | 37 - .../examples/inputattachments/build.gradle | 70 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/instancing/CMakeLists.txt | 37 - android/examples/instancing/build.gradle | 84 - .../instancing/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/meshshader/CMakeLists.txt | 36 - android/examples/meshshader/build.gradle | 59 - .../meshshader/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/multisampling/CMakeLists.txt | 37 - android/examples/multisampling/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/multithreading/CMakeLists.txt | 37 - android/examples/multithreading/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/multiview/CMakeLists.txt | 37 - android/examples/multiview/build.gradle | 65 - .../multiview/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../negativeviewportheight/CMakeLists.txt | 36 - .../negativeviewportheight/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/occlusionquery/CMakeLists.txt | 37 - android/examples/occlusionquery/build.gradle | 78 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/offscreen/CMakeLists.txt | 37 - android/examples/offscreen/build.gradle | 71 - .../offscreen/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/oit/CMakeLists.txt | 37 - android/examples/oit/build.gradle | 71 - .../examples/oit/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/parallaxmapping/CMakeLists.txt | 37 - android/examples/parallaxmapping/build.gradle | 78 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/particlesystem/CMakeLists.txt | 37 - android/examples/particlesystem/build.gradle | 90 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/pbrbasic/CMakeLists.txt | 37 - android/examples/pbrbasic/build.gradle | 84 - .../pbrbasic/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/pbribl/CMakeLists.txt | 37 - android/examples/pbribl/build.gradle | 96 - .../pbribl/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/pbrtexture/CMakeLists.txt | 37 - android/examples/pbrtexture/build.gradle | 78 - .../pbrtexture/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/pipelines/CMakeLists.txt | 38 - android/examples/pipelines/build.gradle | 66 - .../pipelines/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../pipelinestatistics/CMakeLists.txt | 38 - .../examples/pipelinestatistics/build.gradle | 84 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/pushconstants/CMakeLists.txt | 37 - android/examples/pushconstants/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/pushdescriptors/CMakeLists.txt | 37 - android/examples/pushdescriptors/build.gradle | 78 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/radialblur/CMakeLists.txt | 37 - android/examples/radialblur/build.gradle | 72 - .../radialblur/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/rayquery/CMakeLists.txt | 37 - android/examples/rayquery/build.gradle | 65 - .../rayquery/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/raytracingbasic/CMakeLists.txt | 36 - android/examples/raytracingbasic/build.gradle | 60 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../raytracingcallable/CMakeLists.txt | 36 - .../examples/raytracingcallable/build.gradle | 59 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/raytracinggltf/CMakeLists.txt | 37 - android/examples/raytracinggltf/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../raytracingintersection/CMakeLists.txt | 36 - .../raytracingintersection/build.gradle | 59 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../raytracingpositionfetch/CMakeLists.txt | 37 - .../raytracingpositionfetch/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../raytracingreflections/CMakeLists.txt | 37 - .../raytracingreflections/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/raytracingsbtdata/CMakeLists.txt | 36 - .../examples/raytracingsbtdata/build.gradle | 59 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/raytracingshadows/CMakeLists.txt | 37 - .../examples/raytracingshadows/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../raytracingtextures/CMakeLists.txt | 36 - .../examples/raytracingtextures/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/renderheadless/CMakeLists.txt | 36 - android/examples/renderheadless/build.gradle | 60 - .../src/main/AndroidManifest.xml | 26 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/screenshot/CMakeLists.txt | 37 - android/examples/screenshot/build.gradle | 66 - .../screenshot/src/main/AndroidManifest.xml | 26 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/shaderobjects/CMakeLists.txt | 37 - android/examples/shaderobjects/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/shadowmapping/CMakeLists.txt | 37 - android/examples/shadowmapping/build.gradle | 71 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../shadowmappingcascade/CMakeLists.txt | 37 - .../shadowmappingcascade/build.gradle | 71 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/shadowmappingomni/CMakeLists.txt | 37 - .../examples/shadowmappingomni/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../specializationconstants/CMakeLists.txt | 37 - .../specializationconstants/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../sphericalenvmapping/CMakeLists.txt | 37 - .../examples/sphericalenvmapping/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/ssao/CMakeLists.txt | 37 - android/examples/ssao/build.gradle | 65 - .../ssao/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/stencilbuffer/CMakeLists.txt | 37 - android/examples/stencilbuffer/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/subpasses/CMakeLists.txt | 37 - android/examples/subpasses/build.gradle | 78 - .../subpasses/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../terraintessellation/CMakeLists.txt | 37 - .../examples/terraintessellation/build.gradle | 84 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/tessellation/CMakeLists.txt | 37 - android/examples/tessellation/build.gradle | 65 - .../tessellation/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/textoverlay/CMakeLists.txt | 38 - android/examples/textoverlay/build.gradle | 66 - .../textoverlay/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/texture/CMakeLists.txt | 36 - android/examples/texture/build.gradle | 66 - .../texture/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/texture3d/CMakeLists.txt | 36 - android/examples/texture3d/build.gradle | 60 - .../texture3d/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/texturearray/CMakeLists.txt | 36 - android/examples/texturearray/build.gradle | 66 - .../texturearray/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/texturecubemap/CMakeLists.txt | 37 - android/examples/texturecubemap/build.gradle | 96 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../texturecubemaparray/CMakeLists.txt | 37 - .../examples/texturecubemaparray/build.gradle | 96 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/texturemipmapgen/CMakeLists.txt | 37 - .../examples/texturemipmapgen/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../texturesparseresidency/CMakeLists.txt | 37 - .../texturesparseresidency/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/timelinesemaphore/CMakeLists.txt | 36 - .../examples/timelinesemaphore/build.gradle | 72 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/triangle/CMakeLists.txt | 36 - android/examples/triangle/build.gradle | 60 - .../triangle/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/trianglevulkan13/CMakeLists.txt | 36 - .../examples/trianglevulkan13/build.gradle | 59 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../variablerateshading/CMakeLists.txt | 37 - .../examples/variablerateshading/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - .../examples/vertexattributes/CMakeLists.txt | 37 - .../examples/vertexattributes/build.gradle | 65 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/viewportarray/CMakeLists.txt | 37 - android/examples/viewportarray/build.gradle | 66 - .../src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/examples/vulkanscene/CMakeLists.txt | 37 - android/examples/vulkanscene/build.gradle | 90 - .../vulkanscene/src/main/AndroidManifest.xml | 24 - .../vulkanSample/VulkanActivity.java | 58 - android/gradle.properties | 17 - android/gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - android/gradlew | 234 -- android/gradlew.bat | 89 - android/settings.gradle | 9 - base/CMakeLists.txt | 31 +- base/VulkanAndroid.cpp | 366 --- base/VulkanAndroid.h | 198 -- base/VulkanFrameBuffer.hpp | 4 +- base/VulkanRaytracingSample.h | 6 +- base/VulkanglTFModel.h | 2 +- base/camera.hpp | 110 +- base/{ => core}/VulkanDebug.cpp | 0 base/{ => core}/VulkanDebug.h | 0 base/{ => core}/VulkanInitializers.hpp | 0 base/{ => core}/VulkanTools.cpp | 0 base/{ => core}/VulkanTools.h | 0 base/{ => device}/VulkanDevice.cpp | 2 +- base/{ => device}/VulkanDevice.h | 4 +- base/{ => device}/VulkanSwapChain.cpp | 0 base/{ => device}/VulkanSwapChain.h | 2 +- base/{ => foundation}/VulkanUIOverlay.cpp | 0 base/{ => foundation}/VulkanUIOverlay.h | 8 +- base/{ => foundation}/vulkanexamplebase.cpp | 2 +- base/{ => foundation}/vulkanexamplebase.h | 25 +- base/{ => memory}/VulkanBuffer.cpp | 0 base/{ => memory}/VulkanBuffer.h | 2 +- base/{ => memory}/VulkanTexture.cpp | 2 +- base/{ => memory}/VulkanTexture.h | 4 +- examples/CMakeLists.txt | 93 - examples/bloom/bloom.cpp | 727 ------ .../bufferdeviceaddress.cpp | 324 --- examples/computecloth/computecloth.cpp | 768 ------- .../computecullandlod/computecullandlod.cpp | 822 ------- examples/computeheadless/computeheadless.cpp | 636 ------ examples/computenbody/computenbody.cpp | 677 ------ .../computeparticles/computeparticles.cpp | 646 ------ .../computeraytracing/computeraytracing.cpp | 630 ----- examples/computeshader/computeshader.cpp | 582 ----- .../conditionalrender/conditionalrender.cpp | 365 --- .../conservativeraster/conservativeraster.cpp | 686 ------ examples/debugprintf/debugprintf.cpp | 245 -- examples/debugutils/debugutils.cpp | 775 ------- examples/deferred/deferred.cpp | 804 ------- .../deferredmultisampling.cpp | 638 ------ examples/deferredshadows/deferredshadows.cpp | 778 ------- .../descriptorbuffer/descriptorbuffer.cpp | 452 ---- .../descriptorindexing/descriptorindexing.cpp | 461 ---- examples/descriptorsets/descriptorsets.cpp | 383 ---- examples/displacement/displacement.cpp | 308 --- .../distancefieldfonts/distancefieldfonts.cpp | 463 ---- .../dynamicrendering/dynamicrendering.cpp | 316 --- .../dynamicrenderingmultisampling.cpp | 401 ---- examples/dynamicstate/dynamicstate.cpp | 415 ---- examples/dynamicuniformbuffer/README.md | 162 -- .../dynamicuniformbuffer.cpp | 432 ---- examples/gears/gears.cpp | 525 ----- examples/geometryshader/geometryshader.cpp | 257 --- examples/gltfloading/gltfloading.cpp | 744 ------ examples/gltfscenerendering/README.md | 323 --- .../gltfscenerendering/gltfscenerendering.cpp | 663 ------ .../gltfscenerendering/gltfscenerendering.h | 170 -- examples/gltfskinning/README.md | 550 ----- examples/gltfskinning/gltfskinning.cpp | 1006 -------- examples/gltfskinning/gltfskinning.h | 235 -- .../graphicspipelinelibrary.cpp | 528 ----- examples/hdr/hdr.cpp | 830 ------- examples/hostimagecopy/hostimagecopy.cpp | 460 ---- examples/imgui/main.cpp | 2034 ++++++++++++++++- examples/imgui/main_backup.cpp | 1805 +++++++++++++++ examples/indirectdraw/README.md | 109 - examples/indirectdraw/indirectdraw.cpp | 483 ---- .../inlineuniformblocks.cpp | 383 ---- .../inputattachments/inputattachments.cpp | 634 ----- examples/instancing/instancing.cpp | 463 ---- examples/meshshader/meshshader.cpp | 230 -- examples/multisampling/multisampling.cpp | 550 ----- examples/multithreading/multithreading.cpp | 520 ----- examples/multiview/multiview.cpp | 742 ------ .../negativeviewportheight.cpp | 324 --- examples/occlusionquery/occlusionquery.cpp | 424 ---- examples/offscreen/offscreen.cpp | 620 ----- examples/oit/oit.cpp | 592 ----- examples/parallaxmapping/parallaxmapping.cpp | 283 --- examples/particlesystem/particlesystem.cpp | 565 ----- examples/pbrbasic/pbrbasic.cpp | 346 --- examples/pbribl/pbribl.cpp | 1420 ------------ examples/pbrtexture/pbrtexture.cpp | 1352 ----------- examples/pipelines/pipelines.cpp | 303 --- .../pipelinestatistics/pipelinestatistics.cpp | 407 ---- examples/pushconstants/pushconstants.cpp | 251 -- examples/pushdescriptors/pushdescriptors.cpp | 335 --- examples/radialblur/radialblur.cpp | 614 ----- examples/rayquery/rayquery.cpp | 468 ---- examples/raytracingbasic/raytracingbasic.cpp | 909 -------- .../raytracingcallable/raytracingcallable.cpp | 650 ------ examples/raytracinggltf/raytracinggltf.cpp | 793 ------- .../raytracingintersection.cpp | 622 ----- .../raytracingpositionfetch.cpp | 557 ----- .../raytracingreflections.cpp | 584 ----- .../raytracingsbtdata/raytracingsbtdata.cpp | 937 -------- .../raytracingshadows/raytracingshadows.cpp | 584 ----- .../raytracingtextures/raytracingtextures.cpp | 715 ------ examples/renderheadless/renderheadless.cpp | 974 -------- examples/screenshot/screenshot.cpp | 433 ---- examples/shaderobjects/shaderobjects.cpp | 477 ---- examples/shadowmapping/shadowmapping.cpp | 598 ----- .../shadowmappingcascade.cpp | 785 ------- .../shadowmappingomni/shadowmappingomni.cpp | 708 ------ .../specializationconstants.cpp | 286 --- .../sphericalenvmapping.cpp | 230 -- examples/ssao/ssao.cpp | 953 -------- examples/stencilbuffer/stencilbuffer.cpp | 255 --- examples/subpasses/subpasses.cpp | 832 ------- .../terraintessellation.cpp | 752 ------ examples/tessellation/tessellation.cpp | 317 --- examples/textoverlay/textoverlay.cpp | 719 ------ examples/texture/texture.cpp | 683 ------ examples/texture3d/texture3d.cpp | 651 ------ examples/texturearray/texturearray.cpp | 544 ----- examples/texturecubemap/texturecubemap.cpp | 488 ---- .../texturecubemaparray.cpp | 498 ---- examples/texturemipmapgen/README.md | 201 -- .../texturemipmapgen/texturemipmapgen.cpp | 555 ----- .../texturesparseresidency.cpp | 871 ------- .../texturesparseresidency.h | 120 - .../timelinesemaphore/timelinesemaphore.cpp | 674 ------ examples/triangle/triangle.cpp | 1186 ---------- .../trianglevulkan13/trianglevulkan13.cpp | 963 -------- examples/validate_all.py | 23 - .../variablerateshading.cpp | 604 ----- .../variablerateshading/variablerateshading.h | 70 - examples/vertexattributes/README.md | 61 - .../vertexattributes/interleavedbuffer.png | Bin 9565 -> 0 bytes examples/vertexattributes/separatebuffers.png | Bin 18867 -> 0 bytes .../vertexattributes/vertexattributes.cpp | 599 ----- examples/vertexattributes/vertexattributes.h | 140 -- examples/viewportarray/viewportarray.cpp | 295 --- examples/vulkanscene/vulkanscene.cpp | 262 --- .../other_include/KHR/khrplatform.h | 282 +++ external/stb/stb_font_consolas_24_latin1.inl | 734 ------ images/androidlogo.png | Bin 8288 -> 0 bytes images/applelogo.png | Bin 16068 -> 0 bytes images/linuxlogo.png | Bin 11913 -> 0 bytes images/vulkanlogo.png | Bin 19618 -> 0 bytes images/vulkanlogoscene.png | Bin 168127 -> 0 bytes images/windowslogo.png | Bin 5452 -> 0 bytes imgui.ini | 35 + screenshots/bloom.jpg | Bin 62715 -> 0 bytes screenshots/bufferdeviceaddress.jpg | Bin 60864 -> 0 bytes screenshots/computecloth.jpg | Bin 34466 -> 0 bytes screenshots/computecullandlod.jpg | Bin 394033 -> 0 bytes screenshots/computenbody.jpg | Bin 55445 -> 0 bytes screenshots/computeparticles.jpg | Bin 234893 -> 0 bytes screenshots/computeraytracing.jpg | Bin 57014 -> 0 bytes screenshots/computeshader.jpg | Bin 149264 -> 0 bytes screenshots/conditionalrender.jpg | Bin 141786 -> 0 bytes screenshots/conservativeraster.jpg | Bin 57853 -> 0 bytes screenshots/debugprintf.jpg | Bin 77937 -> 0 bytes screenshots/debugutils.jpg | Bin 86619 -> 0 bytes screenshots/deferred.jpg | Bin 103222 -> 0 bytes screenshots/deferredmultisampling.jpg | Bin 126997 -> 0 bytes screenshots/deferredshadows.jpg | Bin 112938 -> 0 bytes screenshots/descriptorbuffer.jpg | Bin 65213 -> 0 bytes screenshots/descriptorindexing.jpg | Bin 47712 -> 0 bytes screenshots/descriptorsets.jpg | Bin 72275 -> 0 bytes screenshots/displacement.jpg | Bin 152394 -> 0 bytes screenshots/distancefieldfonts.jpg | Bin 65902 -> 0 bytes screenshots/dynamicrendering.jpg | Bin 34923 -> 0 bytes screenshots/dynamicrenderingmultisampling.jpg | Bin 26487 -> 0 bytes screenshots/dynamicstate.jpg | Bin 58432 -> 0 bytes screenshots/dynamicuniformbuffer.jpg | Bin 93353 -> 0 bytes screenshots/ext_debugmarker.jpg | Bin 345938 -> 0 bytes screenshots/gears.jpg | Bin 30027 -> 0 bytes screenshots/geometryshader.jpg | Bin 124496 -> 0 bytes screenshots/gltfloading.jpg | Bin 35304 -> 0 bytes screenshots/gltfscenerendering.jpg | Bin 215027 -> 0 bytes screenshots/gltfskinning.jpg | Bin 26593 -> 0 bytes screenshots/graphicspipelinelibrary.jpg | Bin 61783 -> 0 bytes screenshots/hdr.jpg | Bin 119380 -> 0 bytes screenshots/hostimagecopy.jpg | Bin 50779 -> 0 bytes screenshots/imgui.jpg | Bin 85458 -> 0 bytes screenshots/indirectdraw.jpg | Bin 327285 -> 0 bytes screenshots/inlineuniformblocks.jpg | Bin 19541 -> 0 bytes screenshots/inputattachments.jpg | Bin 28015 -> 0 bytes screenshots/instancing.jpg | Bin 149107 -> 0 bytes screenshots/meshshader.jpg | Bin 23171 -> 0 bytes screenshots/multisampling.jpg | Bin 58390 -> 0 bytes screenshots/multithreading.jpg | Bin 146485 -> 0 bytes screenshots/multiview.jpg | Bin 104722 -> 0 bytes screenshots/negativeviewportheight.jpg | Bin 45897 -> 0 bytes screenshots/occlusionquery.jpg | Bin 32131 -> 0 bytes screenshots/offscreen.jpg | Bin 33094 -> 0 bytes screenshots/oit.jpg | Bin 121110 -> 0 bytes screenshots/parallaxmapping.jpg | Bin 112270 -> 0 bytes screenshots/particlesystem.jpg | Bin 42718 -> 0 bytes screenshots/pbrbasic.jpg | Bin 74963 -> 0 bytes screenshots/pbribl.jpg | Bin 80003 -> 0 bytes screenshots/pbrtexture.jpg | Bin 116006 -> 0 bytes screenshots/pipelines.jpg | Bin 110046 -> 0 bytes screenshots/pipelinestatistics.jpg | Bin 85668 -> 0 bytes screenshots/pushconstants.jpg | Bin 28731 -> 0 bytes screenshots/pushdescriptors.jpg | Bin 80984 -> 0 bytes screenshots/radialblur.jpg | Bin 57140 -> 0 bytes screenshots/rayquery.jpg | Bin 43903 -> 0 bytes screenshots/raytracingbasic.jpg | Bin 14333 -> 0 bytes screenshots/raytracingcallable.jpg | Bin 18178 -> 0 bytes screenshots/raytracinggltf.jpg | Bin 26223 -> 0 bytes screenshots/raytracingintersection.jpg | Bin 67768 -> 0 bytes screenshots/raytracingpositionfetch.jpg | Bin 66991 -> 0 bytes screenshots/raytracingreflections.jpg | Bin 102366 -> 0 bytes screenshots/raytracingsbtdata.jpg | Bin 16619 -> 0 bytes screenshots/raytracingshadows.jpg | Bin 42466 -> 0 bytes screenshots/raytracingtextures.jpg | Bin 113488 -> 0 bytes screenshots/screenshot.jpg | Bin 59900 -> 0 bytes screenshots/shaderobjects.jpg | Bin 39297 -> 0 bytes screenshots/shadowmapping.jpg | Bin 35456 -> 0 bytes screenshots/shadowmappingcascade.jpg | Bin 295035 -> 0 bytes screenshots/shadowmappingomni.jpg | Bin 70280 -> 0 bytes screenshots/specializationconstants.jpg | Bin 42310 -> 0 bytes screenshots/sphericalenvmapping.jpg | Bin 65537 -> 0 bytes screenshots/ssao.jpg | Bin 123172 -> 0 bytes screenshots/stencilbuffer.jpg | Bin 46180 -> 0 bytes screenshots/subpasses.jpg | Bin 166102 -> 0 bytes screenshots/terraintessellation.jpg | Bin 124400 -> 0 bytes screenshots/tessellation.jpg | Bin 136944 -> 0 bytes screenshots/textoverlay.jpg | Bin 38502 -> 0 bytes screenshots/texture.jpg | Bin 72427 -> 0 bytes screenshots/texture3d.jpg | Bin 33375 -> 0 bytes screenshots/texturearray.jpg | Bin 74187 -> 0 bytes screenshots/texturecubemap.jpg | Bin 93346 -> 0 bytes screenshots/texturecubemaparray.jpg | Bin 77476 -> 0 bytes screenshots/texturemipmapgen.jpg | Bin 187372 -> 0 bytes screenshots/texturesparseresidency.jpg | Bin 31310 -> 0 bytes screenshots/timelinesemaphore.jpg | Bin 25575 -> 0 bytes screenshots/triangle.jpg | Bin 15498 -> 0 bytes screenshots/trianglevulkan13.jpg | Bin 14333 -> 0 bytes screenshots/variablerateshading.jpg | Bin 159650 -> 0 bytes screenshots/vertexattributes.jpg | Bin 189403 -> 0 bytes screenshots/viewportarray.jpg | Bin 104606 -> 0 bytes screenshots/vulkanscene.jpg | Bin 92788 -> 0 bytes shaders/glsl/bloom/colorpass.frag | 14 - shaders/glsl/bloom/colorpass.frag.spv | Bin 664 -> 0 bytes shaders/glsl/bloom/colorpass.vert | 27 - shaders/glsl/bloom/colorpass.vert.spv | Bin 1448 -> 0 bytes shaders/glsl/bloom/gaussblur.frag | 44 - shaders/glsl/bloom/gaussblur.frag.spv | Bin 3792 -> 0 bytes shaders/glsl/bloom/gaussblur.vert | 14 - shaders/glsl/bloom/gaussblur.vert.spv | Bin 956 -> 0 bytes shaders/glsl/bloom/phongpass.frag | 30 - shaders/glsl/bloom/phongpass.frag.spv | Bin 2384 -> 0 bytes shaders/glsl/bloom/phongpass.vert | 38 - shaders/glsl/bloom/phongpass.vert.spv | Bin 2512 -> 0 bytes shaders/glsl/bloom/skybox.frag | 12 - shaders/glsl/bloom/skybox.frag.spv | Bin 568 -> 0 bytes shaders/glsl/bloom/skybox.vert | 24 - shaders/glsl/bloom/skybox.vert.spv | Bin 1300 -> 0 bytes shaders/glsl/bufferdeviceaddress/cube.frag | 20 - .../glsl/bufferdeviceaddress/cube.frag.spv | Bin 848 -> 0 bytes shaders/glsl/bufferdeviceaddress/cube.vert | 42 - .../glsl/bufferdeviceaddress/cube.vert.spv | Bin 2212 -> 0 bytes shaders/glsl/computecloth/cloth.comp | 145 -- shaders/glsl/computecloth/cloth.comp.spv | Bin 15204 -> 0 bytes shaders/glsl/computecloth/cloth.frag | 22 - shaders/glsl/computecloth/cloth.frag.spv | Bin 1840 -> 0 bytes shaders/glsl/computecloth/cloth.vert | 30 - shaders/glsl/computecloth/cloth.vert.spv | Bin 2216 -> 0 bytes shaders/glsl/computecloth/sphere.frag | 19 - shaders/glsl/computecloth/sphere.frag.spv | Bin 1536 -> 0 bytes shaders/glsl/computecloth/sphere.vert | 31 - shaders/glsl/computecloth/sphere.vert.spv | Bin 2036 -> 0 bytes shaders/glsl/computecullandlod/cull.comp | 110 - shaders/glsl/computecullandlod/cull.comp.spv | Bin 5084 -> 0 bytes .../glsl/computecullandlod/indirectdraw.frag | 17 - .../computecullandlod/indirectdraw.frag.spv | Bin 1164 -> 0 bytes .../glsl/computecullandlod/indirectdraw.vert | 42 - .../computecullandlod/indirectdraw.vert.spv | Bin 2344 -> 0 bytes shaders/glsl/computeheadless/headless.comp | 32 - .../glsl/computeheadless/headless.comp.spv | Bin 1740 -> 0 bytes shaders/glsl/computenbody/particle.frag | 14 - shaders/glsl/computenbody/particle.frag.spv | Bin 1044 -> 0 bytes shaders/glsl/computenbody/particle.vert | 32 - shaders/glsl/computenbody/particle.vert.spv | Bin 2232 -> 0 bytes .../glsl/computenbody/particle_calculate.comp | 75 - .../computenbody/particle_calculate.comp.spv | Bin 4872 -> 0 bytes .../glsl/computenbody/particle_integrate.comp | 30 - .../computenbody/particle_integrate.comp.spv | Bin 1536 -> 0 bytes shaders/glsl/computeparticles/particle.comp | 76 - .../glsl/computeparticles/particle.comp.spv | Bin 5140 -> 0 bytes shaders/glsl/computeparticles/particle.frag | 15 - .../glsl/computeparticles/particle.frag.spv | Bin 1112 -> 0 bytes shaders/glsl/computeparticles/particle.vert | 21 - .../glsl/computeparticles/particle.vert.spv | Bin 1076 -> 0 bytes .../glsl/computeraytracing/raytracing.comp | 245 -- .../computeraytracing/raytracing.comp.spv | Bin 16252 -> 0 bytes shaders/glsl/computeraytracing/texture.frag | 12 - .../glsl/computeraytracing/texture.frag.spv | Bin 744 -> 0 bytes shaders/glsl/computeraytracing/texture.vert | 14 - .../glsl/computeraytracing/texture.vert.spv | Bin 972 -> 0 bytes shaders/glsl/computeshader/edgedetect.comp | 44 - .../glsl/computeshader/edgedetect.comp.spv | Bin 3940 -> 0 bytes shaders/glsl/computeshader/emboss.comp | 44 - shaders/glsl/computeshader/emboss.comp.spv | Bin 3956 -> 0 bytes shaders/glsl/computeshader/sharpen.comp | 53 - shaders/glsl/computeshader/sharpen.comp.spv | Bin 4444 -> 0 bytes shaders/glsl/computeshader/texture.frag | 12 - shaders/glsl/computeshader/texture.frag.spv | Bin 568 -> 0 bytes shaders/glsl/computeshader/texture.vert | 23 - shaders/glsl/computeshader/texture.vert.spv | Bin 1232 -> 0 bytes shaders/glsl/conditionalrender/model.frag | 20 - shaders/glsl/conditionalrender/model.frag.spv | Bin 1648 -> 0 bytes shaders/glsl/conditionalrender/model.vert | 44 - shaders/glsl/conditionalrender/model.vert.spv | Bin 3060 -> 0 bytes .../glsl/conservativeraster/fullscreen.frag | 11 - .../conservativeraster/fullscreen.frag.spv | Bin 568 -> 0 bytes .../glsl/conservativeraster/fullscreen.vert | 14 - .../conservativeraster/fullscreen.vert.spv | Bin 956 -> 0 bytes shaders/glsl/conservativeraster/triangle.frag | 10 - .../glsl/conservativeraster/triangle.frag.spv | Bin 448 -> 0 bytes shaders/glsl/conservativeraster/triangle.vert | 23 - .../glsl/conservativeraster/triangle.vert.spv | Bin 1204 -> 0 bytes .../conservativeraster/triangleoverlay.frag | 8 - .../triangleoverlay.frag.spv | Bin 404 -> 0 bytes shaders/glsl/debugprintf/toon.frag | 35 - shaders/glsl/debugprintf/toon.frag.spv | Bin 3068 -> 0 bytes shaders/glsl/debugprintf/toon.vert | 37 - shaders/glsl/debugprintf/toon.vert.spv | Bin 2928 -> 0 bytes shaders/glsl/debugutils/colorpass.frag | 10 - shaders/glsl/debugutils/colorpass.frag.spv | Bin 448 -> 0 bytes shaders/glsl/debugutils/colorpass.vert | 25 - shaders/glsl/debugutils/colorpass.vert.spv | Bin 1116 -> 0 bytes shaders/glsl/debugutils/postprocess.frag | 39 - shaders/glsl/debugutils/postprocess.frag.spv | Bin 3112 -> 0 bytes shaders/glsl/debugutils/postprocess.vert | 14 - shaders/glsl/debugutils/postprocess.vert.spv | Bin 992 -> 0 bytes shaders/glsl/debugutils/toon.frag | 36 - shaders/glsl/debugutils/toon.frag.spv | Bin 2980 -> 0 bytes shaders/glsl/debugutils/toon.vert | 38 - shaders/glsl/debugutils/toon.vert.spv | Bin 2748 -> 0 bytes shaders/glsl/deferred/deferred.frag | 94 - shaders/glsl/deferred/deferred.frag.spv | Bin 4684 -> 0 bytes shaders/glsl/deferred/deferred.vert | 9 - shaders/glsl/deferred/deferred.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/deferred/mrt.frag | 29 - shaders/glsl/deferred/mrt.frag.spv | Bin 2208 -> 0 bytes shaders/glsl/deferred/mrt.vert | 41 - shaders/glsl/deferred/mrt.vert.spv | Bin 2968 -> 0 bytes .../glsl/deferredmultisampling/deferred.frag | 120 - .../deferredmultisampling/deferred.frag.spv | Bin 7124 -> 0 bytes .../glsl/deferredmultisampling/deferred.vert | 9 - .../deferredmultisampling/deferred.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/deferredmultisampling/mrt.frag | 29 - .../glsl/deferredmultisampling/mrt.frag.spv | Bin 2208 -> 0 bytes shaders/glsl/deferredmultisampling/mrt.vert | 46 - .../glsl/deferredmultisampling/mrt.vert.spv | Bin 2916 -> 0 bytes shaders/glsl/deferredshadows/deferred.frag | 168 -- .../glsl/deferredshadows/deferred.frag.spv | Bin 10412 -> 0 bytes shaders/glsl/deferredshadows/deferred.vert | 9 - .../glsl/deferredshadows/deferred.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/deferredshadows/geom.spv | Bin 1620 -> 0 bytes shaders/glsl/deferredshadows/mrt.frag | 29 - shaders/glsl/deferredshadows/mrt.frag.spv | Bin 2208 -> 0 bytes shaders/glsl/deferredshadows/mrt.vert | 41 - shaders/glsl/deferredshadows/mrt.vert.spv | Bin 3104 -> 0 bytes shaders/glsl/deferredshadows/shadow.geom | 27 - shaders/glsl/deferredshadows/shadow.geom.spv | Bin 2300 -> 0 bytes shaders/glsl/deferredshadows/shadow.vert | 11 - shaders/glsl/deferredshadows/shadow.vert.spv | Bin 892 -> 0 bytes shaders/glsl/descriptorbuffer/cube.frag | 14 - shaders/glsl/descriptorbuffer/cube.frag.spv | Bin 848 -> 0 bytes shaders/glsl/descriptorbuffer/cube.vert | 31 - shaders/glsl/descriptorbuffer/cube.vert.spv | Bin 1792 -> 0 bytes .../descriptorindexing.frag | 17 - .../descriptorindexing.frag.spv | Bin 868 -> 0 bytes .../descriptorindexing.vert | 25 - .../descriptorindexing.vert.spv | Bin 1864 -> 0 bytes shaders/glsl/descriptorsets/cube.frag | 14 - shaders/glsl/descriptorsets/cube.frag.spv | Bin 848 -> 0 bytes shaders/glsl/descriptorsets/cube.vert | 28 - shaders/glsl/descriptorsets/cube.vert.spv | Bin 1692 -> 0 bytes shaders/glsl/displacement/base.frag | 26 - shaders/glsl/displacement/base.frag.spv | Bin 2104 -> 0 bytes shaders/glsl/displacement/base.vert | 15 - shaders/glsl/displacement/base.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/displacement/displacement.tesc | 34 - .../glsl/displacement/displacement.tesc.spv | Bin 2996 -> 0 bytes shaders/glsl/displacement/displacement.tese | 37 - .../glsl/displacement/displacement.tese.spv | Bin 4232 -> 0 bytes shaders/glsl/distancefieldfonts/bitmap.frag | 12 - .../glsl/distancefieldfonts/bitmap.frag.spv | Bin 648 -> 0 bytes shaders/glsl/distancefieldfonts/bitmap.vert | 18 - .../glsl/distancefieldfonts/bitmap.vert.spv | Bin 1436 -> 0 bytes shaders/glsl/distancefieldfonts/sdf.frag | 34 - shaders/glsl/distancefieldfonts/sdf.frag.spv | Bin 2344 -> 0 bytes shaders/glsl/distancefieldfonts/sdf.vert | 21 - shaders/glsl/distancefieldfonts/sdf.vert.spv | Bin 1584 -> 0 bytes shaders/glsl/dynamicrendering/texture.frag | 24 - .../glsl/dynamicrendering/texture.frag.spv | Bin 1908 -> 0 bytes shaders/glsl/dynamicrendering/texture.vert | 33 - .../glsl/dynamicrendering/texture.vert.spv | Bin 3224 -> 0 bytes shaders/glsl/dynamicuniformbuffer/base.frag | 10 - .../glsl/dynamicuniformbuffer/base.frag.spv | Bin 500 -> 0 bytes shaders/glsl/dynamicuniformbuffer/base.vert | 30 - .../glsl/dynamicuniformbuffer/base.vert.spv | Bin 1840 -> 0 bytes shaders/glsl/gears/gears.frag | 21 - shaders/glsl/gears/gears.frag.spv | Bin 1796 -> 0 bytes shaders/glsl/gears/gears.vert | 30 - shaders/glsl/gears/gears.vert.spv | Bin 2924 -> 0 bytes shaders/glsl/geometryshader/base.frag | 10 - shaders/glsl/geometryshader/base.frag.spv | Bin 500 -> 0 bytes shaders/glsl/geometryshader/base.vert | 12 - shaders/glsl/geometryshader/base.vert.spv | Bin 980 -> 0 bytes shaders/glsl/geometryshader/mesh.frag | 20 - shaders/glsl/geometryshader/mesh.frag.spv | Bin 1648 -> 0 bytes shaders/glsl/geometryshader/mesh.vert | 30 - shaders/glsl/geometryshader/mesh.vert.spv | Bin 2416 -> 0 bytes shaders/glsl/geometryshader/normaldebug.geom | 34 - .../glsl/geometryshader/normaldebug.geom.spv | Bin 2780 -> 0 bytes shaders/glsl/gltfloading/mesh.frag | 24 - shaders/glsl/gltfloading/mesh.frag.spv | Bin 2008 -> 0 bytes shaders/glsl/gltfloading/mesh.vert | 38 - shaders/glsl/gltfloading/mesh.vert.spv | Bin 3396 -> 0 bytes shaders/glsl/gltfscenerendering/scene.frag | 41 - .../glsl/gltfscenerendering/scene.frag.spv | Bin 3428 -> 0 bytes shaders/glsl/gltfscenerendering/scene.vert | 40 - .../glsl/gltfscenerendering/scene.vert.spv | Bin 3188 -> 0 bytes shaders/glsl/gltfskinning/skinnedmodel.frag | 24 - .../glsl/gltfskinning/skinnedmodel.frag.spv | Bin 2024 -> 0 bytes shaders/glsl/gltfskinning/skinnedmodel.vert | 52 - .../glsl/gltfskinning/skinnedmodel.vert.spv | Bin 5312 -> 0 bytes .../glsl/graphicspipelinelibrary/shared.vert | 33 - .../graphicspipelinelibrary/shared.vert.spv | Bin 2548 -> 0 bytes .../glsl/graphicspipelinelibrary/uber.frag | 61 - .../graphicspipelinelibrary/uber.frag.spv | Bin 3984 -> 0 bytes shaders/glsl/hdr/bloom.frag | 63 - shaders/glsl/hdr/bloom.frag.spv | Bin 2516 -> 0 bytes shaders/glsl/hdr/bloom.vert | 14 - shaders/glsl/hdr/bloom.vert.spv | Bin 956 -> 0 bytes shaders/glsl/hdr/composition.frag | 13 - shaders/glsl/hdr/composition.frag.spv | Bin 636 -> 0 bytes shaders/glsl/hdr/composition.vert | 9 - shaders/glsl/hdr/composition.vert.spv | Bin 956 -> 0 bytes shaders/glsl/hdr/gbuffer.frag | 93 - shaders/glsl/hdr/gbuffer.frag.spv | Bin 6364 -> 0 bytes shaders/glsl/hdr/gbuffer.vert | 41 - shaders/glsl/hdr/gbuffer.vert.spv | Bin 3584 -> 0 bytes shaders/glsl/indirectdraw/ground.frag | 12 - shaders/glsl/indirectdraw/ground.frag.spv | Bin 568 -> 0 bytes shaders/glsl/indirectdraw/ground.vert | 21 - shaders/glsl/indirectdraw/ground.vert.spv | Bin 1632 -> 0 bytes shaders/glsl/indirectdraw/indirectdraw.frag | 27 - .../glsl/indirectdraw/indirectdraw.frag.spv | Bin 1684 -> 0 bytes shaders/glsl/indirectdraw/indirectdraw.vert | 73 - .../glsl/indirectdraw/indirectdraw.vert.spv | Bin 4852 -> 0 bytes shaders/glsl/indirectdraw/skysphere.frag | 14 - shaders/glsl/indirectdraw/skysphere.frag.spv | Bin 952 -> 0 bytes shaders/glsl/indirectdraw/skysphere.vert | 20 - shaders/glsl/indirectdraw/skysphere.vert.spv | Bin 2040 -> 0 bytes shaders/glsl/inlineuniformblocks/pbr.frag | 116 - shaders/glsl/inlineuniformblocks/pbr.frag.spv | Bin 7240 -> 0 bytes shaders/glsl/inlineuniformblocks/pbr.vert | 27 - shaders/glsl/inlineuniformblocks/pbr.vert.spv | Bin 2400 -> 0 bytes .../glsl/inputattachments/attachmentread.frag | 33 - .../inputattachments/attachmentread.frag.spv | Bin 2500 -> 0 bytes .../glsl/inputattachments/attachmentread.vert | 10 - .../inputattachments/attachmentread.vert.spv | Bin 860 -> 0 bytes .../inputattachments/attachmentwrite.frag | 23 - .../inputattachments/attachmentwrite.frag.spv | Bin 1364 -> 0 bytes .../inputattachments/attachmentwrite.vert | 29 - .../inputattachments/attachmentwrite.vert.spv | Bin 1772 -> 0 bytes shaders/glsl/instancing/instancing.frag | 23 - shaders/glsl/instancing/instancing.frag.spv | Bin 2324 -> 0 bytes shaders/glsl/instancing/instancing.vert | 81 - shaders/glsl/instancing/instancing.vert.spv | Bin 6392 -> 0 bytes shaders/glsl/instancing/planet.frag | 23 - shaders/glsl/instancing/planet.frag.spv | Bin 2148 -> 0 bytes shaders/glsl/instancing/planet.vert | 32 - shaders/glsl/instancing/planet.vert.spv | Bin 2928 -> 0 bytes shaders/glsl/instancing/starfield.frag | 34 - shaders/glsl/instancing/starfield.frag.spv | Bin 2412 -> 0 bytes shaders/glsl/instancing/starfield.vert | 9 - shaders/glsl/instancing/starfield.vert.spv | Bin 1260 -> 0 bytes shaders/glsl/meshshader/meshshader.frag | 19 - shaders/glsl/meshshader/meshshader.frag.spv | Bin 516 -> 0 bytes shaders/glsl/meshshader/meshshader.mesh | 52 - shaders/glsl/meshshader/meshshader.mesh.spv | Bin 3016 -> 0 bytes shaders/glsl/meshshader/meshshader.task | 13 - shaders/glsl/meshshader/meshshader.task.spv | Bin 300 -> 0 bytes shaders/glsl/multisampling/mesh.frag | 24 - shaders/glsl/multisampling/mesh.frag.spv | Bin 2024 -> 0 bytes shaders/glsl/multisampling/mesh.vert | 33 - shaders/glsl/multisampling/mesh.vert.spv | Bin 2952 -> 0 bytes shaders/glsl/multithreading/phong.frag | 20 - shaders/glsl/multithreading/phong.frag.spv | Bin 1484 -> 0 bytes shaders/glsl/multithreading/phong.vert | 39 - shaders/glsl/multithreading/phong.vert.spv | Bin 2828 -> 0 bytes shaders/glsl/multithreading/starsphere.frag | 39 - .../glsl/multithreading/starsphere.frag.spv | Bin 2816 -> 0 bytes shaders/glsl/multithreading/starsphere.vert | 16 - .../glsl/multithreading/starsphere.vert.spv | Bin 1176 -> 0 bytes shaders/glsl/multiview/multiview.frag | 20 - shaders/glsl/multiview/multiview.frag.spv | Bin 1644 -> 0 bytes shaders/glsl/multiview/multiview.vert | 35 - shaders/glsl/multiview/multiview.vert.spv | Bin 2832 -> 0 bytes shaders/glsl/multiview/viewdisplay.frag | 25 - shaders/glsl/multiview/viewdisplay.frag.spv | Bin 2296 -> 0 bytes shaders/glsl/multiview/viewdisplay.vert | 9 - shaders/glsl/multiview/viewdisplay.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/negativeviewportheight/quad.frag | 11 - .../glsl/negativeviewportheight/quad.frag.spv | Bin 564 -> 0 bytes shaders/glsl/negativeviewportheight/quad.vert | 12 - .../glsl/negativeviewportheight/quad.vert.spv | Bin 1004 -> 0 bytes shaders/glsl/occlusionquery/mesh.frag | 28 - shaders/glsl/occlusionquery/mesh.frag.spv | Bin 1740 -> 0 bytes shaders/glsl/occlusionquery/mesh.vert | 35 - shaders/glsl/occlusionquery/mesh.vert.spv | Bin 2956 -> 0 bytes shaders/glsl/occlusionquery/occluder.frag | 10 - shaders/glsl/occlusionquery/occluder.frag.spv | Bin 500 -> 0 bytes shaders/glsl/occlusionquery/occluder.vert | 22 - shaders/glsl/occlusionquery/occluder.vert.spv | Bin 1828 -> 0 bytes shaders/glsl/occlusionquery/simple.frag | 10 - shaders/glsl/occlusionquery/simple.frag.spv | Bin 424 -> 0 bytes shaders/glsl/occlusionquery/simple.vert | 19 - shaders/glsl/occlusionquery/simple.vert.spv | Bin 1572 -> 0 bytes shaders/glsl/offscreen/mirror.frag | 37 - shaders/glsl/offscreen/mirror.frag.spv | Bin 2308 -> 0 bytes shaders/glsl/offscreen/mirror.vert | 18 - shaders/glsl/offscreen/mirror.vert.spv | Bin 1488 -> 0 bytes shaders/glsl/offscreen/phong.frag | 25 - shaders/glsl/offscreen/phong.frag.spv | Bin 1948 -> 0 bytes shaders/glsl/offscreen/phong.vert | 31 - shaders/glsl/offscreen/phong.vert.spv | Bin 2652 -> 0 bytes shaders/glsl/offscreen/quad.frag | 12 - shaders/glsl/offscreen/quad.frag.spv | Bin 568 -> 0 bytes shaders/glsl/offscreen/quad.vert | 9 - shaders/glsl/offscreen/quad.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/oit/color.frag | 56 - shaders/glsl/oit/color.frag.spv | Bin 3664 -> 0 bytes shaders/glsl/oit/color.vert | 7 - shaders/glsl/oit/color.vert.spv | Bin 1152 -> 0 bytes shaders/glsl/oit/geometry.frag | 46 - shaders/glsl/oit/geometry.frag.spv | Bin 2152 -> 0 bytes shaders/glsl/oit/geometry.vert | 20 - shaders/glsl/oit/geometry.vert.spv | Bin 1640 -> 0 bytes shaders/glsl/parallaxmapping/parallax.frag | 109 - .../glsl/parallaxmapping/parallax.frag.spv | Bin 8464 -> 0 bytes shaders/glsl/parallaxmapping/parallax.vert | 36 - .../glsl/parallaxmapping/parallax.vert.spv | Bin 3852 -> 0 bytes shaders/glsl/particlesystem/normalmap.frag | 41 - .../glsl/particlesystem/normalmap.frag.spv | Bin 2876 -> 0 bytes shaders/glsl/particlesystem/normalmap.vert | 50 - .../glsl/particlesystem/normalmap.vert.spv | Bin 4532 -> 0 bytes shaders/glsl/particlesystem/particle.frag | 42 - shaders/glsl/particlesystem/particle.frag.spv | Bin 2796 -> 0 bytes shaders/glsl/particlesystem/particle.vert | 39 - shaders/glsl/particlesystem/particle.vert.spv | Bin 2868 -> 0 bytes shaders/glsl/pbrbasic/pbr.frag | 126 - shaders/glsl/pbrbasic/pbr.frag.spv | Bin 7600 -> 0 bytes shaders/glsl/pbrbasic/pbr.vert | 32 - shaders/glsl/pbrbasic/pbr.vert.spv | Bin 2196 -> 0 bytes shaders/glsl/pbribl/filtercube.vert | 19 - shaders/glsl/pbribl/filtercube.vert.spv | Bin 972 -> 0 bytes shaders/glsl/pbribl/genbrdflut.frag | 90 - shaders/glsl/pbribl/genbrdflut.frag.spv | Bin 8052 -> 0 bytes shaders/glsl/pbribl/genbrdflut.vert | 9 - shaders/glsl/pbribl/genbrdflut.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/pbribl/irradiancecube.frag | 37 - shaders/glsl/pbribl/irradiancecube.frag.spv | Bin 2856 -> 0 bytes shaders/glsl/pbribl/pbribl.frag | 162 -- shaders/glsl/pbribl/pbribl.frag.spv | Bin 12872 -> 0 bytes shaders/glsl/pbribl/pbribl.vert | 36 - shaders/glsl/pbribl/pbribl.vert.spv | Bin 2528 -> 0 bytes shaders/glsl/pbribl/prefilterenvmap.frag | 105 - shaders/glsl/pbribl/prefilterenvmap.frag.spv | Bin 9060 -> 0 bytes shaders/glsl/pbribl/skybox.frag | 39 - shaders/glsl/pbribl/skybox.frag.spv | Bin 2788 -> 0 bytes shaders/glsl/pbribl/skybox.vert | 24 - shaders/glsl/pbribl/skybox.vert.spv | Bin 1288 -> 0 bytes shaders/glsl/pbrtexture/filtercube.vert | 19 - shaders/glsl/pbrtexture/filtercube.vert.spv | Bin 972 -> 0 bytes shaders/glsl/pbrtexture/genbrdflut.frag | 90 - shaders/glsl/pbrtexture/genbrdflut.frag.spv | Bin 8052 -> 0 bytes shaders/glsl/pbrtexture/genbrdflut.vert | 9 - shaders/glsl/pbrtexture/genbrdflut.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/pbrtexture/irradiancecube.frag | 37 - .../glsl/pbrtexture/irradiancecube.frag.spv | Bin 2856 -> 0 bytes shaders/glsl/pbrtexture/pbrtexture.frag | 173 -- shaders/glsl/pbrtexture/pbrtexture.frag.spv | Bin 14112 -> 0 bytes shaders/glsl/pbrtexture/pbrtexture.vert | 30 - shaders/glsl/pbrtexture/pbrtexture.vert.spv | Bin 2952 -> 0 bytes shaders/glsl/pbrtexture/prefilterenvmap.frag | 105 - .../glsl/pbrtexture/prefilterenvmap.frag.spv | Bin 9060 -> 0 bytes shaders/glsl/pbrtexture/skybox.frag | 39 - shaders/glsl/pbrtexture/skybox.frag.spv | Bin 2788 -> 0 bytes shaders/glsl/pbrtexture/skybox.vert | 24 - shaders/glsl/pbrtexture/skybox.vert.spv | Bin 1288 -> 0 bytes shaders/glsl/pipelines/phong.frag | 26 - shaders/glsl/pipelines/phong.frag.spv | Bin 2144 -> 0 bytes shaders/glsl/pipelines/phong.vert | 30 - shaders/glsl/pipelines/phong.vert.spv | Bin 2772 -> 0 bytes shaders/glsl/pipelines/toon.frag | 35 - shaders/glsl/pipelines/toon.frag.spv | Bin 2896 -> 0 bytes shaders/glsl/pipelines/toon.vert | 30 - shaders/glsl/pipelines/toon.vert.spv | Bin 2772 -> 0 bytes shaders/glsl/pipelines/wireframe.frag | 10 - shaders/glsl/pipelines/wireframe.frag.spv | Bin 484 -> 0 bytes shaders/glsl/pipelines/wireframe.vert | 18 - shaders/glsl/pipelines/wireframe.vert.spv | Bin 1320 -> 0 bytes shaders/glsl/pipelinestatistics/scene.frag | 19 - .../glsl/pipelinestatistics/scene.frag.spv | Bin 1484 -> 0 bytes shaders/glsl/pipelinestatistics/scene.tesc | 30 - .../glsl/pipelinestatistics/scene.tesc.spv | Bin 2680 -> 0 bytes shaders/glsl/pipelinestatistics/scene.tese | 24 - .../glsl/pipelinestatistics/scene.tese.spv | Bin 3028 -> 0 bytes shaders/glsl/pipelinestatistics/scene.vert | 36 - .../glsl/pipelinestatistics/scene.vert.spv | Bin 3212 -> 0 bytes shaders/glsl/pushconstants/pushconstants.frag | 10 - .../glsl/pushconstants/pushconstants.frag.spv | Bin 448 -> 0 bytes shaders/glsl/pushconstants/pushconstants.vert | 29 - .../glsl/pushconstants/pushconstants.vert.spv | Bin 2304 -> 0 bytes shaders/glsl/pushdescriptors/cube.frag | 14 - shaders/glsl/pushdescriptors/cube.frag.spv | Bin 848 -> 0 bytes shaders/glsl/pushdescriptors/cube.vert | 31 - shaders/glsl/pushdescriptors/cube.vert.spv | Bin 1800 -> 0 bytes shaders/glsl/radialblur/colorpass.frag | 21 - shaders/glsl/radialblur/colorpass.frag.spv | Bin 1352 -> 0 bytes shaders/glsl/radialblur/colorpass.vert | 26 - shaders/glsl/radialblur/colorpass.vert.spv | Bin 1452 -> 0 bytes shaders/glsl/radialblur/phongpass.frag | 33 - shaders/glsl/radialblur/phongpass.frag.spv | Bin 2700 -> 0 bytes shaders/glsl/radialblur/phongpass.vert | 34 - shaders/glsl/radialblur/phongpass.vert.spv | Bin 2056 -> 0 bytes shaders/glsl/radialblur/radialblur.frag | 35 - shaders/glsl/radialblur/radialblur.frag.spv | Bin 2424 -> 0 bytes shaders/glsl/radialblur/radialblur.vert | 14 - shaders/glsl/radialblur/radialblur.vert.spv | Bin 956 -> 0 bytes shaders/glsl/rayquery/scene.frag | 37 - shaders/glsl/rayquery/scene.frag.spv | Bin 1924 -> 0 bytes shaders/glsl/rayquery/scene.vert | 33 - shaders/glsl/rayquery/scene.vert.spv | Bin 2972 -> 0 bytes shaders/glsl/raytracingbasic/closesthit.rchit | 12 - .../glsl/raytracingbasic/closesthit.rchit.spv | Bin 788 -> 0 bytes shaders/glsl/raytracingbasic/miss.rmiss | 9 - shaders/glsl/raytracingbasic/miss.rmiss.spv | Bin 384 -> 0 bytes shaders/glsl/raytracingbasic/raygen.rgen | 32 - shaders/glsl/raytracingbasic/raygen.rgen.spv | Bin 2844 -> 0 bytes .../glsl/raytracingcallable/callable1.rcall | 11 - .../raytracingcallable/callable1.rcall.spv | Bin 880 -> 0 bytes .../glsl/raytracingcallable/callable2.rcall | 9 - .../raytracingcallable/callable2.rcall.spv | Bin 384 -> 0 bytes .../glsl/raytracingcallable/callable3.rcall | 11 - .../raytracingcallable/callable3.rcall.spv | Bin 788 -> 0 bytes .../glsl/raytracingcallable/closesthit.rchit | 17 - .../raytracingcallable/closesthit.rchit.spv | Bin 768 -> 0 bytes shaders/glsl/raytracingcallable/miss.rmiss | 9 - .../glsl/raytracingcallable/miss.rmiss.spv | Bin 384 -> 0 bytes shaders/glsl/raytracingcallable/raygen.rgen | 32 - .../glsl/raytracingcallable/raygen.rgen.spv | Bin 3048 -> 0 bytes shaders/glsl/raytracinggltf/anyhit.rahit | 47 - shaders/glsl/raytracinggltf/anyhit.rahit.spv | Bin 7120 -> 0 bytes .../glsl/raytracinggltf/bufferreferences.glsl | 15 - shaders/glsl/raytracinggltf/closesthit.rchit | 63 - .../glsl/raytracinggltf/closesthit.rchit.spv | Bin 7808 -> 0 bytes .../glsl/raytracinggltf/geometrytypes.glsl | 50 - shaders/glsl/raytracinggltf/miss.rmiss | 15 - shaders/glsl/raytracinggltf/miss.rmiss.spv | Bin 352 -> 0 bytes shaders/glsl/raytracinggltf/random.glsl | 37 - shaders/glsl/raytracinggltf/raygen.rgen | 77 - shaders/glsl/raytracinggltf/raygen.rgen.spv | Bin 7260 -> 0 bytes shaders/glsl/raytracinggltf/shadow.rmiss | 9 - shaders/glsl/raytracinggltf/shadow.rmiss.spv | Bin 304 -> 0 bytes .../raytracingintersection/closesthit.rchit | 35 - .../closesthit.rchit.spv | Bin 2600 -> 0 bytes .../raytracingintersection/intersection.rint | 39 - .../intersection.rint.spv | Bin 2444 -> 0 bytes .../glsl/raytracingintersection/miss.rmiss | 9 - .../raytracingintersection/miss.rmiss.spv | Bin 368 -> 0 bytes .../glsl/raytracingintersection/raygen.rgen | 33 - .../raytracingintersection/raygen.rgen.spv | Bin 3080 -> 0 bytes .../raytracingpositionfetch/closesthit.rchit | 46 - .../closesthit.rchit.spv | Bin 2920 -> 0 bytes .../glsl/raytracingpositionfetch/miss.rmiss | 15 - .../raytracingpositionfetch/miss.rmiss.spv | Bin 368 -> 0 bytes .../glsl/raytracingpositionfetch/raygen.rgen | 39 - .../raytracingpositionfetch/raygen.rgen.spv | Bin 2876 -> 0 bytes .../raytracingreflections/closesthit.rchit | 76 - .../closesthit.rchit.spv | Bin 5800 -> 0 bytes shaders/glsl/raytracingreflections/miss.rmiss | 25 - .../glsl/raytracingreflections/miss.rmiss.spv | Bin 1300 -> 0 bytes .../glsl/raytracingreflections/raygen.rgen | 62 - .../raytracingreflections/raygen.rgen.spv | Bin 4660 -> 0 bytes .../glsl/raytracingsbtdata/closesthit.rchit | 19 - .../raytracingsbtdata/closesthit.rchit.spv | Bin 824 -> 0 bytes shaders/glsl/raytracingsbtdata/miss.rmiss | 17 - shaders/glsl/raytracingsbtdata/miss.rmiss.spv | Bin 724 -> 0 bytes shaders/glsl/raytracingsbtdata/raygen.rgen | 52 - .../glsl/raytracingsbtdata/raygen.rgen.spv | Bin 4080 -> 0 bytes .../glsl/raytracingshadows/closesthit.rchit | 75 - .../raytracingshadows/closesthit.rchit.spv | Bin 5844 -> 0 bytes shaders/glsl/raytracingshadows/miss.rmiss | 9 - shaders/glsl/raytracingshadows/miss.rmiss.spv | Bin 384 -> 0 bytes shaders/glsl/raytracingshadows/raygen.rgen | 33 - .../glsl/raytracingshadows/raygen.rgen.spv | Bin 3096 -> 0 bytes shaders/glsl/raytracingshadows/shadow.rmiss | 9 - .../glsl/raytracingshadows/shadow.rmiss.spv | Bin 320 -> 0 bytes shaders/glsl/raytracingtextures/anyhit.rahit | 31 - .../glsl/raytracingtextures/anyhit.rahit.spv | Bin 4412 -> 0 bytes .../raytracingtextures/bufferreferences.glsl | 13 - .../glsl/raytracingtextures/closesthit.rchit | 31 - .../raytracingtextures/closesthit.rchit.spv | Bin 4520 -> 0 bytes .../raytracingtextures/geometrytypes.glsl | 39 - shaders/glsl/raytracingtextures/miss.rmiss | 15 - .../glsl/raytracingtextures/miss.rmiss.spv | Bin 368 -> 0 bytes shaders/glsl/raytracingtextures/raygen.rgen | 38 - .../glsl/raytracingtextures/raygen.rgen.spv | Bin 2828 -> 0 bytes shaders/glsl/renderheadless/triangle.frag | 10 - shaders/glsl/renderheadless/triangle.frag.spv | Bin 500 -> 0 bytes shaders/glsl/renderheadless/triangle.vert | 20 - shaders/glsl/renderheadless/triangle.vert.spv | Bin 1028 -> 0 bytes shaders/glsl/screenshot/mesh.frag | 20 - shaders/glsl/screenshot/mesh.frag.spv | Bin 1648 -> 0 bytes shaders/glsl/screenshot/mesh.vert | 36 - shaders/glsl/screenshot/mesh.vert.spv | Bin 2396 -> 0 bytes shaders/glsl/shaderobjects/phong.frag | 26 - shaders/glsl/shaderobjects/phong.frag.spv | Bin 2144 -> 0 bytes shaders/glsl/shaderobjects/phong.vert | 30 - shaders/glsl/shaderobjects/phong.vert.spv | Bin 2772 -> 0 bytes shaders/glsl/shadowmapping/offscreen.frag | 8 - shaders/glsl/shadowmapping/offscreen.frag.spv | Bin 348 -> 0 bytes shaders/glsl/shadowmapping/offscreen.vert | 19 - shaders/glsl/shadowmapping/offscreen.vert.spv | Bin 900 -> 0 bytes shaders/glsl/shadowmapping/quad.frag | 32 - shaders/glsl/shadowmapping/quad.frag.spv | Bin 2128 -> 0 bytes shaders/glsl/shadowmapping/quad.vert | 9 - shaders/glsl/shadowmapping/quad.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/shadowmapping/scene.frag | 66 - shaders/glsl/shadowmapping/scene.frag.spv | Bin 5028 -> 0 bytes shaders/glsl/shadowmapping/scene.vert | 45 - shaders/glsl/shadowmapping/scene.vert.spv | Bin 3356 -> 0 bytes .../shadowmappingcascade/debugshadowmap.frag | 14 - .../debugshadowmap.frag.spv | Bin 1020 -> 0 bytes .../shadowmappingcascade/debugshadowmap.vert | 20 - .../debugshadowmap.vert.spv | Bin 1304 -> 0 bytes .../glsl/shadowmappingcascade/depthpass.frag | 13 - .../shadowmappingcascade/depthpass.frag.spv | Bin 696 -> 0 bytes .../glsl/shadowmappingcascade/depthpass.vert | 25 - .../shadowmappingcascade/depthpass.vert.spv | Bin 1780 -> 0 bytes shaders/glsl/shadowmappingcascade/scene.frag | 126 - .../glsl/shadowmappingcascade/scene.frag.spv | Bin 9240 -> 0 bytes shaders/glsl/shadowmappingcascade/scene.vert | 35 - .../glsl/shadowmappingcascade/scene.vert.spv | Bin 2588 -> 0 bytes .../shadowmappingomni/cubemapdisplay.frag | 54 - .../shadowmappingomni/cubemapdisplay.frag.spv | Bin 3744 -> 0 bytes .../shadowmappingomni/cubemapdisplay.vert | 17 - .../shadowmappingomni/cubemapdisplay.vert.spv | Bin 1528 -> 0 bytes shaders/glsl/shadowmappingomni/offscreen.frag | 13 - .../glsl/shadowmappingomni/offscreen.frag.spv | Bin 640 -> 0 bytes shaders/glsl/shadowmappingomni/offscreen.vert | 32 - .../glsl/shadowmappingomni/offscreen.vert.spv | Bin 1776 -> 0 bytes shaders/glsl/shadowmappingomni/scene.frag | 40 - shaders/glsl/shadowmappingomni/scene.frag.spv | Bin 2496 -> 0 bytes shaders/glsl/shadowmappingomni/scene.vert | 39 - shaders/glsl/shadowmappingomni/scene.vert.spv | Bin 2304 -> 0 bytes .../glsl/specializationconstants/uber.frag | 71 - .../specializationconstants/uber.frag.spv | Bin 4972 -> 0 bytes .../glsl/specializationconstants/uber.vert | 38 - .../specializationconstants/uber.vert.spv | Bin 2748 -> 0 bytes shaders/glsl/sphericalenvmapping/sem.frag | 19 - shaders/glsl/sphericalenvmapping/sem.frag.spv | Bin 1876 -> 0 bytes shaders/glsl/sphericalenvmapping/sem.vert | 31 - shaders/glsl/sphericalenvmapping/sem.vert.spv | Bin 3036 -> 0 bytes shaders/glsl/ssao/blur.frag | 25 - shaders/glsl/ssao/blur.frag.spv | Bin 1868 -> 0 bytes shaders/glsl/ssao/composition.frag | 52 - shaders/glsl/ssao/composition.frag.spv | Bin 3432 -> 0 bytes shaders/glsl/ssao/fullscreen.vert | 14 - shaders/glsl/ssao/fullscreen.vert.spv | Bin 956 -> 0 bytes shaders/glsl/ssao/gbuffer.frag | 34 - shaders/glsl/ssao/gbuffer.frag.spv | Bin 2716 -> 0 bytes shaders/glsl/ssao/gbuffer.vert | 34 - shaders/glsl/ssao/gbuffer.vert.spv | Bin 2500 -> 0 bytes shaders/glsl/ssao/ssao.frag | 65 - shaders/glsl/ssao/ssao.frag.spv | Bin 5148 -> 0 bytes shaders/glsl/stencilbuffer/outline.frag | 8 - shaders/glsl/stencilbuffer/outline.frag.spv | Bin 340 -> 0 bytes shaders/glsl/stencilbuffer/outline.vert | 24 - shaders/glsl/stencilbuffer/outline.vert.spv | Bin 1520 -> 0 bytes shaders/glsl/stencilbuffer/toon.frag | 30 - shaders/glsl/stencilbuffer/toon.frag.spv | Bin 2144 -> 0 bytes shaders/glsl/stencilbuffer/toon.vert | 31 - shaders/glsl/stencilbuffer/toon.vert.spv | Bin 2432 -> 0 bytes shaders/glsl/subpasses/composition.frag | 50 - shaders/glsl/subpasses/composition.frag.spv | Bin 3152 -> 0 bytes shaders/glsl/subpasses/composition.vert | 14 - shaders/glsl/subpasses/composition.vert.spv | Bin 956 -> 0 bytes shaders/glsl/subpasses/gbuffer.frag | 36 - shaders/glsl/subpasses/gbuffer.frag.spv | Bin 2008 -> 0 bytes shaders/glsl/subpasses/gbuffer.vert | 39 - shaders/glsl/subpasses/gbuffer.vert.spv | Bin 2212 -> 0 bytes shaders/glsl/subpasses/transparent.frag | 34 - shaders/glsl/subpasses/transparent.frag.spv | Bin 2032 -> 0 bytes shaders/glsl/subpasses/transparent.vert | 24 - shaders/glsl/subpasses/transparent.vert.spv | Bin 1844 -> 0 bytes .../glsl/terraintessellation/skysphere.frag | 13 - .../terraintessellation/skysphere.frag.spv | Bin 796 -> 0 bytes .../glsl/terraintessellation/skysphere.vert | 18 - .../terraintessellation/skysphere.vert.spv | Bin 1332 -> 0 bytes shaders/glsl/terraintessellation/terrain.frag | 61 - .../glsl/terraintessellation/terrain.frag.spv | Bin 4628 -> 0 bytes shaders/glsl/terraintessellation/terrain.tesc | 116 - .../glsl/terraintessellation/terrain.tesc.spv | Bin 7692 -> 0 bytes shaders/glsl/terraintessellation/terrain.tese | 54 - .../glsl/terraintessellation/terrain.tese.spv | Bin 5160 -> 0 bytes shaders/glsl/terraintessellation/terrain.vert | 15 - .../glsl/terraintessellation/terrain.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/tessellation/base.frag | 18 - shaders/glsl/tessellation/base.frag.spv | Bin 1196 -> 0 bytes shaders/glsl/tessellation/base.vert | 15 - shaders/glsl/tessellation/base.vert.spv | Bin 1160 -> 0 bytes shaders/glsl/tessellation/passthrough.tesc | 24 - .../glsl/tessellation/passthrough.tesc.spv | Bin 2348 -> 0 bytes shaders/glsl/tessellation/passthrough.tese | 28 - .../glsl/tessellation/passthrough.tese.spv | Bin 3084 -> 0 bytes shaders/glsl/tessellation/pntriangles.tesc | 86 - .../glsl/tessellation/pntriangles.tesc.spv | Bin 8504 -> 0 bytes shaders/glsl/tessellation/pntriangles.tese | 89 - .../glsl/tessellation/pntriangles.tese.spv | Bin 8872 -> 0 bytes shaders/glsl/textoverlay/mesh.frag | 19 - shaders/glsl/textoverlay/mesh.frag.spv | Bin 1504 -> 0 bytes shaders/glsl/textoverlay/mesh.vert | 35 - shaders/glsl/textoverlay/mesh.vert.spv | Bin 2676 -> 0 bytes shaders/glsl/textoverlay/text.frag | 13 - shaders/glsl/textoverlay/text.frag.spv | Bin 720 -> 0 bytes shaders/glsl/textoverlay/text.vert | 17 - shaders/glsl/textoverlay/text.vert.spv | Bin 764 -> 0 bytes shaders/glsl/texture/texture.frag | 25 - shaders/glsl/texture/texture.frag.spv | Bin 2004 -> 0 bytes shaders/glsl/texture/texture.vert | 41 - shaders/glsl/texture/texture.vert.spv | Bin 3216 -> 0 bytes shaders/glsl/texture3d/texture3d.frag | 25 - shaders/glsl/texture3d/texture3d.frag.spv | Bin 1936 -> 0 bytes shaders/glsl/texture3d/texture3d.vert | 40 - shaders/glsl/texture3d/texture3d.vert.spv | Bin 3252 -> 0 bytes shaders/glsl/texturearray/instancing.frag | 12 - shaders/glsl/texturearray/instancing.frag.spv | Bin 568 -> 0 bytes shaders/glsl/texturearray/instancing.vert | 26 - shaders/glsl/texturearray/instancing.vert.spv | Bin 2084 -> 0 bytes shaders/glsl/texturecubemap/reflect.frag | 39 - shaders/glsl/texturecubemap/reflect.frag.spv | Bin 3112 -> 0 bytes shaders/glsl/texturecubemap/reflect.vert | 29 - shaders/glsl/texturecubemap/reflect.vert.spv | Bin 2456 -> 0 bytes shaders/glsl/texturecubemap/skybox.frag | 12 - shaders/glsl/texturecubemap/skybox.frag.spv | Bin 568 -> 0 bytes shaders/glsl/texturecubemap/skybox.vert | 21 - shaders/glsl/texturecubemap/skybox.vert.spv | Bin 2228 -> 0 bytes shaders/glsl/texturecubemaparray/reflect.frag | 39 - .../glsl/texturecubemaparray/reflect.frag.spv | Bin 3348 -> 0 bytes shaders/glsl/texturecubemaparray/reflect.vert | 30 - .../glsl/texturecubemaparray/reflect.vert.spv | Bin 2508 -> 0 bytes shaders/glsl/texturecubemaparray/skybox.frag | 21 - .../glsl/texturecubemaparray/skybox.frag.spv | Bin 1312 -> 0 bytes shaders/glsl/texturecubemaparray/skybox.vert | 23 - .../glsl/texturecubemaparray/skybox.vert.spv | Bin 2408 -> 0 bytes shaders/glsl/texturemipmapgen/texture.frag | 35 - .../glsl/texturemipmapgen/texture.frag.spv | Bin 2776 -> 0 bytes shaders/glsl/texturemipmapgen/texture.vert | 36 - .../glsl/texturemipmapgen/texture.vert.spv | Bin 3120 -> 0 bytes .../sparseresidency.frag | 39 - .../sparseresidency.frag.spv | Bin 1224 -> 0 bytes .../sparseresidency.vert | 23 - .../sparseresidency.vert.spv | Bin 1732 -> 0 bytes shaders/glsl/triangle/triangle.frag | 10 - shaders/glsl/triangle/triangle.frag.spv | Bin 500 -> 0 bytes shaders/glsl/triangle/triangle.vert | 25 - shaders/glsl/triangle/triangle.vert.spv | Bin 1372 -> 0 bytes shaders/glsl/variablerateshading/scene.frag | 85 - .../glsl/variablerateshading/scene.frag.spv | Bin 6336 -> 0 bytes shaders/glsl/variablerateshading/scene.vert | 38 - .../glsl/variablerateshading/scene.vert.spv | Bin 3152 -> 0 bytes shaders/glsl/vertexattributes/scene.frag | 43 - shaders/glsl/vertexattributes/scene.frag.spv | Bin 3624 -> 0 bytes shaders/glsl/vertexattributes/scene.vert | 39 - shaders/glsl/vertexattributes/scene.vert.spv | Bin 3156 -> 0 bytes shaders/glsl/viewportarray/multiview.geom | 45 - shaders/glsl/viewportarray/multiview.geom.spv | Bin 3740 -> 0 bytes shaders/glsl/viewportarray/scene.frag | 20 - shaders/glsl/viewportarray/scene.frag.spv | Bin 1644 -> 0 bytes shaders/glsl/viewportarray/scene.vert | 15 - shaders/glsl/viewportarray/scene.vert.spv | Bin 1116 -> 0 bytes shaders/glsl/vulkanscene/logo.frag | 22 - shaders/glsl/vulkanscene/logo.frag.spv | Bin 1636 -> 0 bytes shaders/glsl/vulkanscene/logo.vert | 34 - shaders/glsl/vulkanscene/logo.vert.spv | Bin 2908 -> 0 bytes shaders/glsl/vulkanscene/mesh.frag | 44 - shaders/glsl/vulkanscene/mesh.frag.spv | Bin 3380 -> 0 bytes shaders/glsl/vulkanscene/mesh.vert | 34 - shaders/glsl/vulkanscene/mesh.vert.spv | Bin 3004 -> 0 bytes shaders/glsl/vulkanscene/skybox.frag | 12 - shaders/glsl/vulkanscene/skybox.frag.spv | Bin 568 -> 0 bytes shaders/glsl/vulkanscene/skybox.vert | 21 - shaders/glsl/vulkanscene/skybox.vert.spv | Bin 2228 -> 0 bytes shaders/hlsl/README.md | 10 - shaders/hlsl/base/textoverlay.frag | 10 - shaders/hlsl/base/textoverlay.frag.spv | Bin 1148 -> 0 bytes shaders/hlsl/base/textoverlay.vert | 21 - shaders/hlsl/base/textoverlay.vert.spv | Bin 1228 -> 0 bytes shaders/hlsl/base/uioverlay.frag | 15 - shaders/hlsl/base/uioverlay.frag.spv | Bin 1248 -> 0 bytes shaders/hlsl/base/uioverlay.vert | 33 - shaders/hlsl/base/uioverlay.vert.spv | Bin 1884 -> 0 bytes shaders/hlsl/bloom/colorpass.frag | 15 - shaders/hlsl/bloom/colorpass.frag.spv | Bin 1188 -> 0 bytes shaders/hlsl/bloom/colorpass.vert | 31 - shaders/hlsl/bloom/colorpass.vert.spv | Bin 2072 -> 0 bytes shaders/hlsl/bloom/gaussblur.frag | 43 - shaders/hlsl/bloom/gaussblur.frag.spv | Bin 4332 -> 0 bytes shaders/hlsl/bloom/gaussblur.vert | 15 - shaders/hlsl/bloom/gaussblur.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/bloom/phongpass.frag | 32 - shaders/hlsl/bloom/phongpass.frag.spv | Bin 2908 -> 0 bytes shaders/hlsl/bloom/phongpass.vert | 42 - shaders/hlsl/bloom/phongpass.vert.spv | Bin 3588 -> 0 bytes shaders/hlsl/bloom/skybox.frag | 9 - shaders/hlsl/bloom/skybox.frag.spv | Bin 928 -> 0 bytes shaders/hlsl/bloom/skybox.vert | 22 - shaders/hlsl/bloom/skybox.vert.spv | Bin 1584 -> 0 bytes shaders/hlsl/compileshaders.py | 90 - shaders/hlsl/computecloth/cloth.comp | 147 -- shaders/hlsl/computecloth/cloth.comp.spv | Bin 19800 -> 0 bytes shaders/hlsl/computecloth/cloth.frag | 24 - shaders/hlsl/computecloth/cloth.frag.spv | Bin 2524 -> 0 bytes shaders/hlsl/computecloth/cloth.vert | 43 - shaders/hlsl/computecloth/cloth.vert.spv | Bin 3096 -> 0 bytes shaders/hlsl/computecloth/sphere.frag | 20 - shaders/hlsl/computecloth/sphere.frag.spv | Bin 1888 -> 0 bytes shaders/hlsl/computecloth/sphere.vert | 41 - shaders/hlsl/computecloth/sphere.vert.spv | Bin 2728 -> 0 bytes shaders/hlsl/computecullandlod/cull.comp | 115 - shaders/hlsl/computecullandlod/cull.comp.spv | Bin 5912 -> 0 bytes .../hlsl/computecullandlod/indirectdraw.frag | 18 - .../computecullandlod/indirectdraw.frag.spv | Bin 1628 -> 0 bytes .../hlsl/computecullandlod/indirectdraw.vert | 46 - .../computecullandlod/indirectdraw.vert.spv | Bin 1864 -> 0 bytes shaders/hlsl/computeheadless/headless.comp | 28 - .../hlsl/computeheadless/headless.comp.spv | Bin 2248 -> 0 bytes shaders/hlsl/computenbody/particle.frag | 21 - shaders/hlsl/computenbody/particle.frag.spv | Bin 2292 -> 0 bytes shaders/hlsl/computenbody/particle.vert | 41 - shaders/hlsl/computenbody/particle.vert.spv | Bin 3484 -> 0 bytes .../hlsl/computenbody/particle_calculate.comp | 75 - .../computenbody/particle_calculate.comp.spv | Bin 3408 -> 0 bytes .../hlsl/computenbody/particle_integrate.comp | 28 - .../computenbody/particle_integrate.comp.spv | Bin 1828 -> 0 bytes shaders/hlsl/computeparticles/particle.comp | 74 - .../hlsl/computeparticles/particle.comp.spv | Bin 5332 -> 0 bytes shaders/hlsl/computeparticles/particle.frag | 22 - .../hlsl/computeparticles/particle.frag.spv | Bin 2396 -> 0 bytes shaders/hlsl/computeparticles/particle.vert | 35 - .../hlsl/computeparticles/particle.vert.spv | Bin 2476 -> 0 bytes .../hlsl/computeraytracing/raytracing.comp | 256 --- .../computeraytracing/raytracing.comp.spv | Bin 14652 -> 0 bytes shaders/hlsl/computeraytracing/texture.frag | 9 - .../hlsl/computeraytracing/texture.frag.spv | Bin 1108 -> 0 bytes shaders/hlsl/computeraytracing/texture.vert | 15 - .../hlsl/computeraytracing/texture.vert.spv | Bin 1328 -> 0 bytes shaders/hlsl/computeshader/edgedetect.comp | 40 - .../hlsl/computeshader/edgedetect.comp.spv | Bin 3496 -> 0 bytes shaders/hlsl/computeshader/emboss.comp | 40 - shaders/hlsl/computeshader/emboss.comp.spv | Bin 3512 -> 0 bytes shaders/hlsl/computeshader/sharpen.comp | 49 - shaders/hlsl/computeshader/sharpen.comp.spv | Bin 4880 -> 0 bytes shaders/hlsl/computeshader/texture.frag | 9 - shaders/hlsl/computeshader/texture.frag.spv | Bin 932 -> 0 bytes shaders/hlsl/computeshader/texture.vert | 29 - shaders/hlsl/computeshader/texture.vert.spv | Bin 1820 -> 0 bytes shaders/hlsl/conditionalrender/model.frag | 21 - shaders/hlsl/conditionalrender/model.frag.spv | Bin 2104 -> 0 bytes shaders/hlsl/conditionalrender/model.vert | 57 - shaders/hlsl/conditionalrender/model.vert.spv | Bin 4280 -> 0 bytes .../hlsl/conservativeraster/fullscreen.frag | 9 - .../conservativeraster/fullscreen.frag.spv | Bin 932 -> 0 bytes .../hlsl/conservativeraster/fullscreen.vert | 15 - .../conservativeraster/fullscreen.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/conservativeraster/triangle.frag | 6 - .../hlsl/conservativeraster/triangle.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/conservativeraster/triangle.vert | 29 - .../hlsl/conservativeraster/triangle.vert.spv | Bin 1772 -> 0 bytes .../conservativeraster/triangleoverlay.frag | 6 - .../triangleoverlay.frag.spv | Bin 392 -> 0 bytes shaders/hlsl/debugprintf/toon.frag | 34 - shaders/hlsl/debugprintf/toon.frag.spv | Bin 1072 -> 0 bytes shaders/hlsl/debugprintf/toon.vert | 42 - shaders/hlsl/debugprintf/toon.vert.spv | Bin 2148 -> 0 bytes shaders/hlsl/debugutils/colorpass.frag | 6 - shaders/hlsl/debugutils/colorpass.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/debugutils/colorpass.vert | 29 - shaders/hlsl/debugutils/colorpass.vert.spv | Bin 1684 -> 0 bytes shaders/hlsl/debugutils/postprocess.frag | 36 - shaders/hlsl/debugutils/postprocess.frag.spv | Bin 4124 -> 0 bytes shaders/hlsl/debugutils/postprocess.vert | 15 - shaders/hlsl/debugutils/postprocess.vert.spv | Bin 1348 -> 0 bytes shaders/hlsl/debugutils/toon.frag | 37 - shaders/hlsl/debugutils/toon.frag.spv | Bin 3336 -> 0 bytes shaders/hlsl/debugutils/toon.vert | 44 - shaders/hlsl/debugutils/toon.vert.spv | Bin 4112 -> 0 bytes shaders/hlsl/deferred/deferred.frag | 95 - shaders/hlsl/deferred/deferred.frag.spv | Bin 3724 -> 0 bytes shaders/hlsl/deferred/deferred.vert | 15 - shaders/hlsl/deferred/deferred.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/deferred/mrt.frag | 39 - shaders/hlsl/deferred/mrt.frag.spv | Bin 1968 -> 0 bytes shaders/hlsl/deferred/mrt.vert | 51 - shaders/hlsl/deferred/mrt.vert.spv | Bin 2188 -> 0 bytes .../hlsl/deferredmultisampling/deferred.frag | 125 - .../deferredmultisampling/deferred.frag.spv | Bin 4816 -> 0 bytes .../hlsl/deferredmultisampling/deferred.vert | 15 - .../deferredmultisampling/deferred.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/deferredmultisampling/mrt.frag | 39 - .../hlsl/deferredmultisampling/mrt.frag.spv | Bin 1968 -> 0 bytes shaders/hlsl/deferredmultisampling/mrt.vert | 51 - .../hlsl/deferredmultisampling/mrt.vert.spv | Bin 2188 -> 0 bytes shaders/hlsl/deferredshadows/deferred.frag | 174 -- .../hlsl/deferredshadows/deferred.frag.spv | Bin 8280 -> 0 bytes shaders/hlsl/deferredshadows/deferred.vert | 15 - .../hlsl/deferredshadows/deferred.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/deferredshadows/mrt.frag | 39 - shaders/hlsl/deferredshadows/mrt.frag.spv | Bin 1968 -> 0 bytes shaders/hlsl/deferredshadows/mrt.vert | 51 - shaders/hlsl/deferredshadows/mrt.vert.spv | Bin 2188 -> 0 bytes shaders/hlsl/deferredshadows/shadow.geom | 39 - shaders/hlsl/deferredshadows/shadow.geom.spv | Bin 1908 -> 0 bytes shaders/hlsl/deferredshadows/shadow.vert | 15 - shaders/hlsl/deferredshadows/shadow.vert.spv | Bin 552 -> 0 bytes .../descriptorindexing.frag | 17 - .../descriptorindexing.frag.spv | Bin 1116 -> 0 bytes .../descriptorindexing.vert | 32 - .../descriptorindexing.vert.spv | Bin 1588 -> 0 bytes shaders/hlsl/descriptorsets/cube.frag | 16 - shaders/hlsl/descriptorsets/cube.frag.spv | Bin 1496 -> 0 bytes shaders/hlsl/descriptorsets/cube.vert | 35 - shaders/hlsl/descriptorsets/cube.vert.spv | Bin 2632 -> 0 bytes shaders/hlsl/displacement/base.frag | 27 - shaders/hlsl/displacement/base.frag.spv | Bin 1268 -> 0 bytes shaders/hlsl/displacement/base.vert | 24 - shaders/hlsl/displacement/base.vert.spv | Bin 848 -> 0 bytes shaders/hlsl/displacement/displacement.tesc | 59 - .../hlsl/displacement/displacement.tesc.spv | Bin 2872 -> 0 bytes shaders/hlsl/displacement/displacement.tese | 56 - .../hlsl/displacement/displacement.tese.spv | Bin 3236 -> 0 bytes shaders/hlsl/distancefieldfonts/bitmap.frag | 9 - .../hlsl/distancefieldfonts/bitmap.frag.spv | Bin 968 -> 0 bytes shaders/hlsl/distancefieldfonts/bitmap.vert | 29 - .../hlsl/distancefieldfonts/bitmap.vert.spv | Bin 1820 -> 0 bytes shaders/hlsl/distancefieldfonts/sdf.frag | 33 - shaders/hlsl/distancefieldfonts/sdf.frag.spv | Bin 2152 -> 0 bytes shaders/hlsl/distancefieldfonts/sdf.vert | 33 - shaders/hlsl/distancefieldfonts/sdf.vert.spv | Bin 1344 -> 0 bytes shaders/hlsl/dynamicuniformbuffer/base.frag | 6 - .../hlsl/dynamicuniformbuffer/base.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/dynamicuniformbuffer/base.vert | 36 - .../hlsl/dynamicuniformbuffer/base.vert.spv | Bin 2448 -> 0 bytes shaders/hlsl/gears/gears.frag | 22 - shaders/hlsl/gears/gears.frag.spv | Bin 2156 -> 0 bytes shaders/hlsl/gears/gears.vert | 42 - shaders/hlsl/gears/gears.vert.spv | Bin 2312 -> 0 bytes shaders/hlsl/geometryshader/base.frag | 6 - shaders/hlsl/geometryshader/base.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/geometryshader/base.vert | 21 - shaders/hlsl/geometryshader/base.vert.spv | Bin 1260 -> 0 bytes shaders/hlsl/geometryshader/mesh.frag | 21 - shaders/hlsl/geometryshader/mesh.frag.spv | Bin 2104 -> 0 bytes shaders/hlsl/geometryshader/mesh.vert | 40 - shaders/hlsl/geometryshader/mesh.vert.spv | Bin 3248 -> 0 bytes shaders/hlsl/geometryshader/normaldebug.geom | 43 - .../hlsl/geometryshader/normaldebug.geom.spv | Bin 2244 -> 0 bytes shaders/hlsl/gltfloading/mesh.frag | 31 - shaders/hlsl/gltfloading/mesh.frag.spv | Bin 1752 -> 0 bytes shaders/hlsl/gltfloading/mesh.vert | 49 - shaders/hlsl/gltfloading/mesh.vert.spv | Bin 2516 -> 0 bytes shaders/hlsl/gltfscenerendering/scene.frag | 44 - .../hlsl/gltfscenerendering/scene.frag.spv | Bin 2680 -> 0 bytes shaders/hlsl/gltfscenerendering/scene.vert | 54 - .../hlsl/gltfscenerendering/scene.vert.spv | Bin 2688 -> 0 bytes shaders/hlsl/hdr/bloom.frag | 62 - shaders/hlsl/hdr/bloom.frag.spv | Bin 2976 -> 0 bytes shaders/hlsl/hdr/bloom.vert | 15 - shaders/hlsl/hdr/bloom.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/hdr/composition.frag | 11 - shaders/hlsl/hdr/composition.frag.spv | Bin 1076 -> 0 bytes shaders/hlsl/hdr/composition.vert | 15 - shaders/hlsl/hdr/composition.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/hdr/gbuffer.frag | 104 - shaders/hlsl/hdr/gbuffer.frag.spv | Bin 4472 -> 0 bytes shaders/hlsl/hdr/gbuffer.vert | 51 - shaders/hlsl/hdr/gbuffer.vert.spv | Bin 2768 -> 0 bytes shaders/hlsl/imgui/scene.frag | 20 - shaders/hlsl/imgui/scene.frag.spv | Bin 1972 -> 0 bytes shaders/hlsl/imgui/scene.vert | 41 - shaders/hlsl/imgui/scene.vert.spv | Bin 3744 -> 0 bytes shaders/hlsl/imgui/ui.frag | 15 - shaders/hlsl/imgui/ui.frag.spv | Bin 1248 -> 0 bytes shaders/hlsl/imgui/ui.vert | 33 - shaders/hlsl/imgui/ui.vert.spv | Bin 1884 -> 0 bytes shaders/hlsl/indirectdraw/ground.frag | 15 - shaders/hlsl/indirectdraw/ground.frag.spv | Bin 876 -> 0 bytes shaders/hlsl/indirectdraw/ground.vert | 31 - shaders/hlsl/indirectdraw/ground.vert.spv | Bin 1372 -> 0 bytes shaders/hlsl/indirectdraw/indirectdraw.frag | 29 - .../hlsl/indirectdraw/indirectdraw.frag.spv | Bin 1556 -> 0 bytes shaders/hlsl/indirectdraw/indirectdraw.vert | 81 - .../hlsl/indirectdraw/indirectdraw.vert.spv | Bin 3512 -> 0 bytes shaders/hlsl/indirectdraw/skysphere.frag | 11 - shaders/hlsl/indirectdraw/skysphere.frag.spv | Bin 764 -> 0 bytes shaders/hlsl/indirectdraw/skysphere.vert | 30 - shaders/hlsl/indirectdraw/skysphere.vert.spv | Bin 1428 -> 0 bytes shaders/hlsl/inlineuniformblocks/pbr.frag | 119 - shaders/hlsl/inlineuniformblocks/pbr.frag.spv | Bin 6912 -> 0 bytes shaders/hlsl/inlineuniformblocks/pbr.vert | 38 - shaders/hlsl/inlineuniformblocks/pbr.vert.spv | Bin 2932 -> 0 bytes .../hlsl/inputattachments/attachmentread.frag | 35 - .../inputattachments/attachmentread.frag.spv | Bin 2908 -> 0 bytes .../hlsl/inputattachments/attachmentread.vert | 6 - .../inputattachments/attachmentread.vert.spv | Bin 876 -> 0 bytes .../inputattachments/attachmentwrite.frag | 24 - .../inputattachments/attachmentwrite.frag.spv | Bin 1904 -> 0 bytes .../inputattachments/attachmentwrite.vert | 36 - .../inputattachments/attachmentwrite.vert.spv | Bin 2752 -> 0 bytes shaders/hlsl/instancing/instancing.frag | 25 - shaders/hlsl/instancing/instancing.frag.spv | Bin 2996 -> 0 bytes shaders/hlsl/instancing/instancing.vert | 89 - shaders/hlsl/instancing/instancing.vert.spv | Bin 7736 -> 0 bytes shaders/hlsl/instancing/planet.frag | 25 - shaders/hlsl/instancing/planet.frag.spv | Bin 2892 -> 0 bytes shaders/hlsl/instancing/planet.vert | 43 - shaders/hlsl/instancing/planet.vert.spv | Bin 2120 -> 0 bytes shaders/hlsl/instancing/starfield.frag | 30 - shaders/hlsl/instancing/starfield.frag.spv | Bin 2352 -> 0 bytes shaders/hlsl/instancing/starfield.vert | 15 - shaders/hlsl/instancing/starfield.vert.spv | Bin 1412 -> 0 bytes shaders/hlsl/mesh/mesh.frag | 26 - shaders/hlsl/mesh/mesh.frag.spv | Bin 2784 -> 0 bytes shaders/hlsl/mesh/mesh.vert | 44 - shaders/hlsl/mesh/mesh.vert.spv | Bin 3936 -> 0 bytes shaders/hlsl/meshshader/meshshader.frag | 15 - shaders/hlsl/meshshader/meshshader.frag.spv | Bin 368 -> 0 bytes shaders/hlsl/meshshader/meshshader.mesh | 50 - shaders/hlsl/meshshader/meshshader.mesh.spv | Bin 2192 -> 0 bytes shaders/hlsl/meshshader/meshshader.task | 19 - shaders/hlsl/meshshader/meshshader.task.spv | Bin 448 -> 0 bytes shaders/hlsl/multisampling/mesh.frag | 26 - shaders/hlsl/multisampling/mesh.frag.spv | Bin 1792 -> 0 bytes shaders/hlsl/multisampling/mesh.vert | 43 - shaders/hlsl/multisampling/mesh.vert.spv | Bin 3928 -> 0 bytes shaders/hlsl/multithreading/phong.frag | 20 - shaders/hlsl/multithreading/phong.frag.spv | Bin 1956 -> 0 bytes shaders/hlsl/multithreading/phong.vert | 49 - shaders/hlsl/multithreading/phong.vert.spv | Bin 3384 -> 0 bytes shaders/hlsl/multithreading/starsphere.frag | 35 - .../hlsl/multithreading/starsphere.frag.spv | Bin 1544 -> 0 bytes shaders/hlsl/multithreading/starsphere.vert | 21 - .../hlsl/multithreading/starsphere.vert.spv | Bin 1288 -> 0 bytes shaders/hlsl/multiview/multiview.frag | 21 - shaders/hlsl/multiview/multiview.frag.spv | Bin 2104 -> 0 bytes shaders/hlsl/multiview/multiview.vert | 43 - shaders/hlsl/multiview/multiview.vert.spv | Bin 3504 -> 0 bytes shaders/hlsl/multiview/viewdisplay.frag | 25 - shaders/hlsl/multiview/viewdisplay.frag.spv | Bin 2356 -> 0 bytes shaders/hlsl/multiview/viewdisplay.vert | 15 - shaders/hlsl/multiview/viewdisplay.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/negativeviewportheight/quad.frag | 9 - .../hlsl/negativeviewportheight/quad.frag.spv | Bin 932 -> 0 bytes shaders/hlsl/negativeviewportheight/quad.vert | 21 - .../hlsl/negativeviewportheight/quad.vert.spv | Bin 1280 -> 0 bytes shaders/hlsl/occlusionquery/mesh.frag | 28 - shaders/hlsl/occlusionquery/mesh.frag.spv | Bin 2508 -> 0 bytes shaders/hlsl/occlusionquery/mesh.vert | 44 - shaders/hlsl/occlusionquery/mesh.vert.spv | Bin 3588 -> 0 bytes shaders/hlsl/occlusionquery/occluder.frag | 6 - shaders/hlsl/occlusionquery/occluder.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/occlusionquery/occluder.vert | 31 - shaders/hlsl/occlusionquery/occluder.vert.spv | Bin 1944 -> 0 bytes shaders/hlsl/occlusionquery/simple.frag | 6 - shaders/hlsl/occlusionquery/simple.frag.spv | Bin 560 -> 0 bytes shaders/hlsl/occlusionquery/simple.vert | 23 - shaders/hlsl/occlusionquery/simple.vert.spv | Bin 1556 -> 0 bytes shaders/hlsl/offscreen/mirror.frag | 41 - shaders/hlsl/offscreen/mirror.frag.spv | Bin 1928 -> 0 bytes shaders/hlsl/offscreen/mirror.vert | 29 - shaders/hlsl/offscreen/mirror.vert.spv | Bin 1224 -> 0 bytes shaders/hlsl/offscreen/phong.frag | 26 - shaders/hlsl/offscreen/phong.frag.spv | Bin 2348 -> 0 bytes shaders/hlsl/offscreen/phong.vert | 43 - shaders/hlsl/offscreen/phong.vert.spv | Bin 2028 -> 0 bytes shaders/hlsl/offscreen/quad.frag | 9 - shaders/hlsl/offscreen/quad.frag.spv | Bin 932 -> 0 bytes shaders/hlsl/offscreen/quad.vert | 15 - shaders/hlsl/offscreen/quad.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/oit/color.frag | 64 - shaders/hlsl/oit/color.frag.spv | Bin 2748 -> 0 bytes shaders/hlsl/oit/color.vert | 14 - shaders/hlsl/oit/color.vert.spv | Bin 656 -> 0 bytes shaders/hlsl/oit/geometry.frag | 52 - shaders/hlsl/oit/geometry.frag.spv | Bin 2012 -> 0 bytes shaders/hlsl/oit/geometry.vert | 32 - shaders/hlsl/oit/geometry.vert.spv | Bin 1224 -> 0 bytes shaders/hlsl/parallaxmapping/parallax.frag | 110 - .../hlsl/parallaxmapping/parallax.frag.spv | Bin 5468 -> 0 bytes shaders/hlsl/parallaxmapping/parallax.vert | 46 - .../hlsl/parallaxmapping/parallax.vert.spv | Bin 2344 -> 0 bytes shaders/hlsl/particlesystem/normalmap.frag | 44 - .../hlsl/particlesystem/normalmap.frag.spv | Bin 3688 -> 0 bytes shaders/hlsl/particlesystem/normalmap.vert | 61 - .../hlsl/particlesystem/normalmap.vert.spv | Bin 2876 -> 0 bytes shaders/hlsl/particlesystem/particle.frag | 52 - shaders/hlsl/particlesystem/particle.frag.spv | Bin 4060 -> 0 bytes shaders/hlsl/particlesystem/particle.vert | 54 - shaders/hlsl/particlesystem/particle.vert.spv | Bin 4732 -> 0 bytes shaders/hlsl/pbrbasic/pbr.frag | 133 -- shaders/hlsl/pbrbasic/pbr.frag.spv | Bin 3976 -> 0 bytes shaders/hlsl/pbrbasic/pbr.vert | 39 - shaders/hlsl/pbrbasic/pbr.vert.spv | Bin 2844 -> 0 bytes shaders/hlsl/pbribl/filtercube.vert | 25 - shaders/hlsl/pbribl/filtercube.vert.spv | Bin 1404 -> 0 bytes shaders/hlsl/pbribl/genbrdflut.frag | 88 - shaders/hlsl/pbribl/genbrdflut.frag.spv | Bin 3492 -> 0 bytes shaders/hlsl/pbribl/genbrdflut.vert | 15 - shaders/hlsl/pbribl/genbrdflut.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/pbribl/irradiancecube.frag | 35 - shaders/hlsl/pbribl/irradiancecube.frag.spv | Bin 3176 -> 0 bytes shaders/hlsl/pbribl/pbribl.frag | 170 -- shaders/hlsl/pbribl/pbribl.frag.spv | Bin 12932 -> 0 bytes shaders/hlsl/pbribl/pbribl.vert | 43 - shaders/hlsl/pbribl/pbribl.vert.spv | Bin 3388 -> 0 bytes shaders/hlsl/pbribl/prefilterenvmap.frag | 106 - shaders/hlsl/pbribl/prefilterenvmap.frag.spv | Bin 8564 -> 0 bytes shaders/hlsl/pbribl/skybox.frag | 37 - shaders/hlsl/pbribl/skybox.frag.spv | Bin 3964 -> 0 bytes shaders/hlsl/pbribl/skybox.vert | 30 - shaders/hlsl/pbribl/skybox.vert.spv | Bin 1908 -> 0 bytes shaders/hlsl/pbrtexture/filtercube.vert | 25 - shaders/hlsl/pbrtexture/filtercube.vert.spv | Bin 1404 -> 0 bytes shaders/hlsl/pbrtexture/genbrdflut.frag | 88 - shaders/hlsl/pbrtexture/genbrdflut.frag.spv | Bin 3492 -> 0 bytes shaders/hlsl/pbrtexture/genbrdflut.vert | 15 - shaders/hlsl/pbrtexture/genbrdflut.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/pbrtexture/irradiancecube.frag | 35 - .../hlsl/pbrtexture/irradiancecube.frag.spv | Bin 3176 -> 0 bytes shaders/hlsl/pbrtexture/pbrtexture.frag | 184 -- shaders/hlsl/pbrtexture/pbrtexture.frag.spv | Bin 7876 -> 0 bytes shaders/hlsl/pbrtexture/pbrtexture.vert | 40 - shaders/hlsl/pbrtexture/pbrtexture.vert.spv | Bin 2248 -> 0 bytes shaders/hlsl/pbrtexture/prefilterenvmap.frag | 106 - .../hlsl/pbrtexture/prefilterenvmap.frag.spv | Bin 8564 -> 0 bytes shaders/hlsl/pbrtexture/skybox.frag | 37 - shaders/hlsl/pbrtexture/skybox.frag.spv | Bin 3964 -> 0 bytes shaders/hlsl/pbrtexture/skybox.vert | 30 - shaders/hlsl/pbrtexture/skybox.vert.spv | Bin 1908 -> 0 bytes shaders/hlsl/pipelines/phong.frag | 28 - shaders/hlsl/pipelines/phong.frag.spv | Bin 1364 -> 0 bytes shaders/hlsl/pipelines/phong.vert | 41 - shaders/hlsl/pipelines/phong.vert.spv | Bin 1912 -> 0 bytes shaders/hlsl/pipelines/toon.frag | 36 - shaders/hlsl/pipelines/toon.frag.spv | Bin 1136 -> 0 bytes shaders/hlsl/pipelines/toon.vert | 41 - shaders/hlsl/pipelines/toon.vert.spv | Bin 1912 -> 0 bytes shaders/hlsl/pipelines/wireframe.frag | 6 - shaders/hlsl/pipelines/wireframe.frag.spv | Bin 524 -> 0 bytes shaders/hlsl/pipelines/wireframe.vert | 29 - shaders/hlsl/pipelines/wireframe.vert.spv | Bin 1068 -> 0 bytes shaders/hlsl/pipelinestatistics/scene.frag | 20 - .../hlsl/pipelinestatistics/scene.frag.spv | Bin 1956 -> 0 bytes shaders/hlsl/pipelinestatistics/scene.tesc | 52 - .../hlsl/pipelinestatistics/scene.tesc.spv | Bin 4108 -> 0 bytes shaders/hlsl/pipelinestatistics/scene.tese | 39 - .../hlsl/pipelinestatistics/scene.tese.spv | Bin 4420 -> 0 bytes shaders/hlsl/pipelinestatistics/scene.vert | 48 - .../hlsl/pipelinestatistics/scene.vert.spv | Bin 3904 -> 0 bytes shaders/hlsl/pushconstants/pushconstants.frag | 11 - .../hlsl/pushconstants/pushconstants.frag.spv | Bin 488 -> 0 bytes shaders/hlsl/pushconstants/pushconstants.vert | 41 - .../hlsl/pushconstants/pushconstants.vert.spv | Bin 1888 -> 0 bytes shaders/hlsl/pushdescriptors/cube.frag | 16 - shaders/hlsl/pushdescriptors/cube.frag.spv | Bin 1496 -> 0 bytes shaders/hlsl/pushdescriptors/cube.vert | 38 - shaders/hlsl/pushdescriptors/cube.vert.spv | Bin 2848 -> 0 bytes shaders/hlsl/radialblur/colorpass.frag | 23 - shaders/hlsl/radialblur/colorpass.frag.spv | Bin 2028 -> 0 bytes shaders/hlsl/radialblur/colorpass.vert | 32 - shaders/hlsl/radialblur/colorpass.vert.spv | Bin 2140 -> 0 bytes shaders/hlsl/radialblur/phongpass.frag | 35 - shaders/hlsl/radialblur/phongpass.frag.spv | Bin 3496 -> 0 bytes shaders/hlsl/radialblur/phongpass.vert | 40 - shaders/hlsl/radialblur/phongpass.vert.spv | Bin 3112 -> 0 bytes shaders/hlsl/radialblur/radialblur.frag | 35 - shaders/hlsl/radialblur/radialblur.frag.spv | Bin 2904 -> 0 bytes shaders/hlsl/radialblur/radialblur.vert | 15 - shaders/hlsl/radialblur/radialblur.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/raytracingbasic/closesthit.rchit | 18 - .../hlsl/raytracingbasic/closesthit.rchit.spv | Bin 600 -> 0 bytes shaders/hlsl/raytracingbasic/miss.rmiss | 12 - shaders/hlsl/raytracingbasic/miss.rmiss.spv | Bin 776 -> 0 bytes shaders/hlsl/raytracingbasic/raygen.rgen | 39 - shaders/hlsl/raytracingbasic/raygen.rgen.spv | Bin 3468 -> 0 bytes .../hlsl/raytracingcallable/callable1.rcall | 15 - .../raytracingcallable/callable1.rcall.spv | Bin 660 -> 0 bytes .../hlsl/raytracingcallable/callable2.rcall | 12 - .../raytracingcallable/callable2.rcall.spv | Bin 388 -> 0 bytes .../hlsl/raytracingcallable/callable3.rcall | 15 - .../raytracingcallable/callable3.rcall.spv | Bin 564 -> 0 bytes .../hlsl/raytracingcallable/closesthit.rchit | 26 - .../raytracingcallable/closesthit.rchit.spv | Bin 624 -> 0 bytes shaders/hlsl/raytracingcallable/miss.rmiss | 12 - .../hlsl/raytracingcallable/miss.rmiss.spv | Bin 380 -> 0 bytes shaders/hlsl/raytracingcallable/raygen.rgen | 39 - .../hlsl/raytracingcallable/raygen.rgen.spv | Bin 2356 -> 0 bytes .../raytracingpositionfetch/closesthit.rchit | 56 - .../closesthit.rchit.spv | Bin 2612 -> 0 bytes .../hlsl/raytracingpositionfetch/miss.rmiss | 16 - .../raytracingpositionfetch/miss.rmiss.spv | Bin 380 -> 0 bytes .../hlsl/raytracingpositionfetch/raygen.rgen | 43 - .../raytracingpositionfetch/raygen.rgen.spv | Bin 2388 -> 0 bytes .../raytracingreflections/closesthit.rchit | 80 - .../closesthit.rchit.spv | Bin 3756 -> 0 bytes shaders/hlsl/raytracingreflections/miss.rmiss | 25 - .../hlsl/raytracingreflections/miss.rmiss.spv | Bin 1624 -> 0 bytes .../hlsl/raytracingreflections/raygen.rgen | 64 - .../raytracingreflections/raygen.rgen.spv | Bin 4784 -> 0 bytes .../hlsl/raytracingsbtdata/closesthit.rchit | 27 - .../raytracingsbtdata/closesthit.rchit.spv | Bin 728 -> 0 bytes shaders/hlsl/raytracingsbtdata/miss.rmiss | 22 - shaders/hlsl/raytracingsbtdata/miss.rmiss.spv | Bin 728 -> 0 bytes shaders/hlsl/raytracingsbtdata/raygen.rgen | 65 - .../hlsl/raytracingsbtdata/raygen.rgen.spv | Bin 3324 -> 0 bytes .../hlsl/raytracingshadows/closesthit.rchit | 86 - .../raytracingshadows/closesthit.rchit.spv | Bin 4084 -> 0 bytes shaders/hlsl/raytracingshadows/miss.rmiss | 13 - shaders/hlsl/raytracingshadows/miss.rmiss.spv | Bin 460 -> 0 bytes shaders/hlsl/raytracingshadows/raygen.rgen | 40 - .../hlsl/raytracingshadows/raygen.rgen.spv | Bin 3516 -> 0 bytes shaders/hlsl/raytracingshadows/shadow.rmiss | 13 - .../hlsl/raytracingshadows/shadow.rmiss.spv | Bin 416 -> 0 bytes shaders/hlsl/renderheadless/triangle.frag | 6 - shaders/hlsl/renderheadless/triangle.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/renderheadless/triangle.vert | 26 - shaders/hlsl/renderheadless/triangle.vert.spv | Bin 1508 -> 0 bytes shaders/hlsl/screenshot/mesh.frag | 21 - shaders/hlsl/screenshot/mesh.frag.spv | Bin 2104 -> 0 bytes shaders/hlsl/screenshot/mesh.vert | 41 - shaders/hlsl/screenshot/mesh.vert.spv | Bin 3392 -> 0 bytes shaders/hlsl/shadowmapping/offscreen.frag | 6 - shaders/hlsl/shadowmapping/offscreen.frag.spv | Bin 336 -> 0 bytes shaders/hlsl/shadowmapping/offscreen.vert | 13 - shaders/hlsl/shadowmapping/offscreen.vert.spv | Bin 836 -> 0 bytes shaders/hlsl/shadowmapping/quad.frag | 31 - shaders/hlsl/shadowmapping/quad.frag.spv | Bin 1808 -> 0 bytes shaders/hlsl/shadowmapping/quad.vert | 15 - shaders/hlsl/shadowmapping/quad.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/shadowmapping/scene.frag | 68 - shaders/hlsl/shadowmapping/scene.frag.spv | Bin 3112 -> 0 bytes shaders/hlsl/shadowmapping/scene.vert | 56 - shaders/hlsl/shadowmapping/scene.vert.spv | Bin 2612 -> 0 bytes .../shadowmappingcascade/debugshadowmap.frag | 16 - .../debugshadowmap.frag.spv | Bin 1068 -> 0 bytes .../shadowmappingcascade/debugshadowmap.vert | 23 - .../debugshadowmap.vert.spv | Bin 1148 -> 0 bytes .../hlsl/shadowmappingcascade/depthpass.frag | 12 - .../shadowmappingcascade/depthpass.frag.spv | Bin 792 -> 0 bytes .../hlsl/shadowmappingcascade/depthpass.vert | 36 - .../shadowmappingcascade/depthpass.vert.spv | Bin 1500 -> 0 bytes shaders/hlsl/shadowmappingcascade/scene.frag | 136 -- .../hlsl/shadowmappingcascade/scene.frag.spv | Bin 6780 -> 0 bytes shaders/hlsl/shadowmappingcascade/scene.vert | 47 - .../hlsl/shadowmappingcascade/scene.vert.spv | Bin 2200 -> 0 bytes .../shadowmappingomni/cubemapdisplay.frag | 53 - .../shadowmappingomni/cubemapdisplay.frag.spv | Bin 2800 -> 0 bytes .../shadowmappingomni/cubemapdisplay.vert | 25 - .../shadowmappingomni/cubemapdisplay.vert.spv | Bin 748 -> 0 bytes shaders/hlsl/shadowmappingomni/offscreen.frag | 14 - .../hlsl/shadowmappingomni/offscreen.frag.spv | Bin 584 -> 0 bytes shaders/hlsl/shadowmappingomni/offscreen.vert | 34 - .../hlsl/shadowmappingomni/offscreen.vert.spv | Bin 1656 -> 0 bytes shaders/hlsl/shadowmappingomni/scene.frag | 43 - shaders/hlsl/shadowmappingomni/scene.frag.spv | Bin 1744 -> 0 bytes shaders/hlsl/shadowmappingomni/scene.vert | 45 - shaders/hlsl/shadowmappingomni/scene.vert.spv | Bin 2016 -> 0 bytes .../hlsl/specializationconstants/uber.frag | 73 - .../specializationconstants/uber.frag.spv | Bin 6456 -> 0 bytes .../hlsl/specializationconstants/uber.vert | 44 - .../specializationconstants/uber.vert.spv | Bin 3936 -> 0 bytes shaders/hlsl/sphericalenvmapping/sem.frag | 21 - shaders/hlsl/sphericalenvmapping/sem.frag.spv | Bin 2528 -> 0 bytes shaders/hlsl/sphericalenvmapping/sem.vert | 42 - shaders/hlsl/sphericalenvmapping/sem.vert.spv | Bin 2128 -> 0 bytes shaders/hlsl/ssao/blur.frag | 24 - shaders/hlsl/ssao/blur.frag.spv | Bin 1748 -> 0 bytes shaders/hlsl/ssao/composition.frag | 56 - shaders/hlsl/ssao/composition.frag.spv | Bin 4220 -> 0 bytes shaders/hlsl/ssao/fullscreen.vert | 15 - shaders/hlsl/ssao/fullscreen.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/ssao/gbuffer.frag | 46 - shaders/hlsl/ssao/gbuffer.frag.spv | Bin 2592 -> 0 bytes shaders/hlsl/ssao/gbuffer.vert | 45 - shaders/hlsl/ssao/gbuffer.vert.spv | Bin 3312 -> 0 bytes shaders/hlsl/ssao/ssao.frag | 67 - shaders/hlsl/ssao/ssao.frag.spv | Bin 3908 -> 0 bytes shaders/hlsl/stencilbuffer/outline.frag | 6 - shaders/hlsl/stencilbuffer/outline.frag.spv | Bin 392 -> 0 bytes shaders/hlsl/stencilbuffer/outline.vert | 24 - shaders/hlsl/stencilbuffer/outline.vert.spv | Bin 1812 -> 0 bytes shaders/hlsl/stencilbuffer/toon.frag | 32 - shaders/hlsl/stencilbuffer/toon.frag.spv | Bin 2716 -> 0 bytes shaders/hlsl/stencilbuffer/toon.vert | 37 - shaders/hlsl/stencilbuffer/toon.vert.spv | Bin 3272 -> 0 bytes shaders/hlsl/subpasses/composition.frag | 47 - shaders/hlsl/subpasses/composition.frag.spv | Bin 2236 -> 0 bytes shaders/hlsl/subpasses/composition.vert | 15 - shaders/hlsl/subpasses/composition.vert.spv | Bin 1312 -> 0 bytes shaders/hlsl/subpasses/gbuffer.frag | 45 - shaders/hlsl/subpasses/gbuffer.frag.spv | Bin 2828 -> 0 bytes shaders/hlsl/subpasses/gbuffer.vert | 44 - shaders/hlsl/subpasses/gbuffer.vert.spv | Bin 3072 -> 0 bytes shaders/hlsl/subpasses/transparent.frag | 33 - shaders/hlsl/subpasses/transparent.frag.spv | Bin 2420 -> 0 bytes shaders/hlsl/subpasses/transparent.vert | 35 - shaders/hlsl/subpasses/transparent.vert.spv | Bin 2464 -> 0 bytes .../hlsl/terraintessellation/skysphere.frag | 10 - .../terraintessellation/skysphere.frag.spv | Bin 1128 -> 0 bytes .../hlsl/terraintessellation/skysphere.vert | 28 - .../terraintessellation/skysphere.vert.spv | Bin 1092 -> 0 bytes shaders/hlsl/terraintessellation/terrain.frag | 65 - .../hlsl/terraintessellation/terrain.frag.spv | Bin 5416 -> 0 bytes shaders/hlsl/terraintessellation/terrain.tesc | 141 -- .../hlsl/terraintessellation/terrain.tesc.spv | Bin 13508 -> 0 bytes shaders/hlsl/terraintessellation/terrain.tese | 71 - .../hlsl/terraintessellation/terrain.tese.spv | Bin 6732 -> 0 bytes shaders/hlsl/terraintessellation/terrain.vert | 24 - .../hlsl/terraintessellation/terrain.vert.spv | Bin 1600 -> 0 bytes shaders/hlsl/tessellation/base.frag | 20 - shaders/hlsl/tessellation/base.frag.spv | Bin 1312 -> 0 bytes shaders/hlsl/tessellation/base.vert | 24 - shaders/hlsl/tessellation/base.vert.spv | Bin 1600 -> 0 bytes shaders/hlsl/tessellation/passthrough.tesc | 46 - .../hlsl/tessellation/passthrough.tesc.spv | Bin 3340 -> 0 bytes shaders/hlsl/tessellation/passthrough.tese | 45 - .../hlsl/tessellation/passthrough.tese.spv | Bin 2052 -> 0 bytes shaders/hlsl/tessellation/pntriangles.tesc | 131 -- .../hlsl/tessellation/pntriangles.tesc.spv | Bin 6300 -> 0 bytes shaders/hlsl/tessellation/pntriangles.tese | 127 - .../hlsl/tessellation/pntriangles.tese.spv | Bin 4716 -> 0 bytes shaders/hlsl/textoverlay/mesh.frag | 20 - shaders/hlsl/textoverlay/mesh.frag.spv | Bin 1916 -> 0 bytes shaders/hlsl/textoverlay/mesh.vert | 41 - shaders/hlsl/textoverlay/mesh.vert.spv | Bin 3680 -> 0 bytes shaders/hlsl/textoverlay/text.frag | 10 - shaders/hlsl/textoverlay/text.frag.spv | Bin 1032 -> 0 bytes shaders/hlsl/textoverlay/text.vert | 21 - shaders/hlsl/textoverlay/text.vert.spv | Bin 1228 -> 0 bytes shaders/hlsl/texture/texture.frag | 27 - shaders/hlsl/texture/texture.frag.spv | Bin 2724 -> 0 bytes shaders/hlsl/texture/texture.vert | 47 - shaders/hlsl/texture/texture.vert.spv | Bin 4224 -> 0 bytes shaders/hlsl/texture3d/texture3d.frag | 27 - shaders/hlsl/texture3d/texture3d.frag.spv | Bin 2608 -> 0 bytes shaders/hlsl/texture3d/texture3d.vert | 46 - shaders/hlsl/texture3d/texture3d.vert.spv | Bin 4224 -> 0 bytes shaders/hlsl/texturearray/instancing.frag | 9 - shaders/hlsl/texturearray/instancing.frag.spv | Bin 936 -> 0 bytes shaders/hlsl/texturearray/instancing.vert | 37 - shaders/hlsl/texturearray/instancing.vert.spv | Bin 1788 -> 0 bytes shaders/hlsl/texturecubemap/reflect.frag | 44 - shaders/hlsl/texturecubemap/reflect.frag.spv | Bin 2524 -> 0 bytes shaders/hlsl/texturecubemap/reflect.vert | 42 - shaders/hlsl/texturecubemap/reflect.vert.spv | Bin 2032 -> 0 bytes shaders/hlsl/texturecubemap/skybox.frag | 9 - shaders/hlsl/texturecubemap/skybox.frag.spv | Bin 772 -> 0 bytes shaders/hlsl/texturecubemap/skybox.vert | 30 - shaders/hlsl/texturecubemap/skybox.vert.spv | Bin 1296 -> 0 bytes shaders/hlsl/texturecubemaparray/reflect.frag | 44 - .../hlsl/texturecubemaparray/reflect.frag.spv | Bin 2788 -> 0 bytes shaders/hlsl/texturecubemaparray/reflect.vert | 42 - .../hlsl/texturecubemaparray/reflect.vert.spv | Bin 2032 -> 0 bytes shaders/hlsl/texturecubemaparray/skybox.frag | 20 - .../hlsl/texturecubemaparray/skybox.frag.spv | Bin 1608 -> 0 bytes shaders/hlsl/texturecubemaparray/skybox.vert | 32 - .../hlsl/texturecubemaparray/skybox.vert.spv | Bin 1476 -> 0 bytes shaders/hlsl/texturemipmapgen/texture.frag | 38 - .../hlsl/texturemipmapgen/texture.frag.spv | Bin 2520 -> 0 bytes shaders/hlsl/texturemipmapgen/texture.vert | 47 - .../hlsl/texturemipmapgen/texture.vert.spv | Bin 2476 -> 0 bytes .../sparseresidency.frag | 36 - .../sparseresidency.frag.spv | Bin 3024 -> 0 bytes .../sparseresidency.vert | 45 - .../sparseresidency.vert.spv | Bin 3396 -> 0 bytes shaders/hlsl/triangle/triangle.frag | 6 - shaders/hlsl/triangle/triangle.frag.spv | Bin 648 -> 0 bytes shaders/hlsl/triangle/triangle.vert | 30 - shaders/hlsl/triangle/triangle.vert.spv | Bin 1960 -> 0 bytes shaders/hlsl/variablerateshading/scene.frag | 83 - .../hlsl/variablerateshading/scene.frag.spv | Bin 6156 -> 0 bytes shaders/hlsl/variablerateshading/scene.vert | 51 - .../hlsl/variablerateshading/scene.vert.spv | Bin 2636 -> 0 bytes shaders/hlsl/viewportarray/multiview.geom | 56 - shaders/hlsl/viewportarray/multiview.geom.spv | Bin 4496 -> 0 bytes shaders/hlsl/viewportarray/scene.frag | 21 - shaders/hlsl/viewportarray/scene.frag.spv | Bin 2104 -> 0 bytes shaders/hlsl/viewportarray/scene.vert | 24 - shaders/hlsl/viewportarray/scene.vert.spv | Bin 1536 -> 0 bytes shaders/hlsl/vulkanscene/logo.frag | 22 - shaders/hlsl/vulkanscene/logo.frag.spv | Bin 2224 -> 0 bytes shaders/hlsl/vulkanscene/logo.vert | 45 - shaders/hlsl/vulkanscene/logo.vert.spv | Bin 3764 -> 0 bytes shaders/hlsl/vulkanscene/mesh.frag | 48 - shaders/hlsl/vulkanscene/mesh.frag.spv | Bin 4008 -> 0 bytes shaders/hlsl/vulkanscene/mesh.vert | 45 - shaders/hlsl/vulkanscene/mesh.vert.spv | Bin 3864 -> 0 bytes shaders/hlsl/vulkanscene/skybox.frag | 9 - shaders/hlsl/vulkanscene/skybox.frag.spv | Bin 932 -> 0 bytes shaders/hlsl/vulkanscene/skybox.vert | 33 - shaders/hlsl/vulkanscene/skybox.vert.spv | Bin 1420 -> 0 bytes shaders/slang/_rename.py | 86 - shaders/slang/base/uioverlay.frag.spv | Bin 700 -> 0 bytes shaders/slang/base/uioverlay.slang | 43 - shaders/slang/base/uioverlay.vert.spv | Bin 1268 -> 0 bytes shaders/slang/bloom/colorpass.frag.spv | Bin 436 -> 0 bytes shaders/slang/bloom/colorpass.slang | 45 - shaders/slang/bloom/colorpass.vert.spv | Bin 3096 -> 0 bytes shaders/slang/bloom/gaussblur.frag.spv | Bin 3708 -> 0 bytes shaders/slang/bloom/gaussblur.slang | 63 - shaders/slang/bloom/gaussblur.vert.spv | Bin 832 -> 0 bytes shaders/slang/bloom/phongpass.frag.spv | Bin 1752 -> 0 bytes shaders/slang/bloom/phongpass.slang | 70 - shaders/slang/bloom/phongpass.vert.spv | Bin 6240 -> 0 bytes shaders/slang/bloom/skybox.frag.spv | Bin 596 -> 0 bytes shaders/slang/bloom/skybox.slang | 41 - shaders/slang/bloom/skybox.vert.spv | Bin 2864 -> 0 bytes .../slang/bufferdeviceaddress/cube.frag.spv | Bin 756 -> 0 bytes shaders/slang/bufferdeviceaddress/cube.slang | 54 - .../slang/bufferdeviceaddress/cube.vert.spv | Bin 3228 -> 0 bytes shaders/slang/compileshaders.py | 132 -- shaders/slang/computecloth/cloth.comp.spv | Bin 13140 -> 0 bytes shaders/slang/computecloth/cloth.frag.spv | Bin 1392 -> 0 bytes shaders/slang/computecloth/cloth.slang | 190 -- shaders/slang/computecloth/cloth.vert.spv | Bin 2996 -> 0 bytes shaders/slang/computecloth/sphere.frag.spv | Bin 1068 -> 0 bytes shaders/slang/computecloth/sphere.slang | 55 - shaders/slang/computecloth/sphere.vert.spv | Bin 2756 -> 0 bytes shaders/slang/computecullandlod/cull.comp.spv | Bin 5992 -> 0 bytes shaders/slang/computecullandlod/cull.slang | 115 - .../computecullandlod/indirectdraw.frag.spv | Bin 828 -> 0 bytes .../computecullandlod/indirectdraw.slang | 56 - .../computecullandlod/indirectdraw.vert.spv | Bin 3004 -> 0 bytes .../slang/computeheadless/headless.comp.spv | Bin 1556 -> 0 bytes shaders/slang/computeheadless/headless.slang | 34 - shaders/slang/computenbody/particle.frag.spv | Bin 1356 -> 0 bytes shaders/slang/computenbody/particle.slang | 56 - shaders/slang/computenbody/particle.vert.spv | Bin 3792 -> 0 bytes .../computenbody/particle_calculate.comp.spv | Bin 4472 -> 0 bytes .../computenbody/particle_calculate.slang | 77 - .../computenbody/particle_integrate.comp.spv | Bin 1320 -> 0 bytes .../computenbody/particle_integrate.slang | 31 - .../slang/computeparticles/particle.comp.spv | Bin 3864 -> 0 bytes .../slang/computeparticles/particle.frag.spv | Bin 996 -> 0 bytes shaders/slang/computeparticles/particle.slang | 120 - .../slang/computeparticles/particle.vert.spv | Bin 840 -> 0 bytes .../computeraytracing/raytracing.comp.spv | Bin 19032 -> 0 bytes .../slang/computeraytracing/raytracing.slang | 258 --- .../slang/computeraytracing/texture.frag.spv | Bin 692 -> 0 bytes shaders/slang/computeraytracing/texture.slang | 28 - .../slang/computeraytracing/texture.vert.spv | Bin 848 -> 0 bytes .../slang/computeshader/edgedetect.comp.spv | Bin 2964 -> 0 bytes shaders/slang/computeshader/edgedetect.slang | 34 - shaders/slang/computeshader/emboss.comp.spv | Bin 2980 -> 0 bytes shaders/slang/computeshader/emboss.slang | 34 - shaders/slang/computeshader/shared.slang | 20 - shaders/slang/computeshader/sharpen.comp.spv | Bin 4824 -> 0 bytes shaders/slang/computeshader/sharpen.slang | 43 - shaders/slang/computeshader/texture.frag.spv | Bin 596 -> 0 bytes shaders/slang/computeshader/texture.slang | 41 - shaders/slang/computeshader/texture.vert.spv | Bin 2292 -> 0 bytes .../slang/conditionalrender/model.frag.spv | Bin 1196 -> 0 bytes shaders/slang/conditionalrender/model.slang | 66 - .../slang/conditionalrender/model.vert.spv | Bin 8224 -> 0 bytes .../conservativeraster/fullscreen.frag.spv | Bin 596 -> 0 bytes .../slang/conservativeraster/fullscreen.slang | 28 - .../conservativeraster/fullscreen.vert.spv | Bin 832 -> 0 bytes .../conservativeraster/triangle.frag.spv | Bin 436 -> 0 bytes .../slang/conservativeraster/triangle.slang | 38 - .../conservativeraster/triangle.vert.spv | Bin 2232 -> 0 bytes .../triangleoverlay.frag.spv | Bin 340 -> 0 bytes .../conservativeraster/triangleoverlay.slang | 11 - shaders/slang/debugprintf/toon.frag.spv | Bin 1332 -> 0 bytes shaders/slang/debugprintf/toon.slang | 73 - shaders/slang/debugprintf/toon.vert.spv | Bin 5224 -> 0 bytes shaders/slang/debugutils/colorpass.frag.spv | Bin 436 -> 0 bytes shaders/slang/debugutils/colorpass.slang | 41 - shaders/slang/debugutils/colorpass.vert.spv | Bin 2212 -> 0 bytes shaders/slang/debugutils/postprocess.frag.spv | Bin 1960 -> 0 bytes shaders/slang/debugutils/postprocess.slang | 55 - shaders/slang/debugutils/postprocess.vert.spv | Bin 868 -> 0 bytes shaders/slang/debugutils/toon.frag.spv | Bin 1332 -> 0 bytes shaders/slang/debugutils/toon.slang | 71 - shaders/slang/debugutils/toon.vert.spv | Bin 5052 -> 0 bytes shaders/slang/deferred/deferred.frag.spv | Bin 4320 -> 0 bytes shaders/slang/deferred/deferred.slang | 110 - shaders/slang/deferred/deferred.vert.spv | Bin 832 -> 0 bytes shaders/slang/deferred/mrt.frag.spv | Bin 1548 -> 0 bytes shaders/slang/deferred/mrt.slang | 83 - shaders/slang/deferred/mrt.vert.spv | Bin 4752 -> 0 bytes .../deferredmultisampling/deferred.frag.spv | Bin 6864 -> 0 bytes .../deferredmultisampling/deferred.slang | 140 -- .../deferredmultisampling/deferred.vert.spv | Bin 832 -> 0 bytes .../slang/deferredmultisampling/mrt.frag.spv | Bin 1548 -> 0 bytes shaders/slang/deferredmultisampling/mrt.slang | 83 - .../slang/deferredmultisampling/mrt.vert.spv | Bin 4752 -> 0 bytes .../slang/deferredshadows/deferred.frag.spv | Bin 12548 -> 0 bytes shaders/slang/deferredshadows/deferred.slang | 187 -- .../slang/deferredshadows/deferred.vert.spv | Bin 832 -> 0 bytes shaders/slang/deferredshadows/mrt.frag.spv | Bin 1548 -> 0 bytes shaders/slang/deferredshadows/mrt.slang | 83 - shaders/slang/deferredshadows/mrt.vert.spv | Bin 4752 -> 0 bytes shaders/slang/deferredshadows/shadow.geom.spv | Bin 3184 -> 0 bytes shaders/slang/deferredshadows/shadow.slang | 57 - shaders/slang/deferredshadows/shadow.vert.spv | Bin 656 -> 0 bytes shaders/slang/descriptorbuffer/cube.frag.spv | Bin 756 -> 0 bytes shaders/slang/descriptorbuffer/cube.slang | 51 - shaders/slang/descriptorbuffer/cube.vert.spv | Bin 3448 -> 0 bytes .../descriptorindexing.frag.spv | Bin 852 -> 0 bytes .../descriptorindexing.slang | 43 - .../descriptorindexing.vert.spv | Bin 3188 -> 0 bytes shaders/slang/descriptorsets/cube.frag.spv | Bin 756 -> 0 bytes shaders/slang/descriptorsets/cube.slang | 51 - shaders/slang/descriptorsets/cube.vert.spv | Bin 3332 -> 0 bytes shaders/slang/displacement/base.frag.spv | Bin 1048 -> 0 bytes shaders/slang/displacement/base.vert.spv | Bin 804 -> 0 bytes shaders/slang/displacement/displacement.slang | 118 - .../slang/displacement/displacement.tesc.spv | Bin 3124 -> 0 bytes .../slang/displacement/displacement.tese.spv | Bin 4472 -> 0 bytes .../slang/distancefieldfonts/bitmap.frag.spv | Bin 632 -> 0 bytes shaders/slang/distancefieldfonts/bitmap.slang | 40 - .../slang/distancefieldfonts/bitmap.vert.spv | Bin 2292 -> 0 bytes shaders/slang/distancefieldfonts/sdf.frag.spv | Bin 2080 -> 0 bytes shaders/slang/distancefieldfonts/sdf.slang | 56 - shaders/slang/distancefieldfonts/sdf.vert.spv | Bin 2440 -> 0 bytes .../slang/dynamicrendering/texture.frag.spv | Bin 1368 -> 0 bytes shaders/slang/dynamicrendering/texture.slang | 65 - .../slang/dynamicrendering/texture.vert.spv | Bin 4600 -> 0 bytes .../slang/dynamicuniformbuffer/base.frag.spv | Bin 436 -> 0 bytes shaders/slang/dynamicuniformbuffer/base.slang | 47 - .../slang/dynamicuniformbuffer/base.vert.spv | Bin 3068 -> 0 bytes shaders/slang/gears/gears.frag.spv | Bin 1240 -> 0 bytes shaders/slang/gears/gears.slang | 58 - shaders/slang/gears/gears.vert.spv | Bin 4772 -> 0 bytes shaders/slang/geometryshader/base.frag.spv | Bin 436 -> 0 bytes shaders/slang/geometryshader/base.vert.spv | Bin 636 -> 0 bytes shaders/slang/geometryshader/mesh.frag.spv | Bin 1196 -> 0 bytes shaders/slang/geometryshader/mesh.slang | 58 - shaders/slang/geometryshader/mesh.vert.spv | Bin 4040 -> 0 bytes .../slang/geometryshader/normaldebug.geom.spv | Bin 4768 -> 0 bytes .../slang/geometryshader/normaldebug.slang | 68 - shaders/slang/gltfloading/mesh.frag.spv | Bin 1488 -> 0 bytes shaders/slang/gltfloading/mesh.slang | 64 - shaders/slang/gltfloading/mesh.vert.spv | Bin 5808 -> 0 bytes .../slang/gltfscenerendering/scene.frag.spv | Bin 2320 -> 0 bytes shaders/slang/gltfscenerendering/scene.slang | 86 - .../slang/gltfscenerendering/scene.vert.spv | Bin 5648 -> 0 bytes .../slang/gltfskinning/skinnedmodel.frag.spv | Bin 1488 -> 0 bytes shaders/slang/gltfskinning/skinnedmodel.slang | 78 - .../slang/gltfskinning/skinnedmodel.vert.spv | Bin 9552 -> 0 bytes .../graphicspipelinelibrary/shared.vert.spv | Bin 4516 -> 0 bytes .../graphicspipelinelibrary/uber.frag.spv | Bin 2500 -> 0 bytes .../slang/graphicspipelinelibrary/uber.slang | 99 - shaders/slang/hdr/bloom.frag.spv | Bin 2632 -> 0 bytes shaders/slang/hdr/bloom.slang | 80 - shaders/slang/hdr/bloom.vert.spv | Bin 832 -> 0 bytes shaders/slang/hdr/composition.frag.spv | Bin 596 -> 0 bytes shaders/slang/hdr/composition.slang | 28 - shaders/slang/hdr/composition.vert.spv | Bin 832 -> 0 bytes shaders/slang/hdr/gbuffer.frag.spv | Bin 6804 -> 0 bytes shaders/slang/hdr/gbuffer.slang | 135 -- shaders/slang/hdr/gbuffer.vert.spv | Bin 5896 -> 0 bytes shaders/slang/imgui/scene.frag.spv | Bin 1080 -> 0 bytes shaders/slang/imgui/scene.slang | 57 - shaders/slang/imgui/scene.vert.spv | Bin 4844 -> 0 bytes shaders/slang/imgui/ui.frag.spv | Bin 700 -> 0 bytes shaders/slang/imgui/ui.slang | 37 - shaders/slang/imgui/ui.vert.spv | Bin 1140 -> 0 bytes shaders/slang/indirectdraw/ground.frag.spv | Bin 680 -> 0 bytes shaders/slang/indirectdraw/ground.slang | 44 - shaders/slang/indirectdraw/ground.vert.spv | Bin 2332 -> 0 bytes .../slang/indirectdraw/indirectdraw.frag.spv | Bin 1348 -> 0 bytes shaders/slang/indirectdraw/indirectdraw.slang | 104 - .../slang/indirectdraw/indirectdraw.vert.spv | Bin 4812 -> 0 bytes shaders/slang/indirectdraw/skysphere.frag.spv | Bin 776 -> 0 bytes shaders/slang/indirectdraw/skysphere.slang | 42 - shaders/slang/indirectdraw/skysphere.vert.spv | Bin 2248 -> 0 bytes .../slang/inlineuniformblocks/pbr.frag.spv | Bin 3524 -> 0 bytes shaders/slang/inlineuniformblocks/pbr.slang | 137 -- .../slang/inlineuniformblocks/pbr.vert.spv | Bin 4052 -> 0 bytes .../inputattachments/attachmentread.frag.spv | Bin 2012 -> 0 bytes .../inputattachments/attachmentread.slang | 52 - .../inputattachments/attachmentread.vert.spv | Bin 728 -> 0 bytes .../inputattachments/attachmentwrite.frag.spv | Bin 1332 -> 0 bytes .../inputattachments/attachmentwrite.slang | 56 - .../inputattachments/attachmentwrite.vert.spv | Bin 3400 -> 0 bytes shaders/slang/instancing/instancing.frag.spv | Bin 1740 -> 0 bytes shaders/slang/instancing/instancing.slang | 106 - shaders/slang/instancing/instancing.vert.spv | Bin 7056 -> 0 bytes shaders/slang/instancing/planet.frag.spv | Bin 1580 -> 0 bytes shaders/slang/instancing/planet.slang | 62 - shaders/slang/instancing/planet.vert.spv | Bin 4728 -> 0 bytes shaders/slang/instancing/starfield.frag.spv | Bin 1324 -> 0 bytes shaders/slang/instancing/starfield.slang | 50 - shaders/slang/instancing/starfield.vert.spv | Bin 872 -> 0 bytes shaders/slang/meshshader/meshshader.frag.spv | Bin 384 -> 0 bytes shaders/slang/meshshader/meshshader.mesh.spv | Bin 4088 -> 0 bytes shaders/slang/meshshader/meshshader.slang | 71 - shaders/slang/meshshader/meshshader.task.spv | Bin 396 -> 0 bytes shaders/slang/multisampling/mesh.frag.spv | Bin 1520 -> 0 bytes shaders/slang/multisampling/mesh.slang | 63 - shaders/slang/multisampling/mesh.vert.spv | Bin 4076 -> 0 bytes shaders/slang/multithreading/phong.frag.spv | Bin 1096 -> 0 bytes shaders/slang/multithreading/phong.slang | 54 - shaders/slang/multithreading/phong.vert.spv | Bin 4044 -> 0 bytes .../slang/multithreading/starsphere.frag.spv | Bin 1588 -> 0 bytes shaders/slang/multithreading/starsphere.slang | 58 - .../slang/multithreading/starsphere.vert.spv | Bin 1500 -> 0 bytes shaders/slang/multiview/multiview.frag.spv | Bin 1196 -> 0 bytes shaders/slang/multiview/multiview.slang | 60 - shaders/slang/multiview/multiview.vert.spv | Bin 4356 -> 0 bytes shaders/slang/multiview/viewdisplay.frag.spv | Bin 2432 -> 0 bytes shaders/slang/multiview/viewdisplay.slang | 46 - shaders/slang/multiview/viewdisplay.vert.spv | Bin 832 -> 0 bytes .../negativeviewportheight/quad.frag.spv | Bin 596 -> 0 bytes .../slang/negativeviewportheight/quad.slang | 30 - .../negativeviewportheight/quad.vert.spv | Bin 588 -> 0 bytes shaders/slang/occlusionquery/mesh.frag.spv | Bin 1436 -> 0 bytes shaders/slang/occlusionquery/mesh.slang | 67 - shaders/slang/occlusionquery/mesh.vert.spv | Bin 5024 -> 0 bytes .../slang/occlusionquery/occluder.frag.spv | Bin 436 -> 0 bytes shaders/slang/occlusionquery/occluder.slang | 42 - .../slang/occlusionquery/occluder.vert.spv | Bin 3104 -> 0 bytes shaders/slang/occlusionquery/simple.frag.spv | Bin 340 -> 0 bytes shaders/slang/occlusionquery/simple.slang | 39 - shaders/slang/occlusionquery/simple.vert.spv | Bin 2916 -> 0 bytes shaders/slang/offscreen/mirror.frag.spv | Bin 2396 -> 0 bytes shaders/slang/offscreen/mirror.slang | 68 - shaders/slang/offscreen/mirror.vert.spv | Bin 2856 -> 0 bytes shaders/slang/offscreen/phong.frag.spv | Bin 1440 -> 0 bytes shaders/slang/offscreen/phong.slang | 63 - shaders/slang/offscreen/phong.vert.spv | Bin 4848 -> 0 bytes shaders/slang/offscreen/quad.frag.spv | Bin 596 -> 0 bytes shaders/slang/offscreen/quad.slang | 28 - shaders/slang/offscreen/quad.vert.spv | Bin 832 -> 0 bytes shaders/slang/oit/color.frag.spv | Bin 4232 -> 0 bytes shaders/slang/oit/color.slang | 75 - shaders/slang/oit/color.vert.spv | Bin 740 -> 0 bytes shaders/slang/oit/geometry.frag.spv | Bin 2404 -> 0 bytes shaders/slang/oit/geometry.slang | 75 - shaders/slang/oit/geometry.vert.spv | Bin 3080 -> 0 bytes .../slang/parallaxmapping/parallax.frag.spv | Bin 6084 -> 0 bytes shaders/slang/parallaxmapping/parallax.slang | 150 -- .../slang/parallaxmapping/parallax.vert.spv | Bin 4592 -> 0 bytes .../slang/particlesystem/normalmap.frag.spv | Bin 1936 -> 0 bytes shaders/slang/particlesystem/normalmap.slang | 98 - .../slang/particlesystem/normalmap.vert.spv | Bin 6304 -> 0 bytes .../slang/particlesystem/particle.frag.spv | Bin 2744 -> 0 bytes shaders/slang/particlesystem/particle.slang | 97 - .../slang/particlesystem/particle.vert.spv | Bin 4912 -> 0 bytes shaders/slang/pbrbasic/pbr.frag.spv | Bin 4288 -> 0 bytes shaders/slang/pbrbasic/pbr.slang | 138 -- shaders/slang/pbrbasic/pbr.vert.spv | Bin 3896 -> 0 bytes shaders/slang/pbribl/filtercube.slang | 25 - shaders/slang/pbribl/filtercube.vert.spv | Bin 1500 -> 0 bytes shaders/slang/pbribl/genbrdflut.frag.spv | Bin 3856 -> 0 bytes shaders/slang/pbribl/genbrdflut.slang | 108 - shaders/slang/pbribl/genbrdflut.vert.spv | Bin 832 -> 0 bytes shaders/slang/pbribl/irradiancecube.frag.spv | Bin 2820 -> 0 bytes shaders/slang/pbribl/irradiancecube.slang | 39 - shaders/slang/pbribl/pbribl.frag.spv | Bin 6784 -> 0 bytes shaders/slang/pbribl/pbribl.slang | 193 -- shaders/slang/pbribl/pbribl.vert.spv | Bin 4168 -> 0 bytes shaders/slang/pbribl/prefilterenvmap.frag.spv | Bin 5060 -> 0 bytes shaders/slang/pbribl/prefilterenvmap.slang | 109 - shaders/slang/pbribl/skybox.frag.spv | Bin 1856 -> 0 bytes shaders/slang/pbribl/skybox.slang | 70 - shaders/slang/pbribl/skybox.vert.spv | Bin 2188 -> 0 bytes shaders/slang/pbrtexture/filtercube.slang | 25 - shaders/slang/pbrtexture/filtercube.vert.spv | Bin 1500 -> 0 bytes shaders/slang/pbrtexture/genbrdflut.frag.spv | Bin 3856 -> 0 bytes shaders/slang/pbrtexture/genbrdflut.slang | 108 - shaders/slang/pbrtexture/genbrdflut.vert.spv | Bin 832 -> 0 bytes .../slang/pbrtexture/irradiancecube.frag.spv | Bin 2780 -> 0 bytes shaders/slang/pbrtexture/irradiancecube.slang | 39 - shaders/slang/pbrtexture/pbrtexture.frag.spv | Bin 7500 -> 0 bytes shaders/slang/pbrtexture/pbrtexture.slang | 198 -- shaders/slang/pbrtexture/pbrtexture.vert.spv | Bin 4608 -> 0 bytes .../slang/pbrtexture/prefilterenvmap.frag.spv | Bin 5060 -> 0 bytes .../slang/pbrtexture/prefilterenvmap.slang | 109 - shaders/slang/pbrtexture/skybox.frag.spv | Bin 1856 -> 0 bytes shaders/slang/pbrtexture/skybox.slang | 70 - shaders/slang/pbrtexture/skybox.vert.spv | Bin 2188 -> 0 bytes shaders/slang/pipelines/phong.frag.spv | Bin 1340 -> 0 bytes shaders/slang/pipelines/phong.slang | 62 - shaders/slang/pipelines/phong.vert.spv | Bin 4516 -> 0 bytes shaders/slang/pipelines/toon.frag.spv | Bin 1332 -> 0 bytes shaders/slang/pipelines/toon.slang | 70 - shaders/slang/pipelines/toon.vert.spv | Bin 4516 -> 0 bytes shaders/slang/pipelines/wireframe.frag.spv | Bin 472 -> 0 bytes shaders/slang/pipelines/wireframe.slang | 40 - shaders/slang/pipelines/wireframe.vert.spv | Bin 2212 -> 0 bytes .../slang/pipelinestatistics/scene.frag.spv | Bin 1096 -> 0 bytes shaders/slang/pipelinestatistics/scene.slang | 127 - .../slang/pipelinestatistics/scene.tesc.spv | Bin 3120 -> 0 bytes .../slang/pipelinestatistics/scene.tese.spv | Bin 2132 -> 0 bytes .../slang/pipelinestatistics/scene.vert.spv | Bin 4240 -> 0 bytes .../pushconstants/pushconstants.frag.spv | Bin 436 -> 0 bytes .../slang/pushconstants/pushconstants.slang | 44 - .../pushconstants/pushconstants.vert.spv | Bin 3468 -> 0 bytes shaders/slang/pushdescriptors/cube.frag.spv | Bin 756 -> 0 bytes shaders/slang/pushdescriptors/cube.slang | 51 - shaders/slang/pushdescriptors/cube.vert.spv | Bin 3448 -> 0 bytes shaders/slang/radialblur/colorpass.frag.spv | Bin 1356 -> 0 bytes shaders/slang/radialblur/colorpass.slang | 51 - shaders/slang/radialblur/colorpass.vert.spv | Bin 2504 -> 0 bytes shaders/slang/radialblur/phongpass.frag.spv | Bin 2164 -> 0 bytes shaders/slang/radialblur/phongpass.slang | 67 - shaders/slang/radialblur/phongpass.vert.spv | Bin 3604 -> 0 bytes shaders/slang/radialblur/radialblur.frag.spv | Bin 2596 -> 0 bytes shaders/slang/radialblur/radialblur.slang | 48 - shaders/slang/radialblur/radialblur.vert.spv | Bin 832 -> 0 bytes shaders/slang/rayquery/scene.frag.spv | Bin 1480 -> 0 bytes shaders/slang/rayquery/scene.slang | 81 - shaders/slang/rayquery/scene.vert.spv | Bin 5564 -> 0 bytes .../raytracingbasic/closesthit.rchit.spv | Bin 668 -> 0 bytes shaders/slang/raytracingbasic/miss.rmiss.spv | Bin 432 -> 0 bytes shaders/slang/raytracingbasic/raygen.rgen.spv | Bin 3924 -> 0 bytes .../raytracingbasic/raytracingbasic.slang | 60 - .../raytracingcallable/callable1.rcall.spv | Bin 616 -> 0 bytes .../slang/raytracingcallable/callable1.slang | 14 - .../raytracingcallable/callable2.rcall.spv | Bin 316 -> 0 bytes .../slang/raytracingcallable/callable2.slang | 11 - .../raytracingcallable/callable3.rcall.spv | Bin 504 -> 0 bytes .../slang/raytracingcallable/callable3.slang | 14 - .../raytracingcallable/closesthit.rchit.spv | Bin 652 -> 0 bytes .../slang/raytracingcallable/miss.rmiss.spv | Bin 432 -> 0 bytes .../slang/raytracingcallable/raygen.rgen.spv | Bin 3924 -> 0 bytes .../raytracingcallable.slang | 68 - shaders/slang/raytracinggltf/anyhit.rahit.spv | Bin 4072 -> 0 bytes .../slang/raytracinggltf/closesthit.rchit.spv | Bin 4460 -> 0 bytes shaders/slang/raytracinggltf/miss.rmiss.spv | Bin 496 -> 0 bytes shaders/slang/raytracinggltf/raygen.rgen.spv | Bin 7932 -> 0 bytes .../slang/raytracinggltf/raytracinggltf.slang | 219 -- shaders/slang/raytracinggltf/shadow.rmiss.spv | Bin 468 -> 0 bytes shaders/slang/raytracinggltf/shadow.slang | 18 - .../closesthit.rchit.spv | Bin 1996 -> 0 bytes .../intersection.rint.spv | Bin 1656 -> 0 bytes .../raytracingintersection/miss.rmiss.spv | Bin 432 -> 0 bytes .../raytracingintersection/raygen.rgen.spv | Bin 3960 -> 0 bytes .../raytracingintersection.slang | 96 - .../closesthit.rchit.spv | Bin 2256 -> 0 bytes .../raytracingpositionfetch/miss.rmiss.spv | Bin 432 -> 0 bytes .../raytracingpositionfetch/raygen.rgen.spv | Bin 3960 -> 0 bytes .../raytracingpositionfetch.slang | 78 - .../closesthit.rchit.spv | Bin 4360 -> 0 bytes .../raytracingreflections/miss.rmiss.spv | Bin 1016 -> 0 bytes .../raytracingreflections/raygen.rgen.spv | Bin 5264 -> 0 bytes .../raytracingreflections.slang | 144 -- .../raytracingsbtdata/closesthit.rchit.spv | Bin 732 -> 0 bytes .../slang/raytracingsbtdata/miss.rmiss.spv | Bin 728 -> 0 bytes .../slang/raytracingsbtdata/raygen.rgen.spv | Bin 5220 -> 0 bytes .../raytracingsbtdata/raytracingsbtdata.slang | 88 - .../raytracingshadows/closesthit.rchit.spv | Bin 4216 -> 0 bytes .../slang/raytracingshadows/miss.rmiss.spv | Bin 468 -> 0 bytes shaders/slang/raytracingshadows/payload.slang | 13 - .../slang/raytracingshadows/raygen.rgen.spv | Bin 4044 -> 0 bytes .../raytracingshadows/raytracingshadows.slang | 117 - .../slang/raytracingshadows/shadow.rmiss.spv | Bin 424 -> 0 bytes shaders/slang/raytracingshadows/shadow.slang | 14 - .../slang/raytracingtextures/anyhit.rahit.spv | Bin 2888 -> 0 bytes .../raytracingtextures/closesthit.rchit.spv | Bin 2956 -> 0 bytes .../slang/raytracingtextures/miss.rmiss.spv | Bin 432 -> 0 bytes .../slang/raytracingtextures/raygen.rgen.spv | Bin 3912 -> 0 bytes .../raytracingtextures.slang | 114 - .../slang/renderheadless/triangle.frag.spv | Bin 436 -> 0 bytes shaders/slang/renderheadless/triangle.slang | 32 - .../slang/renderheadless/triangle.vert.spv | Bin 1576 -> 0 bytes shaders/slang/screenshot/mesh.frag.spv | Bin 1160 -> 0 bytes shaders/slang/screenshot/mesh.slang | 60 - shaders/slang/screenshot/mesh.vert.spv | Bin 4564 -> 0 bytes shaders/slang/shaderobjects/phong.frag.spv | Bin 1340 -> 0 bytes shaders/slang/shaderobjects/phong.slang | 65 - shaders/slang/shaderobjects/phong.vert.spv | Bin 4840 -> 0 bytes .../slang/shadowmapping/offscreen.frag.spv | Bin 356 -> 0 bytes shaders/slang/shadowmapping/offscreen.slang | 23 - .../slang/shadowmapping/offscreen.vert.spv | Bin 1368 -> 0 bytes shaders/slang/shadowmapping/quad.frag.spv | Bin 1588 -> 0 bytes shaders/slang/shadowmapping/quad.slang | 48 - shaders/slang/shadowmapping/quad.vert.spv | Bin 832 -> 0 bytes shaders/slang/shadowmapping/scene.frag.spv | Bin 4248 -> 0 bytes shaders/slang/shadowmapping/scene.slang | 116 - shaders/slang/shadowmapping/scene.vert.spv | Bin 6356 -> 0 bytes .../debugshadowmap.frag.spv | Bin 1068 -> 0 bytes .../shadowmappingcascade/debugshadowmap.slang | 29 - .../debugshadowmap.vert.spv | Bin 832 -> 0 bytes .../shadowmappingcascade/depthpass.frag.spv | Bin 716 -> 0 bytes .../shadowmappingcascade/depthpass.slang | 46 - .../shadowmappingcascade/depthpass.vert.spv | Bin 2212 -> 0 bytes .../slang/shadowmappingcascade/scene.frag.spv | Bin 9196 -> 0 bytes .../slang/shadowmappingcascade/scene.slang | 168 -- .../slang/shadowmappingcascade/scene.vert.spv | Bin 4424 -> 0 bytes .../shadowmappingomni/cubemapdisplay.frag.spv | Bin 3216 -> 0 bytes .../shadowmappingomni/cubemapdisplay.slang | 80 - .../shadowmappingomni/cubemapdisplay.vert.spv | Bin 832 -> 0 bytes .../shadowmappingomni/offscreen.frag.spv | Bin 616 -> 0 bytes .../slang/shadowmappingomni/offscreen.slang | 45 - .../shadowmappingomni/offscreen.vert.spv | Bin 3336 -> 0 bytes .../slang/shadowmappingomni/scene.frag.spv | Bin 1804 -> 0 bytes shaders/slang/shadowmappingomni/scene.slang | 80 - .../slang/shadowmappingomni/scene.vert.spv | Bin 4388 -> 0 bytes .../specializationconstants/uber.frag.spv | Bin 3648 -> 0 bytes .../slang/specializationconstants/uber.slang | 110 - .../specializationconstants/uber.vert.spv | Bin 4724 -> 0 bytes .../slang/sphericalenvmapping/sem.frag.spv | Bin 1472 -> 0 bytes shaders/slang/sphericalenvmapping/sem.slang | 58 - .../slang/sphericalenvmapping/sem.vert.spv | Bin 4088 -> 0 bytes shaders/slang/ssao/blur.frag.spv | Bin 2396 -> 0 bytes shaders/slang/ssao/blur.slang | 30 - shaders/slang/ssao/composition.frag.spv | Bin 3372 -> 0 bytes shaders/slang/ssao/composition.slang | 59 - shaders/slang/ssao/fullscreen.slang | 16 - shaders/slang/ssao/fullscreen.vert.spv | Bin 832 -> 0 bytes shaders/slang/ssao/gbuffer.frag.spv | Bin 2396 -> 0 bytes shaders/slang/ssao/gbuffer.slang | 73 - shaders/slang/ssao/gbuffer.vert.spv | Bin 6064 -> 0 bytes shaders/slang/ssao/ssao.frag.spv | Bin 5644 -> 0 bytes shaders/slang/ssao/ssao.slang | 70 - shaders/slang/ssao/types.slang | 13 - shaders/slang/stencilbuffer/outline.frag.spv | Bin 340 -> 0 bytes shaders/slang/stencilbuffer/outline.slang | 42 - shaders/slang/stencilbuffer/outline.vert.spv | Bin 2400 -> 0 bytes shaders/slang/stencilbuffer/toon.frag.spv | Bin 1624 -> 0 bytes shaders/slang/stencilbuffer/toon.slang | 65 - shaders/slang/stencilbuffer/toon.vert.spv | Bin 4376 -> 0 bytes shaders/slang/subpasses/composition.frag.spv | Bin 2668 -> 0 bytes shaders/slang/subpasses/composition.slang | 66 - shaders/slang/subpasses/composition.vert.spv | Bin 832 -> 0 bytes shaders/slang/subpasses/gbuffer.frag.spv | Bin 1648 -> 0 bytes shaders/slang/subpasses/gbuffer.slang | 78 - shaders/slang/subpasses/gbuffer.vert.spv | Bin 4572 -> 0 bytes shaders/slang/subpasses/transparent.frag.spv | Bin 1668 -> 0 bytes shaders/slang/subpasses/transparent.slang | 64 - shaders/slang/subpasses/transparent.vert.spv | Bin 3164 -> 0 bytes .../terraintessellation/skysphere.frag.spv | Bin 596 -> 0 bytes .../slang/terraintessellation/skysphere.slang | 42 - .../terraintessellation/skysphere.vert.spv | Bin 1576 -> 0 bytes .../terraintessellation/terrain.frag.spv | Bin 3256 -> 0 bytes .../slang/terraintessellation/terrain.slang | 252 -- .../terraintessellation/terrain.tesc.spv | Bin 15940 -> 0 bytes .../terraintessellation/terrain.tese.spv | Bin 5580 -> 0 bytes .../terraintessellation/terrain.vert.spv | Bin 804 -> 0 bytes shaders/slang/tessellation/base.frag.spv | Bin 1068 -> 0 bytes shaders/slang/tessellation/base.slang | 46 - shaders/slang/tessellation/base.vert.spv | Bin 804 -> 0 bytes shaders/slang/tessellation/passthrough.slang | 83 - .../slang/tessellation/passthrough.tesc.spv | Bin 2468 -> 0 bytes .../slang/tessellation/passthrough.tese.spv | Bin 3396 -> 0 bytes shaders/slang/tessellation/pntriangles.slang | 216 -- .../slang/tessellation/pntriangles.tesc.spv | Bin 6032 -> 0 bytes .../slang/tessellation/pntriangles.tese.spv | Bin 6380 -> 0 bytes shaders/slang/textoverlay/mesh.frag.spv | Bin 952 -> 0 bytes shaders/slang/textoverlay/mesh.slang | 56 - shaders/slang/textoverlay/mesh.vert.spv | Bin 4608 -> 0 bytes shaders/slang/textoverlay/text.frag.spv | Bin 656 -> 0 bytes shaders/slang/textoverlay/text.slang | 35 - shaders/slang/textoverlay/text.vert.spv | Bin 576 -> 0 bytes shaders/slang/texture/texture.frag.spv | Bin 1464 -> 0 bytes shaders/slang/texture/texture.slang | 68 - shaders/slang/texture/texture.vert.spv | Bin 4820 -> 0 bytes shaders/slang/texture3d/texture3d.frag.spv | Bin 1304 -> 0 bytes shaders/slang/texture3d/texture3d.slang | 66 - shaders/slang/texture3d/texture3d.vert.spv | Bin 4716 -> 0 bytes .../slang/texturearray/instancing.frag.spv | Bin 596 -> 0 bytes shaders/slang/texturearray/instancing.slang | 49 - .../slang/texturearray/instancing.vert.spv | Bin 3556 -> 0 bytes shaders/slang/texturecubemap/reflect.frag.spv | Bin 2704 -> 0 bytes shaders/slang/texturecubemap/reflect.slang | 70 - shaders/slang/texturecubemap/reflect.vert.spv | Bin 4148 -> 0 bytes shaders/slang/texturecubemap/skybox.frag.spv | Bin 596 -> 0 bytes shaders/slang/texturecubemap/skybox.slang | 47 - shaders/slang/texturecubemap/skybox.vert.spv | Bin 2684 -> 0 bytes .../texturecubemaparray/reflect.frag.spv | Bin 3056 -> 0 bytes .../slang/texturecubemaparray/reflect.slang | 68 - .../texturecubemaparray/reflect.vert.spv | Bin 4024 -> 0 bytes .../slang/texturecubemaparray/skybox.frag.spv | Bin 1280 -> 0 bytes .../slang/texturecubemaparray/skybox.slang | 49 - .../slang/texturecubemaparray/skybox.vert.spv | Bin 2828 -> 0 bytes .../slang/texturemipmapgen/texture.frag.spv | Bin 2384 -> 0 bytes shaders/slang/texturemipmapgen/texture.slang | 68 - .../slang/texturemipmapgen/texture.vert.spv | Bin 5104 -> 0 bytes .../sparseresidency.frag.spv | Bin 1156 -> 0 bytes .../sparseresidency.slang | 54 - .../sparseresidency.vert.spv | Bin 2556 -> 0 bytes shaders/slang/triangle/triangle.frag.spv | Bin 436 -> 0 bytes shaders/slang/triangle/triangle.slang | 41 - shaders/slang/triangle/triangle.vert.spv | Bin 2956 -> 0 bytes .../slang/variablerateshading/scene.frag.spv | Bin 4148 -> 0 bytes shaders/slang/variablerateshading/scene.slang | 116 - .../slang/variablerateshading/scene.vert.spv | Bin 5236 -> 0 bytes shaders/slang/vertexattributes/scene.frag.spv | Bin 2700 -> 0 bytes shaders/slang/vertexattributes/scene.slang | 86 - shaders/slang/vertexattributes/scene.vert.spv | Bin 5484 -> 0 bytes .../slang/viewportarray/multiview.geom.spv | Bin 5644 -> 0 bytes shaders/slang/viewportarray/scene.frag.spv | Bin 1196 -> 0 bytes shaders/slang/viewportarray/scene.slang | 90 - shaders/slang/viewportarray/scene.vert.spv | Bin 760 -> 0 bytes shaders/slang/vulkanscene/logo.frag.spv | Bin 764 -> 0 bytes shaders/slang/vulkanscene/logo.slang | 60 - shaders/slang/vulkanscene/logo.vert.spv | Bin 4428 -> 0 bytes shaders/slang/vulkanscene/mesh.frag.spv | Bin 2224 -> 0 bytes shaders/slang/vulkanscene/mesh.slang | 85 - shaders/slang/vulkanscene/mesh.vert.spv | Bin 4440 -> 0 bytes shaders/slang/vulkanscene/skybox.frag.spv | Bin 596 -> 0 bytes shaders/slang/vulkanscene/skybox.slang | 46 - shaders/slang/vulkanscene/skybox.vert.spv | Bin 3220 -> 0 bytes src/CMakeLists.txt | 87 + src/core/Application.cpp | 669 ++++++ src/core/Application.h | 98 + src/core/Camera.cpp | 238 ++ src/core/Camera.h | 93 + src/main.cpp | 15 + src/scene/ProceduralGeometry.cpp | 336 +++ src/scene/ProceduralGeometry.h | 51 + src/scene/SceneManager.cpp | 154 ++ src/scene/SceneManager.h | 53 + src/scene/SceneObject.cpp | 108 + src/scene/SceneObject.h | 91 + src/ui/UIManager.cpp | 1046 +++++++++ src/ui/UIManager.h | 102 + src/ui/UISettings.h | 38 + 2429 files changed, 7751 insertions(+), 112835 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md delete mode 100644 android/.gitignore delete mode 100644 android/build.gradle delete mode 100644 android/common/res/drawable/icon.png delete mode 100644 android/examples/_template/CMakeLists.txt delete mode 100644 android/examples/_template/build.gradle delete mode 100644 android/examples/_template/src/main/AndroidManifest.xml delete mode 100644 android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/base/CMakeLists.txt delete mode 100644 android/examples/bloom/CMakeLists.txt delete mode 100644 android/examples/bloom/build.gradle delete mode 100644 android/examples/bloom/src/main/AndroidManifest.xml delete mode 100644 android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/bufferdeviceaddress/CMakeLists.txt delete mode 100644 android/examples/bufferdeviceaddress/build.gradle delete mode 100644 android/examples/bufferdeviceaddress/src/main/AndroidManifest.xml delete mode 100644 android/examples/bufferdeviceaddress/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computecloth/CMakeLists.txt delete mode 100644 android/examples/computecloth/build.gradle delete mode 100644 android/examples/computecloth/src/main/AndroidManifest.xml delete mode 100644 android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computecullandlod/CMakeLists.txt delete mode 100644 android/examples/computecullandlod/build.gradle delete mode 100644 android/examples/computecullandlod/src/main/AndroidManifest.xml delete mode 100644 android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computeheadless/CMakeLists.txt delete mode 100644 android/examples/computeheadless/build.gradle delete mode 100644 android/examples/computeheadless/src/main/AndroidManifest.xml delete mode 100644 android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computenbody/CMakeLists.txt delete mode 100644 android/examples/computenbody/build.gradle delete mode 100644 android/examples/computenbody/src/main/AndroidManifest.xml delete mode 100644 android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computeparticles/CMakeLists.txt delete mode 100644 android/examples/computeparticles/build.gradle delete mode 100644 android/examples/computeparticles/src/main/AndroidManifest.xml delete mode 100644 android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computeraytracing/CMakeLists.txt delete mode 100644 android/examples/computeraytracing/build.gradle delete mode 100644 android/examples/computeraytracing/src/main/AndroidManifest.xml delete mode 100644 android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/computeshader/CMakeLists.txt delete mode 100644 android/examples/computeshader/build.gradle delete mode 100644 android/examples/computeshader/src/main/AndroidManifest.xml delete mode 100644 android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/conditionalrender/CMakeLists.txt delete mode 100644 android/examples/conditionalrender/build.gradle delete mode 100644 android/examples/conditionalrender/src/main/AndroidManifest.xml delete mode 100644 android/examples/conditionalrender/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/conservativeraster/CMakeLists.txt delete mode 100644 android/examples/conservativeraster/build.gradle delete mode 100644 android/examples/conservativeraster/src/main/AndroidManifest.xml delete mode 100644 android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/debugprintf/CMakeLists.txt delete mode 100644 android/examples/debugprintf/build.gradle delete mode 100644 android/examples/debugprintf/src/main/AndroidManifest.xml delete mode 100644 android/examples/debugprintf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/debugutils/CMakeLists.txt delete mode 100644 android/examples/debugutils/build.gradle delete mode 100644 android/examples/debugutils/src/main/AndroidManifest.xml delete mode 100644 android/examples/debugutils/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/deferred/CMakeLists.txt delete mode 100644 android/examples/deferred/build.gradle delete mode 100644 android/examples/deferred/src/main/AndroidManifest.xml delete mode 100644 android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/deferredmultisampling/CMakeLists.txt delete mode 100644 android/examples/deferredmultisampling/build.gradle delete mode 100644 android/examples/deferredmultisampling/src/main/AndroidManifest.xml delete mode 100644 android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/deferredshadows/CMakeLists.txt delete mode 100644 android/examples/deferredshadows/build.gradle delete mode 100644 android/examples/deferredshadows/src/main/AndroidManifest.xml delete mode 100644 android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/descriptorbuffer/CMakeLists.txt delete mode 100644 android/examples/descriptorbuffer/build.gradle delete mode 100644 android/examples/descriptorbuffer/src/main/AndroidManifest.xml delete mode 100644 android/examples/descriptorbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/descriptorindexing/CMakeLists.txt delete mode 100644 android/examples/descriptorindexing/build.gradle delete mode 100644 android/examples/descriptorindexing/src/main/AndroidManifest.xml delete mode 100644 android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/descriptorsets/CMakeLists.txt delete mode 100644 android/examples/descriptorsets/build.gradle delete mode 100644 android/examples/descriptorsets/src/main/AndroidManifest.xml delete mode 100644 android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/displacement/CMakeLists.txt delete mode 100644 android/examples/displacement/build.gradle delete mode 100644 android/examples/displacement/src/main/AndroidManifest.xml delete mode 100644 android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/distancefieldfonts/CMakeLists.txt delete mode 100644 android/examples/distancefieldfonts/build.gradle delete mode 100644 android/examples/distancefieldfonts/src/main/AndroidManifest.xml delete mode 100644 android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/dynamicrendering/CMakeLists.txt delete mode 100644 android/examples/dynamicrendering/build.gradle delete mode 100644 android/examples/dynamicrendering/src/main/AndroidManifest.xml delete mode 100644 android/examples/dynamicrendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/dynamicrenderingmultisampling/CMakeLists.txt delete mode 100644 android/examples/dynamicrenderingmultisampling/build.gradle delete mode 100644 android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml delete mode 100644 android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/dynamicstate/CMakeLists.txt delete mode 100644 android/examples/dynamicstate/build.gradle delete mode 100644 android/examples/dynamicstate/src/main/AndroidManifest.xml delete mode 100644 android/examples/dynamicstate/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/dynamicuniformbuffer/CMakeLists.txt delete mode 100644 android/examples/dynamicuniformbuffer/build.gradle delete mode 100644 android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml delete mode 100644 android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/gears/CMakeLists.txt delete mode 100644 android/examples/gears/build.gradle delete mode 100644 android/examples/gears/src/main/AndroidManifest.xml delete mode 100644 android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/geometryshader/CMakeLists.txt delete mode 100644 android/examples/geometryshader/build.gradle delete mode 100644 android/examples/geometryshader/src/main/AndroidManifest.xml delete mode 100644 android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/gltfloading/CMakeLists.txt delete mode 100644 android/examples/gltfloading/build.gradle delete mode 100644 android/examples/gltfloading/src/main/AndroidManifest.xml delete mode 100644 android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/gltfscenerendering/CMakeLists.txt delete mode 100644 android/examples/gltfscenerendering/build.gradle delete mode 100644 android/examples/gltfscenerendering/src/main/AndroidManifest.xml delete mode 100644 android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/gltfskinning/CMakeLists.txt delete mode 100644 android/examples/gltfskinning/build.gradle delete mode 100644 android/examples/gltfskinning/src/main/AndroidManifest.xml delete mode 100644 android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/gradle/outputfilename.gradle delete mode 100644 android/examples/graphicspipelinelibrary/CMakeLists.txt delete mode 100644 android/examples/graphicspipelinelibrary/build.gradle delete mode 100644 android/examples/graphicspipelinelibrary/src/main/AndroidManifest.xml delete mode 100644 android/examples/graphicspipelinelibrary/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/hdr/CMakeLists.txt delete mode 100644 android/examples/hdr/build.gradle delete mode 100644 android/examples/hdr/src/main/AndroidManifest.xml delete mode 100644 android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/hostimagecopy/CMakeLists.txt delete mode 100644 android/examples/hostimagecopy/build.gradle delete mode 100644 android/examples/hostimagecopy/src/main/AndroidManifest.xml delete mode 100644 android/examples/hostimagecopy/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/imgui/CMakeLists.txt delete mode 100644 android/examples/imgui/build.gradle delete mode 100644 android/examples/imgui/src/main/AndroidManifest.xml delete mode 100644 android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/indirectdraw/CMakeLists.txt delete mode 100644 android/examples/indirectdraw/build.gradle delete mode 100644 android/examples/indirectdraw/src/main/AndroidManifest.xml delete mode 100644 android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/inlineuniformblocks/CMakeLists.txt delete mode 100644 android/examples/inlineuniformblocks/build.gradle delete mode 100644 android/examples/inlineuniformblocks/src/main/AndroidManifest.xml delete mode 100644 android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/inputattachments/CMakeLists.txt delete mode 100644 android/examples/inputattachments/build.gradle delete mode 100644 android/examples/inputattachments/src/main/AndroidManifest.xml delete mode 100644 android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/instancing/CMakeLists.txt delete mode 100644 android/examples/instancing/build.gradle delete mode 100644 android/examples/instancing/src/main/AndroidManifest.xml delete mode 100644 android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/meshshader/CMakeLists.txt delete mode 100644 android/examples/meshshader/build.gradle delete mode 100644 android/examples/meshshader/src/main/AndroidManifest.xml delete mode 100644 android/examples/meshshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/multisampling/CMakeLists.txt delete mode 100644 android/examples/multisampling/build.gradle delete mode 100644 android/examples/multisampling/src/main/AndroidManifest.xml delete mode 100644 android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/multithreading/CMakeLists.txt delete mode 100644 android/examples/multithreading/build.gradle delete mode 100644 android/examples/multithreading/src/main/AndroidManifest.xml delete mode 100644 android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/multiview/CMakeLists.txt delete mode 100644 android/examples/multiview/build.gradle delete mode 100644 android/examples/multiview/src/main/AndroidManifest.xml delete mode 100644 android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/negativeviewportheight/CMakeLists.txt delete mode 100644 android/examples/negativeviewportheight/build.gradle delete mode 100644 android/examples/negativeviewportheight/src/main/AndroidManifest.xml delete mode 100644 android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/occlusionquery/CMakeLists.txt delete mode 100644 android/examples/occlusionquery/build.gradle delete mode 100644 android/examples/occlusionquery/src/main/AndroidManifest.xml delete mode 100644 android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/offscreen/CMakeLists.txt delete mode 100644 android/examples/offscreen/build.gradle delete mode 100644 android/examples/offscreen/src/main/AndroidManifest.xml delete mode 100644 android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/oit/CMakeLists.txt delete mode 100644 android/examples/oit/build.gradle delete mode 100644 android/examples/oit/src/main/AndroidManifest.xml delete mode 100644 android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/parallaxmapping/CMakeLists.txt delete mode 100644 android/examples/parallaxmapping/build.gradle delete mode 100644 android/examples/parallaxmapping/src/main/AndroidManifest.xml delete mode 100644 android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/particlesystem/CMakeLists.txt delete mode 100644 android/examples/particlesystem/build.gradle delete mode 100644 android/examples/particlesystem/src/main/AndroidManifest.xml delete mode 100644 android/examples/particlesystem/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pbrbasic/CMakeLists.txt delete mode 100644 android/examples/pbrbasic/build.gradle delete mode 100644 android/examples/pbrbasic/src/main/AndroidManifest.xml delete mode 100644 android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pbribl/CMakeLists.txt delete mode 100644 android/examples/pbribl/build.gradle delete mode 100644 android/examples/pbribl/src/main/AndroidManifest.xml delete mode 100644 android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pbrtexture/CMakeLists.txt delete mode 100644 android/examples/pbrtexture/build.gradle delete mode 100644 android/examples/pbrtexture/src/main/AndroidManifest.xml delete mode 100644 android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pipelines/CMakeLists.txt delete mode 100644 android/examples/pipelines/build.gradle delete mode 100644 android/examples/pipelines/src/main/AndroidManifest.xml delete mode 100644 android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pipelinestatistics/CMakeLists.txt delete mode 100644 android/examples/pipelinestatistics/build.gradle delete mode 100644 android/examples/pipelinestatistics/src/main/AndroidManifest.xml delete mode 100644 android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pushconstants/CMakeLists.txt delete mode 100644 android/examples/pushconstants/build.gradle delete mode 100644 android/examples/pushconstants/src/main/AndroidManifest.xml delete mode 100644 android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/pushdescriptors/CMakeLists.txt delete mode 100644 android/examples/pushdescriptors/build.gradle delete mode 100644 android/examples/pushdescriptors/src/main/AndroidManifest.xml delete mode 100644 android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/radialblur/CMakeLists.txt delete mode 100644 android/examples/radialblur/build.gradle delete mode 100644 android/examples/radialblur/src/main/AndroidManifest.xml delete mode 100644 android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/rayquery/CMakeLists.txt delete mode 100644 android/examples/rayquery/build.gradle delete mode 100644 android/examples/rayquery/src/main/AndroidManifest.xml delete mode 100644 android/examples/rayquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingbasic/CMakeLists.txt delete mode 100644 android/examples/raytracingbasic/build.gradle delete mode 100644 android/examples/raytracingbasic/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingcallable/CMakeLists.txt delete mode 100644 android/examples/raytracingcallable/build.gradle delete mode 100644 android/examples/raytracingcallable/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingcallable/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracinggltf/CMakeLists.txt delete mode 100644 android/examples/raytracinggltf/build.gradle delete mode 100644 android/examples/raytracinggltf/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracinggltf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingintersection/CMakeLists.txt delete mode 100644 android/examples/raytracingintersection/build.gradle delete mode 100644 android/examples/raytracingintersection/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingintersection/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingpositionfetch/CMakeLists.txt delete mode 100644 android/examples/raytracingpositionfetch/build.gradle delete mode 100644 android/examples/raytracingpositionfetch/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingpositionfetch/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingreflections/CMakeLists.txt delete mode 100644 android/examples/raytracingreflections/build.gradle delete mode 100644 android/examples/raytracingreflections/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingsbtdata/CMakeLists.txt delete mode 100644 android/examples/raytracingsbtdata/build.gradle delete mode 100644 android/examples/raytracingsbtdata/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingsbtdata/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingshadows/CMakeLists.txt delete mode 100644 android/examples/raytracingshadows/build.gradle delete mode 100644 android/examples/raytracingshadows/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/raytracingtextures/CMakeLists.txt delete mode 100644 android/examples/raytracingtextures/build.gradle delete mode 100644 android/examples/raytracingtextures/src/main/AndroidManifest.xml delete mode 100644 android/examples/raytracingtextures/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/renderheadless/CMakeLists.txt delete mode 100644 android/examples/renderheadless/build.gradle delete mode 100644 android/examples/renderheadless/src/main/AndroidManifest.xml delete mode 100644 android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/screenshot/CMakeLists.txt delete mode 100644 android/examples/screenshot/build.gradle delete mode 100644 android/examples/screenshot/src/main/AndroidManifest.xml delete mode 100644 android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/shaderobjects/CMakeLists.txt delete mode 100644 android/examples/shaderobjects/build.gradle delete mode 100644 android/examples/shaderobjects/src/main/AndroidManifest.xml delete mode 100644 android/examples/shaderobjects/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/shadowmapping/CMakeLists.txt delete mode 100644 android/examples/shadowmapping/build.gradle delete mode 100644 android/examples/shadowmapping/src/main/AndroidManifest.xml delete mode 100644 android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/shadowmappingcascade/CMakeLists.txt delete mode 100644 android/examples/shadowmappingcascade/build.gradle delete mode 100644 android/examples/shadowmappingcascade/src/main/AndroidManifest.xml delete mode 100644 android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/shadowmappingomni/CMakeLists.txt delete mode 100644 android/examples/shadowmappingomni/build.gradle delete mode 100644 android/examples/shadowmappingomni/src/main/AndroidManifest.xml delete mode 100644 android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/specializationconstants/CMakeLists.txt delete mode 100644 android/examples/specializationconstants/build.gradle delete mode 100644 android/examples/specializationconstants/src/main/AndroidManifest.xml delete mode 100644 android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/sphericalenvmapping/CMakeLists.txt delete mode 100644 android/examples/sphericalenvmapping/build.gradle delete mode 100644 android/examples/sphericalenvmapping/src/main/AndroidManifest.xml delete mode 100644 android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/ssao/CMakeLists.txt delete mode 100644 android/examples/ssao/build.gradle delete mode 100644 android/examples/ssao/src/main/AndroidManifest.xml delete mode 100644 android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/stencilbuffer/CMakeLists.txt delete mode 100644 android/examples/stencilbuffer/build.gradle delete mode 100644 android/examples/stencilbuffer/src/main/AndroidManifest.xml delete mode 100644 android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/subpasses/CMakeLists.txt delete mode 100644 android/examples/subpasses/build.gradle delete mode 100644 android/examples/subpasses/src/main/AndroidManifest.xml delete mode 100644 android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/terraintessellation/CMakeLists.txt delete mode 100644 android/examples/terraintessellation/build.gradle delete mode 100644 android/examples/terraintessellation/src/main/AndroidManifest.xml delete mode 100644 android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/tessellation/CMakeLists.txt delete mode 100644 android/examples/tessellation/build.gradle delete mode 100644 android/examples/tessellation/src/main/AndroidManifest.xml delete mode 100644 android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/textoverlay/CMakeLists.txt delete mode 100644 android/examples/textoverlay/build.gradle delete mode 100644 android/examples/textoverlay/src/main/AndroidManifest.xml delete mode 100644 android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texture/CMakeLists.txt delete mode 100644 android/examples/texture/build.gradle delete mode 100644 android/examples/texture/src/main/AndroidManifest.xml delete mode 100644 android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texture3d/CMakeLists.txt delete mode 100644 android/examples/texture3d/build.gradle delete mode 100644 android/examples/texture3d/src/main/AndroidManifest.xml delete mode 100644 android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texturearray/CMakeLists.txt delete mode 100644 android/examples/texturearray/build.gradle delete mode 100644 android/examples/texturearray/src/main/AndroidManifest.xml delete mode 100644 android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texturecubemap/CMakeLists.txt delete mode 100644 android/examples/texturecubemap/build.gradle delete mode 100644 android/examples/texturecubemap/src/main/AndroidManifest.xml delete mode 100644 android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texturecubemaparray/CMakeLists.txt delete mode 100644 android/examples/texturecubemaparray/build.gradle delete mode 100644 android/examples/texturecubemaparray/src/main/AndroidManifest.xml delete mode 100644 android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texturemipmapgen/CMakeLists.txt delete mode 100644 android/examples/texturemipmapgen/build.gradle delete mode 100644 android/examples/texturemipmapgen/src/main/AndroidManifest.xml delete mode 100644 android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/texturesparseresidency/CMakeLists.txt delete mode 100644 android/examples/texturesparseresidency/build.gradle delete mode 100644 android/examples/texturesparseresidency/src/main/AndroidManifest.xml delete mode 100644 android/examples/texturesparseresidency/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/timelinesemaphore/CMakeLists.txt delete mode 100644 android/examples/timelinesemaphore/build.gradle delete mode 100644 android/examples/timelinesemaphore/src/main/AndroidManifest.xml delete mode 100644 android/examples/timelinesemaphore/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/triangle/CMakeLists.txt delete mode 100644 android/examples/triangle/build.gradle delete mode 100644 android/examples/triangle/src/main/AndroidManifest.xml delete mode 100644 android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/trianglevulkan13/CMakeLists.txt delete mode 100644 android/examples/trianglevulkan13/build.gradle delete mode 100644 android/examples/trianglevulkan13/src/main/AndroidManifest.xml delete mode 100644 android/examples/trianglevulkan13/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/variablerateshading/CMakeLists.txt delete mode 100644 android/examples/variablerateshading/build.gradle delete mode 100644 android/examples/variablerateshading/src/main/AndroidManifest.xml delete mode 100644 android/examples/variablerateshading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/vertexattributes/CMakeLists.txt delete mode 100644 android/examples/vertexattributes/build.gradle delete mode 100644 android/examples/vertexattributes/src/main/AndroidManifest.xml delete mode 100644 android/examples/vertexattributes/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/viewportarray/CMakeLists.txt delete mode 100644 android/examples/viewportarray/build.gradle delete mode 100644 android/examples/viewportarray/src/main/AndroidManifest.xml delete mode 100644 android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/examples/vulkanscene/CMakeLists.txt delete mode 100644 android/examples/vulkanscene/build.gradle delete mode 100644 android/examples/vulkanscene/src/main/AndroidManifest.xml delete mode 100644 android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java delete mode 100644 android/gradle.properties delete mode 100644 android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 android/gradlew delete mode 100644 android/gradlew.bat delete mode 100644 android/settings.gradle delete mode 100644 base/VulkanAndroid.cpp delete mode 100644 base/VulkanAndroid.h rename base/{ => core}/VulkanDebug.cpp (100%) rename base/{ => core}/VulkanDebug.h (100%) rename base/{ => core}/VulkanInitializers.hpp (100%) rename base/{ => core}/VulkanTools.cpp (100%) rename base/{ => core}/VulkanTools.h (100%) rename base/{ => device}/VulkanDevice.cpp (99%) rename base/{ => device}/VulkanDevice.h (97%) rename base/{ => device}/VulkanSwapChain.cpp (100%) rename base/{ => device}/VulkanSwapChain.h (99%) rename base/{ => foundation}/VulkanUIOverlay.cpp (100%) rename base/{ => foundation}/VulkanUIOverlay.h (94%) rename base/{ => foundation}/vulkanexamplebase.cpp (99%) rename base/{ => foundation}/vulkanexamplebase.h (97%) rename base/{ => memory}/VulkanBuffer.cpp (100%) rename base/{ => memory}/VulkanBuffer.h (97%) rename base/{ => memory}/VulkanTexture.cpp (99%) rename base/{ => memory}/VulkanTexture.h (97%) delete mode 100644 examples/bloom/bloom.cpp delete mode 100644 examples/bufferdeviceaddress/bufferdeviceaddress.cpp delete mode 100755 examples/computecloth/computecloth.cpp delete mode 100644 examples/computecullandlod/computecullandlod.cpp delete mode 100644 examples/computeheadless/computeheadless.cpp delete mode 100644 examples/computenbody/computenbody.cpp delete mode 100644 examples/computeparticles/computeparticles.cpp delete mode 100644 examples/computeraytracing/computeraytracing.cpp delete mode 100644 examples/computeshader/computeshader.cpp delete mode 100644 examples/conditionalrender/conditionalrender.cpp delete mode 100644 examples/conservativeraster/conservativeraster.cpp delete mode 100644 examples/debugprintf/debugprintf.cpp delete mode 100644 examples/debugutils/debugutils.cpp delete mode 100644 examples/deferred/deferred.cpp delete mode 100644 examples/deferredmultisampling/deferredmultisampling.cpp delete mode 100644 examples/deferredshadows/deferredshadows.cpp delete mode 100644 examples/descriptorbuffer/descriptorbuffer.cpp delete mode 100644 examples/descriptorindexing/descriptorindexing.cpp delete mode 100644 examples/descriptorsets/descriptorsets.cpp delete mode 100644 examples/displacement/displacement.cpp delete mode 100644 examples/distancefieldfonts/distancefieldfonts.cpp delete mode 100644 examples/dynamicrendering/dynamicrendering.cpp delete mode 100644 examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp delete mode 100644 examples/dynamicstate/dynamicstate.cpp delete mode 100644 examples/dynamicuniformbuffer/README.md delete mode 100644 examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp delete mode 100644 examples/gears/gears.cpp delete mode 100644 examples/geometryshader/geometryshader.cpp delete mode 100644 examples/gltfloading/gltfloading.cpp delete mode 100644 examples/gltfscenerendering/README.md delete mode 100644 examples/gltfscenerendering/gltfscenerendering.cpp delete mode 100644 examples/gltfscenerendering/gltfscenerendering.h delete mode 100644 examples/gltfskinning/README.md delete mode 100644 examples/gltfskinning/gltfskinning.cpp delete mode 100644 examples/gltfskinning/gltfskinning.h delete mode 100644 examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp delete mode 100644 examples/hdr/hdr.cpp delete mode 100644 examples/hostimagecopy/hostimagecopy.cpp create mode 100644 examples/imgui/main_backup.cpp delete mode 100644 examples/indirectdraw/README.md delete mode 100644 examples/indirectdraw/indirectdraw.cpp delete mode 100644 examples/inlineuniformblocks/inlineuniformblocks.cpp delete mode 100644 examples/inputattachments/inputattachments.cpp delete mode 100644 examples/instancing/instancing.cpp delete mode 100644 examples/meshshader/meshshader.cpp delete mode 100644 examples/multisampling/multisampling.cpp delete mode 100644 examples/multithreading/multithreading.cpp delete mode 100644 examples/multiview/multiview.cpp delete mode 100644 examples/negativeviewportheight/negativeviewportheight.cpp delete mode 100644 examples/occlusionquery/occlusionquery.cpp delete mode 100644 examples/offscreen/offscreen.cpp delete mode 100644 examples/oit/oit.cpp delete mode 100644 examples/parallaxmapping/parallaxmapping.cpp delete mode 100644 examples/particlesystem/particlesystem.cpp delete mode 100644 examples/pbrbasic/pbrbasic.cpp delete mode 100644 examples/pbribl/pbribl.cpp delete mode 100644 examples/pbrtexture/pbrtexture.cpp delete mode 100644 examples/pipelines/pipelines.cpp delete mode 100644 examples/pipelinestatistics/pipelinestatistics.cpp delete mode 100644 examples/pushconstants/pushconstants.cpp delete mode 100644 examples/pushdescriptors/pushdescriptors.cpp delete mode 100644 examples/radialblur/radialblur.cpp delete mode 100644 examples/rayquery/rayquery.cpp delete mode 100644 examples/raytracingbasic/raytracingbasic.cpp delete mode 100644 examples/raytracingcallable/raytracingcallable.cpp delete mode 100644 examples/raytracinggltf/raytracinggltf.cpp delete mode 100644 examples/raytracingintersection/raytracingintersection.cpp delete mode 100644 examples/raytracingpositionfetch/raytracingpositionfetch.cpp delete mode 100644 examples/raytracingreflections/raytracingreflections.cpp delete mode 100644 examples/raytracingsbtdata/raytracingsbtdata.cpp delete mode 100644 examples/raytracingshadows/raytracingshadows.cpp delete mode 100644 examples/raytracingtextures/raytracingtextures.cpp delete mode 100644 examples/renderheadless/renderheadless.cpp delete mode 100644 examples/screenshot/screenshot.cpp delete mode 100644 examples/shaderobjects/shaderobjects.cpp delete mode 100644 examples/shadowmapping/shadowmapping.cpp delete mode 100644 examples/shadowmappingcascade/shadowmappingcascade.cpp delete mode 100644 examples/shadowmappingomni/shadowmappingomni.cpp delete mode 100644 examples/specializationconstants/specializationconstants.cpp delete mode 100644 examples/sphericalenvmapping/sphericalenvmapping.cpp delete mode 100644 examples/ssao/ssao.cpp delete mode 100644 examples/stencilbuffer/stencilbuffer.cpp delete mode 100644 examples/subpasses/subpasses.cpp delete mode 100644 examples/terraintessellation/terraintessellation.cpp delete mode 100644 examples/tessellation/tessellation.cpp delete mode 100644 examples/textoverlay/textoverlay.cpp delete mode 100644 examples/texture/texture.cpp delete mode 100644 examples/texture3d/texture3d.cpp delete mode 100644 examples/texturearray/texturearray.cpp delete mode 100644 examples/texturecubemap/texturecubemap.cpp delete mode 100644 examples/texturecubemaparray/texturecubemaparray.cpp delete mode 100644 examples/texturemipmapgen/README.md delete mode 100644 examples/texturemipmapgen/texturemipmapgen.cpp delete mode 100644 examples/texturesparseresidency/texturesparseresidency.cpp delete mode 100644 examples/texturesparseresidency/texturesparseresidency.h delete mode 100644 examples/timelinesemaphore/timelinesemaphore.cpp delete mode 100644 examples/triangle/triangle.cpp delete mode 100644 examples/trianglevulkan13/trianglevulkan13.cpp delete mode 100644 examples/validate_all.py delete mode 100644 examples/variablerateshading/variablerateshading.cpp delete mode 100644 examples/variablerateshading/variablerateshading.h delete mode 100644 examples/vertexattributes/README.md delete mode 100644 examples/vertexattributes/interleavedbuffer.png delete mode 100644 examples/vertexattributes/separatebuffers.png delete mode 100644 examples/vertexattributes/vertexattributes.cpp delete mode 100644 examples/vertexattributes/vertexattributes.h delete mode 100644 examples/viewportarray/viewportarray.cpp delete mode 100644 examples/vulkanscene/vulkanscene.cpp create mode 100644 external/ktx/other_include/other_include/KHR/khrplatform.h delete mode 100644 external/stb/stb_font_consolas_24_latin1.inl delete mode 100644 images/androidlogo.png delete mode 100644 images/applelogo.png delete mode 100644 images/linuxlogo.png delete mode 100644 images/vulkanlogo.png delete mode 100644 images/vulkanlogoscene.png delete mode 100644 images/windowslogo.png create mode 100644 imgui.ini delete mode 100644 screenshots/bloom.jpg delete mode 100644 screenshots/bufferdeviceaddress.jpg delete mode 100644 screenshots/computecloth.jpg delete mode 100644 screenshots/computecullandlod.jpg delete mode 100644 screenshots/computenbody.jpg delete mode 100644 screenshots/computeparticles.jpg delete mode 100644 screenshots/computeraytracing.jpg delete mode 100644 screenshots/computeshader.jpg delete mode 100644 screenshots/conditionalrender.jpg delete mode 100644 screenshots/conservativeraster.jpg delete mode 100644 screenshots/debugprintf.jpg delete mode 100644 screenshots/debugutils.jpg delete mode 100644 screenshots/deferred.jpg delete mode 100644 screenshots/deferredmultisampling.jpg delete mode 100644 screenshots/deferredshadows.jpg delete mode 100644 screenshots/descriptorbuffer.jpg delete mode 100644 screenshots/descriptorindexing.jpg delete mode 100644 screenshots/descriptorsets.jpg delete mode 100644 screenshots/displacement.jpg delete mode 100644 screenshots/distancefieldfonts.jpg delete mode 100644 screenshots/dynamicrendering.jpg delete mode 100644 screenshots/dynamicrenderingmultisampling.jpg delete mode 100644 screenshots/dynamicstate.jpg delete mode 100644 screenshots/dynamicuniformbuffer.jpg delete mode 100644 screenshots/ext_debugmarker.jpg delete mode 100644 screenshots/gears.jpg delete mode 100644 screenshots/geometryshader.jpg delete mode 100644 screenshots/gltfloading.jpg delete mode 100644 screenshots/gltfscenerendering.jpg delete mode 100644 screenshots/gltfskinning.jpg delete mode 100644 screenshots/graphicspipelinelibrary.jpg delete mode 100644 screenshots/hdr.jpg delete mode 100644 screenshots/hostimagecopy.jpg delete mode 100644 screenshots/imgui.jpg delete mode 100644 screenshots/indirectdraw.jpg delete mode 100644 screenshots/inlineuniformblocks.jpg delete mode 100644 screenshots/inputattachments.jpg delete mode 100644 screenshots/instancing.jpg delete mode 100644 screenshots/meshshader.jpg delete mode 100644 screenshots/multisampling.jpg delete mode 100644 screenshots/multithreading.jpg delete mode 100644 screenshots/multiview.jpg delete mode 100644 screenshots/negativeviewportheight.jpg delete mode 100644 screenshots/occlusionquery.jpg delete mode 100644 screenshots/offscreen.jpg delete mode 100644 screenshots/oit.jpg delete mode 100644 screenshots/parallaxmapping.jpg delete mode 100644 screenshots/particlesystem.jpg delete mode 100644 screenshots/pbrbasic.jpg delete mode 100644 screenshots/pbribl.jpg delete mode 100644 screenshots/pbrtexture.jpg delete mode 100644 screenshots/pipelines.jpg delete mode 100644 screenshots/pipelinestatistics.jpg delete mode 100644 screenshots/pushconstants.jpg delete mode 100644 screenshots/pushdescriptors.jpg delete mode 100644 screenshots/radialblur.jpg delete mode 100644 screenshots/rayquery.jpg delete mode 100644 screenshots/raytracingbasic.jpg delete mode 100644 screenshots/raytracingcallable.jpg delete mode 100644 screenshots/raytracinggltf.jpg delete mode 100644 screenshots/raytracingintersection.jpg delete mode 100644 screenshots/raytracingpositionfetch.jpg delete mode 100644 screenshots/raytracingreflections.jpg delete mode 100644 screenshots/raytracingsbtdata.jpg delete mode 100644 screenshots/raytracingshadows.jpg delete mode 100644 screenshots/raytracingtextures.jpg delete mode 100644 screenshots/screenshot.jpg delete mode 100644 screenshots/shaderobjects.jpg delete mode 100644 screenshots/shadowmapping.jpg delete mode 100644 screenshots/shadowmappingcascade.jpg delete mode 100644 screenshots/shadowmappingomni.jpg delete mode 100644 screenshots/specializationconstants.jpg delete mode 100644 screenshots/sphericalenvmapping.jpg delete mode 100644 screenshots/ssao.jpg delete mode 100644 screenshots/stencilbuffer.jpg delete mode 100644 screenshots/subpasses.jpg delete mode 100644 screenshots/terraintessellation.jpg delete mode 100644 screenshots/tessellation.jpg delete mode 100644 screenshots/textoverlay.jpg delete mode 100644 screenshots/texture.jpg delete mode 100644 screenshots/texture3d.jpg delete mode 100644 screenshots/texturearray.jpg delete mode 100644 screenshots/texturecubemap.jpg delete mode 100644 screenshots/texturecubemaparray.jpg delete mode 100644 screenshots/texturemipmapgen.jpg delete mode 100644 screenshots/texturesparseresidency.jpg delete mode 100644 screenshots/timelinesemaphore.jpg delete mode 100644 screenshots/triangle.jpg delete mode 100644 screenshots/trianglevulkan13.jpg delete mode 100644 screenshots/variablerateshading.jpg delete mode 100644 screenshots/vertexattributes.jpg delete mode 100644 screenshots/viewportarray.jpg delete mode 100644 screenshots/vulkanscene.jpg delete mode 100644 shaders/glsl/bloom/colorpass.frag delete mode 100644 shaders/glsl/bloom/colorpass.frag.spv delete mode 100644 shaders/glsl/bloom/colorpass.vert delete mode 100644 shaders/glsl/bloom/colorpass.vert.spv delete mode 100644 shaders/glsl/bloom/gaussblur.frag delete mode 100644 shaders/glsl/bloom/gaussblur.frag.spv delete mode 100644 shaders/glsl/bloom/gaussblur.vert delete mode 100644 shaders/glsl/bloom/gaussblur.vert.spv delete mode 100644 shaders/glsl/bloom/phongpass.frag delete mode 100644 shaders/glsl/bloom/phongpass.frag.spv delete mode 100644 shaders/glsl/bloom/phongpass.vert delete mode 100644 shaders/glsl/bloom/phongpass.vert.spv delete mode 100644 shaders/glsl/bloom/skybox.frag delete mode 100644 shaders/glsl/bloom/skybox.frag.spv delete mode 100644 shaders/glsl/bloom/skybox.vert delete mode 100644 shaders/glsl/bloom/skybox.vert.spv delete mode 100644 shaders/glsl/bufferdeviceaddress/cube.frag delete mode 100644 shaders/glsl/bufferdeviceaddress/cube.frag.spv delete mode 100644 shaders/glsl/bufferdeviceaddress/cube.vert delete mode 100644 shaders/glsl/bufferdeviceaddress/cube.vert.spv delete mode 100644 shaders/glsl/computecloth/cloth.comp delete mode 100644 shaders/glsl/computecloth/cloth.comp.spv delete mode 100644 shaders/glsl/computecloth/cloth.frag delete mode 100644 shaders/glsl/computecloth/cloth.frag.spv delete mode 100644 shaders/glsl/computecloth/cloth.vert delete mode 100644 shaders/glsl/computecloth/cloth.vert.spv delete mode 100644 shaders/glsl/computecloth/sphere.frag delete mode 100644 shaders/glsl/computecloth/sphere.frag.spv delete mode 100644 shaders/glsl/computecloth/sphere.vert delete mode 100644 shaders/glsl/computecloth/sphere.vert.spv delete mode 100644 shaders/glsl/computecullandlod/cull.comp delete mode 100644 shaders/glsl/computecullandlod/cull.comp.spv delete mode 100644 shaders/glsl/computecullandlod/indirectdraw.frag delete mode 100644 shaders/glsl/computecullandlod/indirectdraw.frag.spv delete mode 100644 shaders/glsl/computecullandlod/indirectdraw.vert delete mode 100644 shaders/glsl/computecullandlod/indirectdraw.vert.spv delete mode 100644 shaders/glsl/computeheadless/headless.comp delete mode 100644 shaders/glsl/computeheadless/headless.comp.spv delete mode 100644 shaders/glsl/computenbody/particle.frag delete mode 100644 shaders/glsl/computenbody/particle.frag.spv delete mode 100644 shaders/glsl/computenbody/particle.vert delete mode 100644 shaders/glsl/computenbody/particle.vert.spv delete mode 100644 shaders/glsl/computenbody/particle_calculate.comp delete mode 100644 shaders/glsl/computenbody/particle_calculate.comp.spv delete mode 100644 shaders/glsl/computenbody/particle_integrate.comp delete mode 100644 shaders/glsl/computenbody/particle_integrate.comp.spv delete mode 100644 shaders/glsl/computeparticles/particle.comp delete mode 100644 shaders/glsl/computeparticles/particle.comp.spv delete mode 100644 shaders/glsl/computeparticles/particle.frag delete mode 100644 shaders/glsl/computeparticles/particle.frag.spv delete mode 100644 shaders/glsl/computeparticles/particle.vert delete mode 100644 shaders/glsl/computeparticles/particle.vert.spv delete mode 100644 shaders/glsl/computeraytracing/raytracing.comp delete mode 100644 shaders/glsl/computeraytracing/raytracing.comp.spv delete mode 100644 shaders/glsl/computeraytracing/texture.frag delete mode 100644 shaders/glsl/computeraytracing/texture.frag.spv delete mode 100644 shaders/glsl/computeraytracing/texture.vert delete mode 100644 shaders/glsl/computeraytracing/texture.vert.spv delete mode 100644 shaders/glsl/computeshader/edgedetect.comp delete mode 100644 shaders/glsl/computeshader/edgedetect.comp.spv delete mode 100644 shaders/glsl/computeshader/emboss.comp delete mode 100644 shaders/glsl/computeshader/emboss.comp.spv delete mode 100644 shaders/glsl/computeshader/sharpen.comp delete mode 100644 shaders/glsl/computeshader/sharpen.comp.spv delete mode 100644 shaders/glsl/computeshader/texture.frag delete mode 100644 shaders/glsl/computeshader/texture.frag.spv delete mode 100644 shaders/glsl/computeshader/texture.vert delete mode 100644 shaders/glsl/computeshader/texture.vert.spv delete mode 100644 shaders/glsl/conditionalrender/model.frag delete mode 100644 shaders/glsl/conditionalrender/model.frag.spv delete mode 100644 shaders/glsl/conditionalrender/model.vert delete mode 100644 shaders/glsl/conditionalrender/model.vert.spv delete mode 100644 shaders/glsl/conservativeraster/fullscreen.frag delete mode 100644 shaders/glsl/conservativeraster/fullscreen.frag.spv delete mode 100644 shaders/glsl/conservativeraster/fullscreen.vert delete mode 100644 shaders/glsl/conservativeraster/fullscreen.vert.spv delete mode 100644 shaders/glsl/conservativeraster/triangle.frag delete mode 100644 shaders/glsl/conservativeraster/triangle.frag.spv delete mode 100644 shaders/glsl/conservativeraster/triangle.vert delete mode 100644 shaders/glsl/conservativeraster/triangle.vert.spv delete mode 100644 shaders/glsl/conservativeraster/triangleoverlay.frag delete mode 100644 shaders/glsl/conservativeraster/triangleoverlay.frag.spv delete mode 100644 shaders/glsl/debugprintf/toon.frag delete mode 100644 shaders/glsl/debugprintf/toon.frag.spv delete mode 100644 shaders/glsl/debugprintf/toon.vert delete mode 100644 shaders/glsl/debugprintf/toon.vert.spv delete mode 100644 shaders/glsl/debugutils/colorpass.frag delete mode 100644 shaders/glsl/debugutils/colorpass.frag.spv delete mode 100644 shaders/glsl/debugutils/colorpass.vert delete mode 100644 shaders/glsl/debugutils/colorpass.vert.spv delete mode 100644 shaders/glsl/debugutils/postprocess.frag delete mode 100644 shaders/glsl/debugutils/postprocess.frag.spv delete mode 100644 shaders/glsl/debugutils/postprocess.vert delete mode 100644 shaders/glsl/debugutils/postprocess.vert.spv delete mode 100644 shaders/glsl/debugutils/toon.frag delete mode 100644 shaders/glsl/debugutils/toon.frag.spv delete mode 100644 shaders/glsl/debugutils/toon.vert delete mode 100644 shaders/glsl/debugutils/toon.vert.spv delete mode 100644 shaders/glsl/deferred/deferred.frag delete mode 100644 shaders/glsl/deferred/deferred.frag.spv delete mode 100644 shaders/glsl/deferred/deferred.vert delete mode 100644 shaders/glsl/deferred/deferred.vert.spv delete mode 100644 shaders/glsl/deferred/mrt.frag delete mode 100644 shaders/glsl/deferred/mrt.frag.spv delete mode 100644 shaders/glsl/deferred/mrt.vert delete mode 100644 shaders/glsl/deferred/mrt.vert.spv delete mode 100644 shaders/glsl/deferredmultisampling/deferred.frag delete mode 100644 shaders/glsl/deferredmultisampling/deferred.frag.spv delete mode 100644 shaders/glsl/deferredmultisampling/deferred.vert delete mode 100644 shaders/glsl/deferredmultisampling/deferred.vert.spv delete mode 100644 shaders/glsl/deferredmultisampling/mrt.frag delete mode 100644 shaders/glsl/deferredmultisampling/mrt.frag.spv delete mode 100644 shaders/glsl/deferredmultisampling/mrt.vert delete mode 100644 shaders/glsl/deferredmultisampling/mrt.vert.spv delete mode 100644 shaders/glsl/deferredshadows/deferred.frag delete mode 100644 shaders/glsl/deferredshadows/deferred.frag.spv delete mode 100644 shaders/glsl/deferredshadows/deferred.vert delete mode 100644 shaders/glsl/deferredshadows/deferred.vert.spv delete mode 100644 shaders/glsl/deferredshadows/geom.spv delete mode 100644 shaders/glsl/deferredshadows/mrt.frag delete mode 100644 shaders/glsl/deferredshadows/mrt.frag.spv delete mode 100644 shaders/glsl/deferredshadows/mrt.vert delete mode 100644 shaders/glsl/deferredshadows/mrt.vert.spv delete mode 100644 shaders/glsl/deferredshadows/shadow.geom delete mode 100644 shaders/glsl/deferredshadows/shadow.geom.spv delete mode 100644 shaders/glsl/deferredshadows/shadow.vert delete mode 100644 shaders/glsl/deferredshadows/shadow.vert.spv delete mode 100644 shaders/glsl/descriptorbuffer/cube.frag delete mode 100644 shaders/glsl/descriptorbuffer/cube.frag.spv delete mode 100644 shaders/glsl/descriptorbuffer/cube.vert delete mode 100644 shaders/glsl/descriptorbuffer/cube.vert.spv delete mode 100644 shaders/glsl/descriptorindexing/descriptorindexing.frag delete mode 100644 shaders/glsl/descriptorindexing/descriptorindexing.frag.spv delete mode 100644 shaders/glsl/descriptorindexing/descriptorindexing.vert delete mode 100644 shaders/glsl/descriptorindexing/descriptorindexing.vert.spv delete mode 100644 shaders/glsl/descriptorsets/cube.frag delete mode 100644 shaders/glsl/descriptorsets/cube.frag.spv delete mode 100644 shaders/glsl/descriptorsets/cube.vert delete mode 100644 shaders/glsl/descriptorsets/cube.vert.spv delete mode 100644 shaders/glsl/displacement/base.frag delete mode 100644 shaders/glsl/displacement/base.frag.spv delete mode 100644 shaders/glsl/displacement/base.vert delete mode 100644 shaders/glsl/displacement/base.vert.spv delete mode 100644 shaders/glsl/displacement/displacement.tesc delete mode 100644 shaders/glsl/displacement/displacement.tesc.spv delete mode 100644 shaders/glsl/displacement/displacement.tese delete mode 100644 shaders/glsl/displacement/displacement.tese.spv delete mode 100644 shaders/glsl/distancefieldfonts/bitmap.frag delete mode 100644 shaders/glsl/distancefieldfonts/bitmap.frag.spv delete mode 100644 shaders/glsl/distancefieldfonts/bitmap.vert delete mode 100644 shaders/glsl/distancefieldfonts/bitmap.vert.spv delete mode 100644 shaders/glsl/distancefieldfonts/sdf.frag delete mode 100644 shaders/glsl/distancefieldfonts/sdf.frag.spv delete mode 100644 shaders/glsl/distancefieldfonts/sdf.vert delete mode 100644 shaders/glsl/distancefieldfonts/sdf.vert.spv delete mode 100644 shaders/glsl/dynamicrendering/texture.frag delete mode 100644 shaders/glsl/dynamicrendering/texture.frag.spv delete mode 100644 shaders/glsl/dynamicrendering/texture.vert delete mode 100644 shaders/glsl/dynamicrendering/texture.vert.spv delete mode 100644 shaders/glsl/dynamicuniformbuffer/base.frag delete mode 100644 shaders/glsl/dynamicuniformbuffer/base.frag.spv delete mode 100644 shaders/glsl/dynamicuniformbuffer/base.vert delete mode 100644 shaders/glsl/dynamicuniformbuffer/base.vert.spv delete mode 100644 shaders/glsl/gears/gears.frag delete mode 100644 shaders/glsl/gears/gears.frag.spv delete mode 100644 shaders/glsl/gears/gears.vert delete mode 100644 shaders/glsl/gears/gears.vert.spv delete mode 100644 shaders/glsl/geometryshader/base.frag delete mode 100644 shaders/glsl/geometryshader/base.frag.spv delete mode 100644 shaders/glsl/geometryshader/base.vert delete mode 100644 shaders/glsl/geometryshader/base.vert.spv delete mode 100644 shaders/glsl/geometryshader/mesh.frag delete mode 100644 shaders/glsl/geometryshader/mesh.frag.spv delete mode 100644 shaders/glsl/geometryshader/mesh.vert delete mode 100644 shaders/glsl/geometryshader/mesh.vert.spv delete mode 100644 shaders/glsl/geometryshader/normaldebug.geom delete mode 100644 shaders/glsl/geometryshader/normaldebug.geom.spv delete mode 100644 shaders/glsl/gltfloading/mesh.frag delete mode 100644 shaders/glsl/gltfloading/mesh.frag.spv delete mode 100644 shaders/glsl/gltfloading/mesh.vert delete mode 100644 shaders/glsl/gltfloading/mesh.vert.spv delete mode 100644 shaders/glsl/gltfscenerendering/scene.frag delete mode 100644 shaders/glsl/gltfscenerendering/scene.frag.spv delete mode 100644 shaders/glsl/gltfscenerendering/scene.vert delete mode 100644 shaders/glsl/gltfscenerendering/scene.vert.spv delete mode 100644 shaders/glsl/gltfskinning/skinnedmodel.frag delete mode 100644 shaders/glsl/gltfskinning/skinnedmodel.frag.spv delete mode 100644 shaders/glsl/gltfskinning/skinnedmodel.vert delete mode 100644 shaders/glsl/gltfskinning/skinnedmodel.vert.spv delete mode 100644 shaders/glsl/graphicspipelinelibrary/shared.vert delete mode 100644 shaders/glsl/graphicspipelinelibrary/shared.vert.spv delete mode 100644 shaders/glsl/graphicspipelinelibrary/uber.frag delete mode 100644 shaders/glsl/graphicspipelinelibrary/uber.frag.spv delete mode 100644 shaders/glsl/hdr/bloom.frag delete mode 100644 shaders/glsl/hdr/bloom.frag.spv delete mode 100644 shaders/glsl/hdr/bloom.vert delete mode 100644 shaders/glsl/hdr/bloom.vert.spv delete mode 100644 shaders/glsl/hdr/composition.frag delete mode 100644 shaders/glsl/hdr/composition.frag.spv delete mode 100644 shaders/glsl/hdr/composition.vert delete mode 100644 shaders/glsl/hdr/composition.vert.spv delete mode 100644 shaders/glsl/hdr/gbuffer.frag delete mode 100644 shaders/glsl/hdr/gbuffer.frag.spv delete mode 100644 shaders/glsl/hdr/gbuffer.vert delete mode 100644 shaders/glsl/hdr/gbuffer.vert.spv delete mode 100644 shaders/glsl/indirectdraw/ground.frag delete mode 100644 shaders/glsl/indirectdraw/ground.frag.spv delete mode 100644 shaders/glsl/indirectdraw/ground.vert delete mode 100644 shaders/glsl/indirectdraw/ground.vert.spv delete mode 100644 shaders/glsl/indirectdraw/indirectdraw.frag delete mode 100644 shaders/glsl/indirectdraw/indirectdraw.frag.spv delete mode 100644 shaders/glsl/indirectdraw/indirectdraw.vert delete mode 100644 shaders/glsl/indirectdraw/indirectdraw.vert.spv delete mode 100644 shaders/glsl/indirectdraw/skysphere.frag delete mode 100644 shaders/glsl/indirectdraw/skysphere.frag.spv delete mode 100644 shaders/glsl/indirectdraw/skysphere.vert delete mode 100644 shaders/glsl/indirectdraw/skysphere.vert.spv delete mode 100644 shaders/glsl/inlineuniformblocks/pbr.frag delete mode 100644 shaders/glsl/inlineuniformblocks/pbr.frag.spv delete mode 100644 shaders/glsl/inlineuniformblocks/pbr.vert delete mode 100644 shaders/glsl/inlineuniformblocks/pbr.vert.spv delete mode 100644 shaders/glsl/inputattachments/attachmentread.frag delete mode 100644 shaders/glsl/inputattachments/attachmentread.frag.spv delete mode 100644 shaders/glsl/inputattachments/attachmentread.vert delete mode 100644 shaders/glsl/inputattachments/attachmentread.vert.spv delete mode 100644 shaders/glsl/inputattachments/attachmentwrite.frag delete mode 100644 shaders/glsl/inputattachments/attachmentwrite.frag.spv delete mode 100644 shaders/glsl/inputattachments/attachmentwrite.vert delete mode 100644 shaders/glsl/inputattachments/attachmentwrite.vert.spv delete mode 100644 shaders/glsl/instancing/instancing.frag delete mode 100644 shaders/glsl/instancing/instancing.frag.spv delete mode 100644 shaders/glsl/instancing/instancing.vert delete mode 100644 shaders/glsl/instancing/instancing.vert.spv delete mode 100644 shaders/glsl/instancing/planet.frag delete mode 100644 shaders/glsl/instancing/planet.frag.spv delete mode 100644 shaders/glsl/instancing/planet.vert delete mode 100644 shaders/glsl/instancing/planet.vert.spv delete mode 100644 shaders/glsl/instancing/starfield.frag delete mode 100644 shaders/glsl/instancing/starfield.frag.spv delete mode 100644 shaders/glsl/instancing/starfield.vert delete mode 100644 shaders/glsl/instancing/starfield.vert.spv delete mode 100644 shaders/glsl/meshshader/meshshader.frag delete mode 100644 shaders/glsl/meshshader/meshshader.frag.spv delete mode 100644 shaders/glsl/meshshader/meshshader.mesh delete mode 100644 shaders/glsl/meshshader/meshshader.mesh.spv delete mode 100644 shaders/glsl/meshshader/meshshader.task delete mode 100644 shaders/glsl/meshshader/meshshader.task.spv delete mode 100644 shaders/glsl/multisampling/mesh.frag delete mode 100644 shaders/glsl/multisampling/mesh.frag.spv delete mode 100644 shaders/glsl/multisampling/mesh.vert delete mode 100644 shaders/glsl/multisampling/mesh.vert.spv delete mode 100644 shaders/glsl/multithreading/phong.frag delete mode 100644 shaders/glsl/multithreading/phong.frag.spv delete mode 100644 shaders/glsl/multithreading/phong.vert delete mode 100644 shaders/glsl/multithreading/phong.vert.spv delete mode 100644 shaders/glsl/multithreading/starsphere.frag delete mode 100644 shaders/glsl/multithreading/starsphere.frag.spv delete mode 100644 shaders/glsl/multithreading/starsphere.vert delete mode 100644 shaders/glsl/multithreading/starsphere.vert.spv delete mode 100644 shaders/glsl/multiview/multiview.frag delete mode 100644 shaders/glsl/multiview/multiview.frag.spv delete mode 100644 shaders/glsl/multiview/multiview.vert delete mode 100644 shaders/glsl/multiview/multiview.vert.spv delete mode 100644 shaders/glsl/multiview/viewdisplay.frag delete mode 100644 shaders/glsl/multiview/viewdisplay.frag.spv delete mode 100644 shaders/glsl/multiview/viewdisplay.vert delete mode 100644 shaders/glsl/multiview/viewdisplay.vert.spv delete mode 100644 shaders/glsl/negativeviewportheight/quad.frag delete mode 100644 shaders/glsl/negativeviewportheight/quad.frag.spv delete mode 100644 shaders/glsl/negativeviewportheight/quad.vert delete mode 100644 shaders/glsl/negativeviewportheight/quad.vert.spv delete mode 100644 shaders/glsl/occlusionquery/mesh.frag delete mode 100644 shaders/glsl/occlusionquery/mesh.frag.spv delete mode 100644 shaders/glsl/occlusionquery/mesh.vert delete mode 100644 shaders/glsl/occlusionquery/mesh.vert.spv delete mode 100644 shaders/glsl/occlusionquery/occluder.frag delete mode 100644 shaders/glsl/occlusionquery/occluder.frag.spv delete mode 100644 shaders/glsl/occlusionquery/occluder.vert delete mode 100644 shaders/glsl/occlusionquery/occluder.vert.spv delete mode 100644 shaders/glsl/occlusionquery/simple.frag delete mode 100644 shaders/glsl/occlusionquery/simple.frag.spv delete mode 100644 shaders/glsl/occlusionquery/simple.vert delete mode 100644 shaders/glsl/occlusionquery/simple.vert.spv delete mode 100644 shaders/glsl/offscreen/mirror.frag delete mode 100644 shaders/glsl/offscreen/mirror.frag.spv delete mode 100644 shaders/glsl/offscreen/mirror.vert delete mode 100644 shaders/glsl/offscreen/mirror.vert.spv delete mode 100644 shaders/glsl/offscreen/phong.frag delete mode 100644 shaders/glsl/offscreen/phong.frag.spv delete mode 100644 shaders/glsl/offscreen/phong.vert delete mode 100644 shaders/glsl/offscreen/phong.vert.spv delete mode 100644 shaders/glsl/offscreen/quad.frag delete mode 100644 shaders/glsl/offscreen/quad.frag.spv delete mode 100644 shaders/glsl/offscreen/quad.vert delete mode 100644 shaders/glsl/offscreen/quad.vert.spv delete mode 100644 shaders/glsl/oit/color.frag delete mode 100644 shaders/glsl/oit/color.frag.spv delete mode 100644 shaders/glsl/oit/color.vert delete mode 100644 shaders/glsl/oit/color.vert.spv delete mode 100644 shaders/glsl/oit/geometry.frag delete mode 100644 shaders/glsl/oit/geometry.frag.spv delete mode 100644 shaders/glsl/oit/geometry.vert delete mode 100644 shaders/glsl/oit/geometry.vert.spv delete mode 100644 shaders/glsl/parallaxmapping/parallax.frag delete mode 100644 shaders/glsl/parallaxmapping/parallax.frag.spv delete mode 100644 shaders/glsl/parallaxmapping/parallax.vert delete mode 100644 shaders/glsl/parallaxmapping/parallax.vert.spv delete mode 100644 shaders/glsl/particlesystem/normalmap.frag delete mode 100644 shaders/glsl/particlesystem/normalmap.frag.spv delete mode 100644 shaders/glsl/particlesystem/normalmap.vert delete mode 100644 shaders/glsl/particlesystem/normalmap.vert.spv delete mode 100644 shaders/glsl/particlesystem/particle.frag delete mode 100644 shaders/glsl/particlesystem/particle.frag.spv delete mode 100644 shaders/glsl/particlesystem/particle.vert delete mode 100644 shaders/glsl/particlesystem/particle.vert.spv delete mode 100644 shaders/glsl/pbrbasic/pbr.frag delete mode 100644 shaders/glsl/pbrbasic/pbr.frag.spv delete mode 100644 shaders/glsl/pbrbasic/pbr.vert delete mode 100644 shaders/glsl/pbrbasic/pbr.vert.spv delete mode 100644 shaders/glsl/pbribl/filtercube.vert delete mode 100644 shaders/glsl/pbribl/filtercube.vert.spv delete mode 100644 shaders/glsl/pbribl/genbrdflut.frag delete mode 100644 shaders/glsl/pbribl/genbrdflut.frag.spv delete mode 100644 shaders/glsl/pbribl/genbrdflut.vert delete mode 100644 shaders/glsl/pbribl/genbrdflut.vert.spv delete mode 100644 shaders/glsl/pbribl/irradiancecube.frag delete mode 100644 shaders/glsl/pbribl/irradiancecube.frag.spv delete mode 100644 shaders/glsl/pbribl/pbribl.frag delete mode 100644 shaders/glsl/pbribl/pbribl.frag.spv delete mode 100644 shaders/glsl/pbribl/pbribl.vert delete mode 100644 shaders/glsl/pbribl/pbribl.vert.spv delete mode 100644 shaders/glsl/pbribl/prefilterenvmap.frag delete mode 100644 shaders/glsl/pbribl/prefilterenvmap.frag.spv delete mode 100644 shaders/glsl/pbribl/skybox.frag delete mode 100644 shaders/glsl/pbribl/skybox.frag.spv delete mode 100644 shaders/glsl/pbribl/skybox.vert delete mode 100644 shaders/glsl/pbribl/skybox.vert.spv delete mode 100644 shaders/glsl/pbrtexture/filtercube.vert delete mode 100644 shaders/glsl/pbrtexture/filtercube.vert.spv delete mode 100644 shaders/glsl/pbrtexture/genbrdflut.frag delete mode 100644 shaders/glsl/pbrtexture/genbrdflut.frag.spv delete mode 100644 shaders/glsl/pbrtexture/genbrdflut.vert delete mode 100644 shaders/glsl/pbrtexture/genbrdflut.vert.spv delete mode 100644 shaders/glsl/pbrtexture/irradiancecube.frag delete mode 100644 shaders/glsl/pbrtexture/irradiancecube.frag.spv delete mode 100644 shaders/glsl/pbrtexture/pbrtexture.frag delete mode 100644 shaders/glsl/pbrtexture/pbrtexture.frag.spv delete mode 100644 shaders/glsl/pbrtexture/pbrtexture.vert delete mode 100644 shaders/glsl/pbrtexture/pbrtexture.vert.spv delete mode 100644 shaders/glsl/pbrtexture/prefilterenvmap.frag delete mode 100644 shaders/glsl/pbrtexture/prefilterenvmap.frag.spv delete mode 100644 shaders/glsl/pbrtexture/skybox.frag delete mode 100644 shaders/glsl/pbrtexture/skybox.frag.spv delete mode 100644 shaders/glsl/pbrtexture/skybox.vert delete mode 100644 shaders/glsl/pbrtexture/skybox.vert.spv delete mode 100644 shaders/glsl/pipelines/phong.frag delete mode 100644 shaders/glsl/pipelines/phong.frag.spv delete mode 100644 shaders/glsl/pipelines/phong.vert delete mode 100644 shaders/glsl/pipelines/phong.vert.spv delete mode 100644 shaders/glsl/pipelines/toon.frag delete mode 100644 shaders/glsl/pipelines/toon.frag.spv delete mode 100644 shaders/glsl/pipelines/toon.vert delete mode 100644 shaders/glsl/pipelines/toon.vert.spv delete mode 100644 shaders/glsl/pipelines/wireframe.frag delete mode 100644 shaders/glsl/pipelines/wireframe.frag.spv delete mode 100644 shaders/glsl/pipelines/wireframe.vert delete mode 100644 shaders/glsl/pipelines/wireframe.vert.spv delete mode 100644 shaders/glsl/pipelinestatistics/scene.frag delete mode 100644 shaders/glsl/pipelinestatistics/scene.frag.spv delete mode 100644 shaders/glsl/pipelinestatistics/scene.tesc delete mode 100644 shaders/glsl/pipelinestatistics/scene.tesc.spv delete mode 100644 shaders/glsl/pipelinestatistics/scene.tese delete mode 100644 shaders/glsl/pipelinestatistics/scene.tese.spv delete mode 100644 shaders/glsl/pipelinestatistics/scene.vert delete mode 100644 shaders/glsl/pipelinestatistics/scene.vert.spv delete mode 100644 shaders/glsl/pushconstants/pushconstants.frag delete mode 100644 shaders/glsl/pushconstants/pushconstants.frag.spv delete mode 100644 shaders/glsl/pushconstants/pushconstants.vert delete mode 100644 shaders/glsl/pushconstants/pushconstants.vert.spv delete mode 100644 shaders/glsl/pushdescriptors/cube.frag delete mode 100644 shaders/glsl/pushdescriptors/cube.frag.spv delete mode 100644 shaders/glsl/pushdescriptors/cube.vert delete mode 100644 shaders/glsl/pushdescriptors/cube.vert.spv delete mode 100644 shaders/glsl/radialblur/colorpass.frag delete mode 100644 shaders/glsl/radialblur/colorpass.frag.spv delete mode 100644 shaders/glsl/radialblur/colorpass.vert delete mode 100644 shaders/glsl/radialblur/colorpass.vert.spv delete mode 100644 shaders/glsl/radialblur/phongpass.frag delete mode 100644 shaders/glsl/radialblur/phongpass.frag.spv delete mode 100644 shaders/glsl/radialblur/phongpass.vert delete mode 100644 shaders/glsl/radialblur/phongpass.vert.spv delete mode 100644 shaders/glsl/radialblur/radialblur.frag delete mode 100644 shaders/glsl/radialblur/radialblur.frag.spv delete mode 100644 shaders/glsl/radialblur/radialblur.vert delete mode 100644 shaders/glsl/radialblur/radialblur.vert.spv delete mode 100644 shaders/glsl/rayquery/scene.frag delete mode 100644 shaders/glsl/rayquery/scene.frag.spv delete mode 100644 shaders/glsl/rayquery/scene.vert delete mode 100644 shaders/glsl/rayquery/scene.vert.spv delete mode 100644 shaders/glsl/raytracingbasic/closesthit.rchit delete mode 100644 shaders/glsl/raytracingbasic/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingbasic/miss.rmiss delete mode 100644 shaders/glsl/raytracingbasic/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingbasic/raygen.rgen delete mode 100644 shaders/glsl/raytracingbasic/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingcallable/callable1.rcall delete mode 100644 shaders/glsl/raytracingcallable/callable1.rcall.spv delete mode 100644 shaders/glsl/raytracingcallable/callable2.rcall delete mode 100644 shaders/glsl/raytracingcallable/callable2.rcall.spv delete mode 100644 shaders/glsl/raytracingcallable/callable3.rcall delete mode 100644 shaders/glsl/raytracingcallable/callable3.rcall.spv delete mode 100644 shaders/glsl/raytracingcallable/closesthit.rchit delete mode 100644 shaders/glsl/raytracingcallable/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingcallable/miss.rmiss delete mode 100644 shaders/glsl/raytracingcallable/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingcallable/raygen.rgen delete mode 100644 shaders/glsl/raytracingcallable/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracinggltf/anyhit.rahit delete mode 100644 shaders/glsl/raytracinggltf/anyhit.rahit.spv delete mode 100644 shaders/glsl/raytracinggltf/bufferreferences.glsl delete mode 100644 shaders/glsl/raytracinggltf/closesthit.rchit delete mode 100644 shaders/glsl/raytracinggltf/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracinggltf/geometrytypes.glsl delete mode 100644 shaders/glsl/raytracinggltf/miss.rmiss delete mode 100644 shaders/glsl/raytracinggltf/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracinggltf/random.glsl delete mode 100644 shaders/glsl/raytracinggltf/raygen.rgen delete mode 100644 shaders/glsl/raytracinggltf/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracinggltf/shadow.rmiss delete mode 100644 shaders/glsl/raytracinggltf/shadow.rmiss.spv delete mode 100644 shaders/glsl/raytracingintersection/closesthit.rchit delete mode 100644 shaders/glsl/raytracingintersection/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingintersection/intersection.rint delete mode 100644 shaders/glsl/raytracingintersection/intersection.rint.spv delete mode 100644 shaders/glsl/raytracingintersection/miss.rmiss delete mode 100644 shaders/glsl/raytracingintersection/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingintersection/raygen.rgen delete mode 100644 shaders/glsl/raytracingintersection/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingpositionfetch/closesthit.rchit delete mode 100644 shaders/glsl/raytracingpositionfetch/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingpositionfetch/miss.rmiss delete mode 100644 shaders/glsl/raytracingpositionfetch/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingpositionfetch/raygen.rgen delete mode 100644 shaders/glsl/raytracingpositionfetch/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingreflections/closesthit.rchit delete mode 100644 shaders/glsl/raytracingreflections/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingreflections/miss.rmiss delete mode 100644 shaders/glsl/raytracingreflections/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingreflections/raygen.rgen delete mode 100644 shaders/glsl/raytracingreflections/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingsbtdata/closesthit.rchit delete mode 100644 shaders/glsl/raytracingsbtdata/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingsbtdata/miss.rmiss delete mode 100644 shaders/glsl/raytracingsbtdata/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingsbtdata/raygen.rgen delete mode 100644 shaders/glsl/raytracingsbtdata/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingshadows/closesthit.rchit delete mode 100644 shaders/glsl/raytracingshadows/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingshadows/miss.rmiss delete mode 100644 shaders/glsl/raytracingshadows/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingshadows/raygen.rgen delete mode 100644 shaders/glsl/raytracingshadows/raygen.rgen.spv delete mode 100644 shaders/glsl/raytracingshadows/shadow.rmiss delete mode 100644 shaders/glsl/raytracingshadows/shadow.rmiss.spv delete mode 100644 shaders/glsl/raytracingtextures/anyhit.rahit delete mode 100644 shaders/glsl/raytracingtextures/anyhit.rahit.spv delete mode 100644 shaders/glsl/raytracingtextures/bufferreferences.glsl delete mode 100644 shaders/glsl/raytracingtextures/closesthit.rchit delete mode 100644 shaders/glsl/raytracingtextures/closesthit.rchit.spv delete mode 100644 shaders/glsl/raytracingtextures/geometrytypes.glsl delete mode 100644 shaders/glsl/raytracingtextures/miss.rmiss delete mode 100644 shaders/glsl/raytracingtextures/miss.rmiss.spv delete mode 100644 shaders/glsl/raytracingtextures/raygen.rgen delete mode 100644 shaders/glsl/raytracingtextures/raygen.rgen.spv delete mode 100644 shaders/glsl/renderheadless/triangle.frag delete mode 100644 shaders/glsl/renderheadless/triangle.frag.spv delete mode 100644 shaders/glsl/renderheadless/triangle.vert delete mode 100644 shaders/glsl/renderheadless/triangle.vert.spv delete mode 100644 shaders/glsl/screenshot/mesh.frag delete mode 100644 shaders/glsl/screenshot/mesh.frag.spv delete mode 100644 shaders/glsl/screenshot/mesh.vert delete mode 100644 shaders/glsl/screenshot/mesh.vert.spv delete mode 100644 shaders/glsl/shaderobjects/phong.frag delete mode 100644 shaders/glsl/shaderobjects/phong.frag.spv delete mode 100644 shaders/glsl/shaderobjects/phong.vert delete mode 100644 shaders/glsl/shaderobjects/phong.vert.spv delete mode 100644 shaders/glsl/shadowmapping/offscreen.frag delete mode 100644 shaders/glsl/shadowmapping/offscreen.frag.spv delete mode 100644 shaders/glsl/shadowmapping/offscreen.vert delete mode 100644 shaders/glsl/shadowmapping/offscreen.vert.spv delete mode 100644 shaders/glsl/shadowmapping/quad.frag delete mode 100644 shaders/glsl/shadowmapping/quad.frag.spv delete mode 100644 shaders/glsl/shadowmapping/quad.vert delete mode 100644 shaders/glsl/shadowmapping/quad.vert.spv delete mode 100644 shaders/glsl/shadowmapping/scene.frag delete mode 100644 shaders/glsl/shadowmapping/scene.frag.spv delete mode 100644 shaders/glsl/shadowmapping/scene.vert delete mode 100644 shaders/glsl/shadowmapping/scene.vert.spv delete mode 100644 shaders/glsl/shadowmappingcascade/debugshadowmap.frag delete mode 100644 shaders/glsl/shadowmappingcascade/debugshadowmap.frag.spv delete mode 100644 shaders/glsl/shadowmappingcascade/debugshadowmap.vert delete mode 100644 shaders/glsl/shadowmappingcascade/debugshadowmap.vert.spv delete mode 100644 shaders/glsl/shadowmappingcascade/depthpass.frag delete mode 100644 shaders/glsl/shadowmappingcascade/depthpass.frag.spv delete mode 100644 shaders/glsl/shadowmappingcascade/depthpass.vert delete mode 100644 shaders/glsl/shadowmappingcascade/depthpass.vert.spv delete mode 100644 shaders/glsl/shadowmappingcascade/scene.frag delete mode 100644 shaders/glsl/shadowmappingcascade/scene.frag.spv delete mode 100644 shaders/glsl/shadowmappingcascade/scene.vert delete mode 100644 shaders/glsl/shadowmappingcascade/scene.vert.spv delete mode 100644 shaders/glsl/shadowmappingomni/cubemapdisplay.frag delete mode 100644 shaders/glsl/shadowmappingomni/cubemapdisplay.frag.spv delete mode 100644 shaders/glsl/shadowmappingomni/cubemapdisplay.vert delete mode 100644 shaders/glsl/shadowmappingomni/cubemapdisplay.vert.spv delete mode 100644 shaders/glsl/shadowmappingomni/offscreen.frag delete mode 100644 shaders/glsl/shadowmappingomni/offscreen.frag.spv delete mode 100644 shaders/glsl/shadowmappingomni/offscreen.vert delete mode 100644 shaders/glsl/shadowmappingomni/offscreen.vert.spv delete mode 100644 shaders/glsl/shadowmappingomni/scene.frag delete mode 100644 shaders/glsl/shadowmappingomni/scene.frag.spv delete mode 100644 shaders/glsl/shadowmappingomni/scene.vert delete mode 100644 shaders/glsl/shadowmappingomni/scene.vert.spv delete mode 100644 shaders/glsl/specializationconstants/uber.frag delete mode 100644 shaders/glsl/specializationconstants/uber.frag.spv delete mode 100644 shaders/glsl/specializationconstants/uber.vert delete mode 100644 shaders/glsl/specializationconstants/uber.vert.spv delete mode 100644 shaders/glsl/sphericalenvmapping/sem.frag delete mode 100644 shaders/glsl/sphericalenvmapping/sem.frag.spv delete mode 100644 shaders/glsl/sphericalenvmapping/sem.vert delete mode 100644 shaders/glsl/sphericalenvmapping/sem.vert.spv delete mode 100644 shaders/glsl/ssao/blur.frag delete mode 100644 shaders/glsl/ssao/blur.frag.spv delete mode 100644 shaders/glsl/ssao/composition.frag delete mode 100644 shaders/glsl/ssao/composition.frag.spv delete mode 100644 shaders/glsl/ssao/fullscreen.vert delete mode 100644 shaders/glsl/ssao/fullscreen.vert.spv delete mode 100644 shaders/glsl/ssao/gbuffer.frag delete mode 100644 shaders/glsl/ssao/gbuffer.frag.spv delete mode 100644 shaders/glsl/ssao/gbuffer.vert delete mode 100644 shaders/glsl/ssao/gbuffer.vert.spv delete mode 100644 shaders/glsl/ssao/ssao.frag delete mode 100644 shaders/glsl/ssao/ssao.frag.spv delete mode 100644 shaders/glsl/stencilbuffer/outline.frag delete mode 100644 shaders/glsl/stencilbuffer/outline.frag.spv delete mode 100644 shaders/glsl/stencilbuffer/outline.vert delete mode 100644 shaders/glsl/stencilbuffer/outline.vert.spv delete mode 100644 shaders/glsl/stencilbuffer/toon.frag delete mode 100644 shaders/glsl/stencilbuffer/toon.frag.spv delete mode 100644 shaders/glsl/stencilbuffer/toon.vert delete mode 100644 shaders/glsl/stencilbuffer/toon.vert.spv delete mode 100644 shaders/glsl/subpasses/composition.frag delete mode 100644 shaders/glsl/subpasses/composition.frag.spv delete mode 100644 shaders/glsl/subpasses/composition.vert delete mode 100644 shaders/glsl/subpasses/composition.vert.spv delete mode 100644 shaders/glsl/subpasses/gbuffer.frag delete mode 100644 shaders/glsl/subpasses/gbuffer.frag.spv delete mode 100644 shaders/glsl/subpasses/gbuffer.vert delete mode 100644 shaders/glsl/subpasses/gbuffer.vert.spv delete mode 100644 shaders/glsl/subpasses/transparent.frag delete mode 100644 shaders/glsl/subpasses/transparent.frag.spv delete mode 100644 shaders/glsl/subpasses/transparent.vert delete mode 100644 shaders/glsl/subpasses/transparent.vert.spv delete mode 100644 shaders/glsl/terraintessellation/skysphere.frag delete mode 100644 shaders/glsl/terraintessellation/skysphere.frag.spv delete mode 100644 shaders/glsl/terraintessellation/skysphere.vert delete mode 100644 shaders/glsl/terraintessellation/skysphere.vert.spv delete mode 100644 shaders/glsl/terraintessellation/terrain.frag delete mode 100644 shaders/glsl/terraintessellation/terrain.frag.spv delete mode 100644 shaders/glsl/terraintessellation/terrain.tesc delete mode 100644 shaders/glsl/terraintessellation/terrain.tesc.spv delete mode 100644 shaders/glsl/terraintessellation/terrain.tese delete mode 100644 shaders/glsl/terraintessellation/terrain.tese.spv delete mode 100644 shaders/glsl/terraintessellation/terrain.vert delete mode 100644 shaders/glsl/terraintessellation/terrain.vert.spv delete mode 100644 shaders/glsl/tessellation/base.frag delete mode 100644 shaders/glsl/tessellation/base.frag.spv delete mode 100644 shaders/glsl/tessellation/base.vert delete mode 100644 shaders/glsl/tessellation/base.vert.spv delete mode 100644 shaders/glsl/tessellation/passthrough.tesc delete mode 100644 shaders/glsl/tessellation/passthrough.tesc.spv delete mode 100644 shaders/glsl/tessellation/passthrough.tese delete mode 100644 shaders/glsl/tessellation/passthrough.tese.spv delete mode 100644 shaders/glsl/tessellation/pntriangles.tesc delete mode 100644 shaders/glsl/tessellation/pntriangles.tesc.spv delete mode 100644 shaders/glsl/tessellation/pntriangles.tese delete mode 100644 shaders/glsl/tessellation/pntriangles.tese.spv delete mode 100644 shaders/glsl/textoverlay/mesh.frag delete mode 100644 shaders/glsl/textoverlay/mesh.frag.spv delete mode 100644 shaders/glsl/textoverlay/mesh.vert delete mode 100644 shaders/glsl/textoverlay/mesh.vert.spv delete mode 100644 shaders/glsl/textoverlay/text.frag delete mode 100644 shaders/glsl/textoverlay/text.frag.spv delete mode 100644 shaders/glsl/textoverlay/text.vert delete mode 100644 shaders/glsl/textoverlay/text.vert.spv delete mode 100644 shaders/glsl/texture/texture.frag delete mode 100644 shaders/glsl/texture/texture.frag.spv delete mode 100644 shaders/glsl/texture/texture.vert delete mode 100644 shaders/glsl/texture/texture.vert.spv delete mode 100644 shaders/glsl/texture3d/texture3d.frag delete mode 100644 shaders/glsl/texture3d/texture3d.frag.spv delete mode 100644 shaders/glsl/texture3d/texture3d.vert delete mode 100644 shaders/glsl/texture3d/texture3d.vert.spv delete mode 100644 shaders/glsl/texturearray/instancing.frag delete mode 100644 shaders/glsl/texturearray/instancing.frag.spv delete mode 100644 shaders/glsl/texturearray/instancing.vert delete mode 100644 shaders/glsl/texturearray/instancing.vert.spv delete mode 100644 shaders/glsl/texturecubemap/reflect.frag delete mode 100644 shaders/glsl/texturecubemap/reflect.frag.spv delete mode 100644 shaders/glsl/texturecubemap/reflect.vert delete mode 100644 shaders/glsl/texturecubemap/reflect.vert.spv delete mode 100644 shaders/glsl/texturecubemap/skybox.frag delete mode 100644 shaders/glsl/texturecubemap/skybox.frag.spv delete mode 100644 shaders/glsl/texturecubemap/skybox.vert delete mode 100644 shaders/glsl/texturecubemap/skybox.vert.spv delete mode 100644 shaders/glsl/texturecubemaparray/reflect.frag delete mode 100644 shaders/glsl/texturecubemaparray/reflect.frag.spv delete mode 100644 shaders/glsl/texturecubemaparray/reflect.vert delete mode 100644 shaders/glsl/texturecubemaparray/reflect.vert.spv delete mode 100644 shaders/glsl/texturecubemaparray/skybox.frag delete mode 100644 shaders/glsl/texturecubemaparray/skybox.frag.spv delete mode 100644 shaders/glsl/texturecubemaparray/skybox.vert delete mode 100644 shaders/glsl/texturecubemaparray/skybox.vert.spv delete mode 100644 shaders/glsl/texturemipmapgen/texture.frag delete mode 100644 shaders/glsl/texturemipmapgen/texture.frag.spv delete mode 100644 shaders/glsl/texturemipmapgen/texture.vert delete mode 100644 shaders/glsl/texturemipmapgen/texture.vert.spv delete mode 100644 shaders/glsl/texturesparseresidency/sparseresidency.frag delete mode 100644 shaders/glsl/texturesparseresidency/sparseresidency.frag.spv delete mode 100644 shaders/glsl/texturesparseresidency/sparseresidency.vert delete mode 100644 shaders/glsl/texturesparseresidency/sparseresidency.vert.spv delete mode 100644 shaders/glsl/triangle/triangle.frag delete mode 100644 shaders/glsl/triangle/triangle.frag.spv delete mode 100644 shaders/glsl/triangle/triangle.vert delete mode 100644 shaders/glsl/triangle/triangle.vert.spv delete mode 100644 shaders/glsl/variablerateshading/scene.frag delete mode 100644 shaders/glsl/variablerateshading/scene.frag.spv delete mode 100644 shaders/glsl/variablerateshading/scene.vert delete mode 100644 shaders/glsl/variablerateshading/scene.vert.spv delete mode 100644 shaders/glsl/vertexattributes/scene.frag delete mode 100644 shaders/glsl/vertexattributes/scene.frag.spv delete mode 100644 shaders/glsl/vertexattributes/scene.vert delete mode 100644 shaders/glsl/vertexattributes/scene.vert.spv delete mode 100644 shaders/glsl/viewportarray/multiview.geom delete mode 100644 shaders/glsl/viewportarray/multiview.geom.spv delete mode 100644 shaders/glsl/viewportarray/scene.frag delete mode 100644 shaders/glsl/viewportarray/scene.frag.spv delete mode 100644 shaders/glsl/viewportarray/scene.vert delete mode 100644 shaders/glsl/viewportarray/scene.vert.spv delete mode 100644 shaders/glsl/vulkanscene/logo.frag delete mode 100644 shaders/glsl/vulkanscene/logo.frag.spv delete mode 100644 shaders/glsl/vulkanscene/logo.vert delete mode 100644 shaders/glsl/vulkanscene/logo.vert.spv delete mode 100644 shaders/glsl/vulkanscene/mesh.frag delete mode 100644 shaders/glsl/vulkanscene/mesh.frag.spv delete mode 100644 shaders/glsl/vulkanscene/mesh.vert delete mode 100644 shaders/glsl/vulkanscene/mesh.vert.spv delete mode 100644 shaders/glsl/vulkanscene/skybox.frag delete mode 100644 shaders/glsl/vulkanscene/skybox.frag.spv delete mode 100644 shaders/glsl/vulkanscene/skybox.vert delete mode 100644 shaders/glsl/vulkanscene/skybox.vert.spv delete mode 100644 shaders/hlsl/README.md delete mode 100644 shaders/hlsl/base/textoverlay.frag delete mode 100644 shaders/hlsl/base/textoverlay.frag.spv delete mode 100644 shaders/hlsl/base/textoverlay.vert delete mode 100644 shaders/hlsl/base/textoverlay.vert.spv delete mode 100644 shaders/hlsl/base/uioverlay.frag delete mode 100644 shaders/hlsl/base/uioverlay.frag.spv delete mode 100644 shaders/hlsl/base/uioverlay.vert delete mode 100644 shaders/hlsl/base/uioverlay.vert.spv delete mode 100644 shaders/hlsl/bloom/colorpass.frag delete mode 100644 shaders/hlsl/bloom/colorpass.frag.spv delete mode 100644 shaders/hlsl/bloom/colorpass.vert delete mode 100644 shaders/hlsl/bloom/colorpass.vert.spv delete mode 100644 shaders/hlsl/bloom/gaussblur.frag delete mode 100644 shaders/hlsl/bloom/gaussblur.frag.spv delete mode 100644 shaders/hlsl/bloom/gaussblur.vert delete mode 100644 shaders/hlsl/bloom/gaussblur.vert.spv delete mode 100644 shaders/hlsl/bloom/phongpass.frag delete mode 100644 shaders/hlsl/bloom/phongpass.frag.spv delete mode 100644 shaders/hlsl/bloom/phongpass.vert delete mode 100644 shaders/hlsl/bloom/phongpass.vert.spv delete mode 100644 shaders/hlsl/bloom/skybox.frag delete mode 100644 shaders/hlsl/bloom/skybox.frag.spv delete mode 100644 shaders/hlsl/bloom/skybox.vert delete mode 100644 shaders/hlsl/bloom/skybox.vert.spv delete mode 100644 shaders/hlsl/compileshaders.py delete mode 100644 shaders/hlsl/computecloth/cloth.comp delete mode 100644 shaders/hlsl/computecloth/cloth.comp.spv delete mode 100644 shaders/hlsl/computecloth/cloth.frag delete mode 100644 shaders/hlsl/computecloth/cloth.frag.spv delete mode 100644 shaders/hlsl/computecloth/cloth.vert delete mode 100644 shaders/hlsl/computecloth/cloth.vert.spv delete mode 100644 shaders/hlsl/computecloth/sphere.frag delete mode 100644 shaders/hlsl/computecloth/sphere.frag.spv delete mode 100644 shaders/hlsl/computecloth/sphere.vert delete mode 100644 shaders/hlsl/computecloth/sphere.vert.spv delete mode 100644 shaders/hlsl/computecullandlod/cull.comp delete mode 100644 shaders/hlsl/computecullandlod/cull.comp.spv delete mode 100644 shaders/hlsl/computecullandlod/indirectdraw.frag delete mode 100644 shaders/hlsl/computecullandlod/indirectdraw.frag.spv delete mode 100644 shaders/hlsl/computecullandlod/indirectdraw.vert delete mode 100644 shaders/hlsl/computecullandlod/indirectdraw.vert.spv delete mode 100644 shaders/hlsl/computeheadless/headless.comp delete mode 100644 shaders/hlsl/computeheadless/headless.comp.spv delete mode 100644 shaders/hlsl/computenbody/particle.frag delete mode 100644 shaders/hlsl/computenbody/particle.frag.spv delete mode 100644 shaders/hlsl/computenbody/particle.vert delete mode 100644 shaders/hlsl/computenbody/particle.vert.spv delete mode 100644 shaders/hlsl/computenbody/particle_calculate.comp delete mode 100644 shaders/hlsl/computenbody/particle_calculate.comp.spv delete mode 100644 shaders/hlsl/computenbody/particle_integrate.comp delete mode 100644 shaders/hlsl/computenbody/particle_integrate.comp.spv delete mode 100644 shaders/hlsl/computeparticles/particle.comp delete mode 100644 shaders/hlsl/computeparticles/particle.comp.spv delete mode 100644 shaders/hlsl/computeparticles/particle.frag delete mode 100644 shaders/hlsl/computeparticles/particle.frag.spv delete mode 100644 shaders/hlsl/computeparticles/particle.vert delete mode 100644 shaders/hlsl/computeparticles/particle.vert.spv delete mode 100644 shaders/hlsl/computeraytracing/raytracing.comp delete mode 100644 shaders/hlsl/computeraytracing/raytracing.comp.spv delete mode 100644 shaders/hlsl/computeraytracing/texture.frag delete mode 100644 shaders/hlsl/computeraytracing/texture.frag.spv delete mode 100644 shaders/hlsl/computeraytracing/texture.vert delete mode 100644 shaders/hlsl/computeraytracing/texture.vert.spv delete mode 100644 shaders/hlsl/computeshader/edgedetect.comp delete mode 100644 shaders/hlsl/computeshader/edgedetect.comp.spv delete mode 100644 shaders/hlsl/computeshader/emboss.comp delete mode 100644 shaders/hlsl/computeshader/emboss.comp.spv delete mode 100644 shaders/hlsl/computeshader/sharpen.comp delete mode 100644 shaders/hlsl/computeshader/sharpen.comp.spv delete mode 100644 shaders/hlsl/computeshader/texture.frag delete mode 100644 shaders/hlsl/computeshader/texture.frag.spv delete mode 100644 shaders/hlsl/computeshader/texture.vert delete mode 100644 shaders/hlsl/computeshader/texture.vert.spv delete mode 100644 shaders/hlsl/conditionalrender/model.frag delete mode 100644 shaders/hlsl/conditionalrender/model.frag.spv delete mode 100644 shaders/hlsl/conditionalrender/model.vert delete mode 100644 shaders/hlsl/conditionalrender/model.vert.spv delete mode 100644 shaders/hlsl/conservativeraster/fullscreen.frag delete mode 100644 shaders/hlsl/conservativeraster/fullscreen.frag.spv delete mode 100644 shaders/hlsl/conservativeraster/fullscreen.vert delete mode 100644 shaders/hlsl/conservativeraster/fullscreen.vert.spv delete mode 100644 shaders/hlsl/conservativeraster/triangle.frag delete mode 100644 shaders/hlsl/conservativeraster/triangle.frag.spv delete mode 100644 shaders/hlsl/conservativeraster/triangle.vert delete mode 100644 shaders/hlsl/conservativeraster/triangle.vert.spv delete mode 100644 shaders/hlsl/conservativeraster/triangleoverlay.frag delete mode 100644 shaders/hlsl/conservativeraster/triangleoverlay.frag.spv delete mode 100644 shaders/hlsl/debugprintf/toon.frag delete mode 100644 shaders/hlsl/debugprintf/toon.frag.spv delete mode 100644 shaders/hlsl/debugprintf/toon.vert delete mode 100644 shaders/hlsl/debugprintf/toon.vert.spv delete mode 100644 shaders/hlsl/debugutils/colorpass.frag delete mode 100644 shaders/hlsl/debugutils/colorpass.frag.spv delete mode 100644 shaders/hlsl/debugutils/colorpass.vert delete mode 100644 shaders/hlsl/debugutils/colorpass.vert.spv delete mode 100644 shaders/hlsl/debugutils/postprocess.frag delete mode 100644 shaders/hlsl/debugutils/postprocess.frag.spv delete mode 100644 shaders/hlsl/debugutils/postprocess.vert delete mode 100644 shaders/hlsl/debugutils/postprocess.vert.spv delete mode 100644 shaders/hlsl/debugutils/toon.frag delete mode 100644 shaders/hlsl/debugutils/toon.frag.spv delete mode 100644 shaders/hlsl/debugutils/toon.vert delete mode 100644 shaders/hlsl/debugutils/toon.vert.spv delete mode 100644 shaders/hlsl/deferred/deferred.frag delete mode 100644 shaders/hlsl/deferred/deferred.frag.spv delete mode 100644 shaders/hlsl/deferred/deferred.vert delete mode 100644 shaders/hlsl/deferred/deferred.vert.spv delete mode 100644 shaders/hlsl/deferred/mrt.frag delete mode 100644 shaders/hlsl/deferred/mrt.frag.spv delete mode 100644 shaders/hlsl/deferred/mrt.vert delete mode 100644 shaders/hlsl/deferred/mrt.vert.spv delete mode 100644 shaders/hlsl/deferredmultisampling/deferred.frag delete mode 100644 shaders/hlsl/deferredmultisampling/deferred.frag.spv delete mode 100644 shaders/hlsl/deferredmultisampling/deferred.vert delete mode 100644 shaders/hlsl/deferredmultisampling/deferred.vert.spv delete mode 100644 shaders/hlsl/deferredmultisampling/mrt.frag delete mode 100644 shaders/hlsl/deferredmultisampling/mrt.frag.spv delete mode 100644 shaders/hlsl/deferredmultisampling/mrt.vert delete mode 100644 shaders/hlsl/deferredmultisampling/mrt.vert.spv delete mode 100644 shaders/hlsl/deferredshadows/deferred.frag delete mode 100644 shaders/hlsl/deferredshadows/deferred.frag.spv delete mode 100644 shaders/hlsl/deferredshadows/deferred.vert delete mode 100644 shaders/hlsl/deferredshadows/deferred.vert.spv delete mode 100644 shaders/hlsl/deferredshadows/mrt.frag delete mode 100644 shaders/hlsl/deferredshadows/mrt.frag.spv delete mode 100644 shaders/hlsl/deferredshadows/mrt.vert delete mode 100644 shaders/hlsl/deferredshadows/mrt.vert.spv delete mode 100644 shaders/hlsl/deferredshadows/shadow.geom delete mode 100644 shaders/hlsl/deferredshadows/shadow.geom.spv delete mode 100644 shaders/hlsl/deferredshadows/shadow.vert delete mode 100644 shaders/hlsl/deferredshadows/shadow.vert.spv delete mode 100644 shaders/hlsl/descriptorindexing/descriptorindexing.frag delete mode 100644 shaders/hlsl/descriptorindexing/descriptorindexing.frag.spv delete mode 100644 shaders/hlsl/descriptorindexing/descriptorindexing.vert delete mode 100644 shaders/hlsl/descriptorindexing/descriptorindexing.vert.spv delete mode 100644 shaders/hlsl/descriptorsets/cube.frag delete mode 100644 shaders/hlsl/descriptorsets/cube.frag.spv delete mode 100644 shaders/hlsl/descriptorsets/cube.vert delete mode 100644 shaders/hlsl/descriptorsets/cube.vert.spv delete mode 100644 shaders/hlsl/displacement/base.frag delete mode 100644 shaders/hlsl/displacement/base.frag.spv delete mode 100644 shaders/hlsl/displacement/base.vert delete mode 100644 shaders/hlsl/displacement/base.vert.spv delete mode 100644 shaders/hlsl/displacement/displacement.tesc delete mode 100644 shaders/hlsl/displacement/displacement.tesc.spv delete mode 100644 shaders/hlsl/displacement/displacement.tese delete mode 100644 shaders/hlsl/displacement/displacement.tese.spv delete mode 100644 shaders/hlsl/distancefieldfonts/bitmap.frag delete mode 100644 shaders/hlsl/distancefieldfonts/bitmap.frag.spv delete mode 100644 shaders/hlsl/distancefieldfonts/bitmap.vert delete mode 100644 shaders/hlsl/distancefieldfonts/bitmap.vert.spv delete mode 100644 shaders/hlsl/distancefieldfonts/sdf.frag delete mode 100644 shaders/hlsl/distancefieldfonts/sdf.frag.spv delete mode 100644 shaders/hlsl/distancefieldfonts/sdf.vert delete mode 100644 shaders/hlsl/distancefieldfonts/sdf.vert.spv delete mode 100644 shaders/hlsl/dynamicuniformbuffer/base.frag delete mode 100644 shaders/hlsl/dynamicuniformbuffer/base.frag.spv delete mode 100644 shaders/hlsl/dynamicuniformbuffer/base.vert delete mode 100644 shaders/hlsl/dynamicuniformbuffer/base.vert.spv delete mode 100644 shaders/hlsl/gears/gears.frag delete mode 100644 shaders/hlsl/gears/gears.frag.spv delete mode 100644 shaders/hlsl/gears/gears.vert delete mode 100644 shaders/hlsl/gears/gears.vert.spv delete mode 100644 shaders/hlsl/geometryshader/base.frag delete mode 100644 shaders/hlsl/geometryshader/base.frag.spv delete mode 100644 shaders/hlsl/geometryshader/base.vert delete mode 100644 shaders/hlsl/geometryshader/base.vert.spv delete mode 100644 shaders/hlsl/geometryshader/mesh.frag delete mode 100644 shaders/hlsl/geometryshader/mesh.frag.spv delete mode 100644 shaders/hlsl/geometryshader/mesh.vert delete mode 100644 shaders/hlsl/geometryshader/mesh.vert.spv delete mode 100644 shaders/hlsl/geometryshader/normaldebug.geom delete mode 100644 shaders/hlsl/geometryshader/normaldebug.geom.spv delete mode 100644 shaders/hlsl/gltfloading/mesh.frag delete mode 100644 shaders/hlsl/gltfloading/mesh.frag.spv delete mode 100644 shaders/hlsl/gltfloading/mesh.vert delete mode 100644 shaders/hlsl/gltfloading/mesh.vert.spv delete mode 100644 shaders/hlsl/gltfscenerendering/scene.frag delete mode 100644 shaders/hlsl/gltfscenerendering/scene.frag.spv delete mode 100644 shaders/hlsl/gltfscenerendering/scene.vert delete mode 100644 shaders/hlsl/gltfscenerendering/scene.vert.spv delete mode 100644 shaders/hlsl/hdr/bloom.frag delete mode 100644 shaders/hlsl/hdr/bloom.frag.spv delete mode 100644 shaders/hlsl/hdr/bloom.vert delete mode 100644 shaders/hlsl/hdr/bloom.vert.spv delete mode 100644 shaders/hlsl/hdr/composition.frag delete mode 100644 shaders/hlsl/hdr/composition.frag.spv delete mode 100644 shaders/hlsl/hdr/composition.vert delete mode 100644 shaders/hlsl/hdr/composition.vert.spv delete mode 100644 shaders/hlsl/hdr/gbuffer.frag delete mode 100644 shaders/hlsl/hdr/gbuffer.frag.spv delete mode 100644 shaders/hlsl/hdr/gbuffer.vert delete mode 100644 shaders/hlsl/hdr/gbuffer.vert.spv delete mode 100644 shaders/hlsl/imgui/scene.frag delete mode 100644 shaders/hlsl/imgui/scene.frag.spv delete mode 100644 shaders/hlsl/imgui/scene.vert delete mode 100644 shaders/hlsl/imgui/scene.vert.spv delete mode 100644 shaders/hlsl/imgui/ui.frag delete mode 100644 shaders/hlsl/imgui/ui.frag.spv delete mode 100644 shaders/hlsl/imgui/ui.vert delete mode 100644 shaders/hlsl/imgui/ui.vert.spv delete mode 100644 shaders/hlsl/indirectdraw/ground.frag delete mode 100644 shaders/hlsl/indirectdraw/ground.frag.spv delete mode 100644 shaders/hlsl/indirectdraw/ground.vert delete mode 100644 shaders/hlsl/indirectdraw/ground.vert.spv delete mode 100644 shaders/hlsl/indirectdraw/indirectdraw.frag delete mode 100644 shaders/hlsl/indirectdraw/indirectdraw.frag.spv delete mode 100644 shaders/hlsl/indirectdraw/indirectdraw.vert delete mode 100644 shaders/hlsl/indirectdraw/indirectdraw.vert.spv delete mode 100644 shaders/hlsl/indirectdraw/skysphere.frag delete mode 100644 shaders/hlsl/indirectdraw/skysphere.frag.spv delete mode 100644 shaders/hlsl/indirectdraw/skysphere.vert delete mode 100644 shaders/hlsl/indirectdraw/skysphere.vert.spv delete mode 100644 shaders/hlsl/inlineuniformblocks/pbr.frag delete mode 100644 shaders/hlsl/inlineuniformblocks/pbr.frag.spv delete mode 100644 shaders/hlsl/inlineuniformblocks/pbr.vert delete mode 100644 shaders/hlsl/inlineuniformblocks/pbr.vert.spv delete mode 100644 shaders/hlsl/inputattachments/attachmentread.frag delete mode 100644 shaders/hlsl/inputattachments/attachmentread.frag.spv delete mode 100644 shaders/hlsl/inputattachments/attachmentread.vert delete mode 100644 shaders/hlsl/inputattachments/attachmentread.vert.spv delete mode 100644 shaders/hlsl/inputattachments/attachmentwrite.frag delete mode 100644 shaders/hlsl/inputattachments/attachmentwrite.frag.spv delete mode 100644 shaders/hlsl/inputattachments/attachmentwrite.vert delete mode 100644 shaders/hlsl/inputattachments/attachmentwrite.vert.spv delete mode 100644 shaders/hlsl/instancing/instancing.frag delete mode 100644 shaders/hlsl/instancing/instancing.frag.spv delete mode 100644 shaders/hlsl/instancing/instancing.vert delete mode 100644 shaders/hlsl/instancing/instancing.vert.spv delete mode 100644 shaders/hlsl/instancing/planet.frag delete mode 100644 shaders/hlsl/instancing/planet.frag.spv delete mode 100644 shaders/hlsl/instancing/planet.vert delete mode 100644 shaders/hlsl/instancing/planet.vert.spv delete mode 100644 shaders/hlsl/instancing/starfield.frag delete mode 100644 shaders/hlsl/instancing/starfield.frag.spv delete mode 100644 shaders/hlsl/instancing/starfield.vert delete mode 100644 shaders/hlsl/instancing/starfield.vert.spv delete mode 100644 shaders/hlsl/mesh/mesh.frag delete mode 100644 shaders/hlsl/mesh/mesh.frag.spv delete mode 100644 shaders/hlsl/mesh/mesh.vert delete mode 100644 shaders/hlsl/mesh/mesh.vert.spv delete mode 100644 shaders/hlsl/meshshader/meshshader.frag delete mode 100644 shaders/hlsl/meshshader/meshshader.frag.spv delete mode 100644 shaders/hlsl/meshshader/meshshader.mesh delete mode 100644 shaders/hlsl/meshshader/meshshader.mesh.spv delete mode 100644 shaders/hlsl/meshshader/meshshader.task delete mode 100644 shaders/hlsl/meshshader/meshshader.task.spv delete mode 100644 shaders/hlsl/multisampling/mesh.frag delete mode 100644 shaders/hlsl/multisampling/mesh.frag.spv delete mode 100644 shaders/hlsl/multisampling/mesh.vert delete mode 100644 shaders/hlsl/multisampling/mesh.vert.spv delete mode 100644 shaders/hlsl/multithreading/phong.frag delete mode 100644 shaders/hlsl/multithreading/phong.frag.spv delete mode 100644 shaders/hlsl/multithreading/phong.vert delete mode 100644 shaders/hlsl/multithreading/phong.vert.spv delete mode 100644 shaders/hlsl/multithreading/starsphere.frag delete mode 100644 shaders/hlsl/multithreading/starsphere.frag.spv delete mode 100644 shaders/hlsl/multithreading/starsphere.vert delete mode 100644 shaders/hlsl/multithreading/starsphere.vert.spv delete mode 100644 shaders/hlsl/multiview/multiview.frag delete mode 100644 shaders/hlsl/multiview/multiview.frag.spv delete mode 100644 shaders/hlsl/multiview/multiview.vert delete mode 100644 shaders/hlsl/multiview/multiview.vert.spv delete mode 100644 shaders/hlsl/multiview/viewdisplay.frag delete mode 100644 shaders/hlsl/multiview/viewdisplay.frag.spv delete mode 100644 shaders/hlsl/multiview/viewdisplay.vert delete mode 100644 shaders/hlsl/multiview/viewdisplay.vert.spv delete mode 100644 shaders/hlsl/negativeviewportheight/quad.frag delete mode 100644 shaders/hlsl/negativeviewportheight/quad.frag.spv delete mode 100644 shaders/hlsl/negativeviewportheight/quad.vert delete mode 100644 shaders/hlsl/negativeviewportheight/quad.vert.spv delete mode 100644 shaders/hlsl/occlusionquery/mesh.frag delete mode 100644 shaders/hlsl/occlusionquery/mesh.frag.spv delete mode 100644 shaders/hlsl/occlusionquery/mesh.vert delete mode 100644 shaders/hlsl/occlusionquery/mesh.vert.spv delete mode 100644 shaders/hlsl/occlusionquery/occluder.frag delete mode 100644 shaders/hlsl/occlusionquery/occluder.frag.spv delete mode 100644 shaders/hlsl/occlusionquery/occluder.vert delete mode 100644 shaders/hlsl/occlusionquery/occluder.vert.spv delete mode 100644 shaders/hlsl/occlusionquery/simple.frag delete mode 100644 shaders/hlsl/occlusionquery/simple.frag.spv delete mode 100644 shaders/hlsl/occlusionquery/simple.vert delete mode 100644 shaders/hlsl/occlusionquery/simple.vert.spv delete mode 100644 shaders/hlsl/offscreen/mirror.frag delete mode 100644 shaders/hlsl/offscreen/mirror.frag.spv delete mode 100644 shaders/hlsl/offscreen/mirror.vert delete mode 100644 shaders/hlsl/offscreen/mirror.vert.spv delete mode 100644 shaders/hlsl/offscreen/phong.frag delete mode 100644 shaders/hlsl/offscreen/phong.frag.spv delete mode 100644 shaders/hlsl/offscreen/phong.vert delete mode 100644 shaders/hlsl/offscreen/phong.vert.spv delete mode 100644 shaders/hlsl/offscreen/quad.frag delete mode 100644 shaders/hlsl/offscreen/quad.frag.spv delete mode 100644 shaders/hlsl/offscreen/quad.vert delete mode 100644 shaders/hlsl/offscreen/quad.vert.spv delete mode 100644 shaders/hlsl/oit/color.frag delete mode 100644 shaders/hlsl/oit/color.frag.spv delete mode 100644 shaders/hlsl/oit/color.vert delete mode 100644 shaders/hlsl/oit/color.vert.spv delete mode 100644 shaders/hlsl/oit/geometry.frag delete mode 100644 shaders/hlsl/oit/geometry.frag.spv delete mode 100644 shaders/hlsl/oit/geometry.vert delete mode 100644 shaders/hlsl/oit/geometry.vert.spv delete mode 100644 shaders/hlsl/parallaxmapping/parallax.frag delete mode 100644 shaders/hlsl/parallaxmapping/parallax.frag.spv delete mode 100644 shaders/hlsl/parallaxmapping/parallax.vert delete mode 100644 shaders/hlsl/parallaxmapping/parallax.vert.spv delete mode 100644 shaders/hlsl/particlesystem/normalmap.frag delete mode 100644 shaders/hlsl/particlesystem/normalmap.frag.spv delete mode 100644 shaders/hlsl/particlesystem/normalmap.vert delete mode 100644 shaders/hlsl/particlesystem/normalmap.vert.spv delete mode 100644 shaders/hlsl/particlesystem/particle.frag delete mode 100644 shaders/hlsl/particlesystem/particle.frag.spv delete mode 100644 shaders/hlsl/particlesystem/particle.vert delete mode 100644 shaders/hlsl/particlesystem/particle.vert.spv delete mode 100644 shaders/hlsl/pbrbasic/pbr.frag delete mode 100644 shaders/hlsl/pbrbasic/pbr.frag.spv delete mode 100644 shaders/hlsl/pbrbasic/pbr.vert delete mode 100644 shaders/hlsl/pbrbasic/pbr.vert.spv delete mode 100644 shaders/hlsl/pbribl/filtercube.vert delete mode 100644 shaders/hlsl/pbribl/filtercube.vert.spv delete mode 100644 shaders/hlsl/pbribl/genbrdflut.frag delete mode 100644 shaders/hlsl/pbribl/genbrdflut.frag.spv delete mode 100644 shaders/hlsl/pbribl/genbrdflut.vert delete mode 100644 shaders/hlsl/pbribl/genbrdflut.vert.spv delete mode 100644 shaders/hlsl/pbribl/irradiancecube.frag delete mode 100644 shaders/hlsl/pbribl/irradiancecube.frag.spv delete mode 100644 shaders/hlsl/pbribl/pbribl.frag delete mode 100644 shaders/hlsl/pbribl/pbribl.frag.spv delete mode 100644 shaders/hlsl/pbribl/pbribl.vert delete mode 100644 shaders/hlsl/pbribl/pbribl.vert.spv delete mode 100644 shaders/hlsl/pbribl/prefilterenvmap.frag delete mode 100644 shaders/hlsl/pbribl/prefilterenvmap.frag.spv delete mode 100644 shaders/hlsl/pbribl/skybox.frag delete mode 100644 shaders/hlsl/pbribl/skybox.frag.spv delete mode 100644 shaders/hlsl/pbribl/skybox.vert delete mode 100644 shaders/hlsl/pbribl/skybox.vert.spv delete mode 100644 shaders/hlsl/pbrtexture/filtercube.vert delete mode 100644 shaders/hlsl/pbrtexture/filtercube.vert.spv delete mode 100644 shaders/hlsl/pbrtexture/genbrdflut.frag delete mode 100644 shaders/hlsl/pbrtexture/genbrdflut.frag.spv delete mode 100644 shaders/hlsl/pbrtexture/genbrdflut.vert delete mode 100644 shaders/hlsl/pbrtexture/genbrdflut.vert.spv delete mode 100644 shaders/hlsl/pbrtexture/irradiancecube.frag delete mode 100644 shaders/hlsl/pbrtexture/irradiancecube.frag.spv delete mode 100644 shaders/hlsl/pbrtexture/pbrtexture.frag delete mode 100644 shaders/hlsl/pbrtexture/pbrtexture.frag.spv delete mode 100644 shaders/hlsl/pbrtexture/pbrtexture.vert delete mode 100644 shaders/hlsl/pbrtexture/pbrtexture.vert.spv delete mode 100644 shaders/hlsl/pbrtexture/prefilterenvmap.frag delete mode 100644 shaders/hlsl/pbrtexture/prefilterenvmap.frag.spv delete mode 100644 shaders/hlsl/pbrtexture/skybox.frag delete mode 100644 shaders/hlsl/pbrtexture/skybox.frag.spv delete mode 100644 shaders/hlsl/pbrtexture/skybox.vert delete mode 100644 shaders/hlsl/pbrtexture/skybox.vert.spv delete mode 100644 shaders/hlsl/pipelines/phong.frag delete mode 100644 shaders/hlsl/pipelines/phong.frag.spv delete mode 100644 shaders/hlsl/pipelines/phong.vert delete mode 100644 shaders/hlsl/pipelines/phong.vert.spv delete mode 100644 shaders/hlsl/pipelines/toon.frag delete mode 100644 shaders/hlsl/pipelines/toon.frag.spv delete mode 100644 shaders/hlsl/pipelines/toon.vert delete mode 100644 shaders/hlsl/pipelines/toon.vert.spv delete mode 100644 shaders/hlsl/pipelines/wireframe.frag delete mode 100644 shaders/hlsl/pipelines/wireframe.frag.spv delete mode 100644 shaders/hlsl/pipelines/wireframe.vert delete mode 100644 shaders/hlsl/pipelines/wireframe.vert.spv delete mode 100644 shaders/hlsl/pipelinestatistics/scene.frag delete mode 100644 shaders/hlsl/pipelinestatistics/scene.frag.spv delete mode 100644 shaders/hlsl/pipelinestatistics/scene.tesc delete mode 100644 shaders/hlsl/pipelinestatistics/scene.tesc.spv delete mode 100644 shaders/hlsl/pipelinestatistics/scene.tese delete mode 100644 shaders/hlsl/pipelinestatistics/scene.tese.spv delete mode 100644 shaders/hlsl/pipelinestatistics/scene.vert delete mode 100644 shaders/hlsl/pipelinestatistics/scene.vert.spv delete mode 100644 shaders/hlsl/pushconstants/pushconstants.frag delete mode 100644 shaders/hlsl/pushconstants/pushconstants.frag.spv delete mode 100644 shaders/hlsl/pushconstants/pushconstants.vert delete mode 100644 shaders/hlsl/pushconstants/pushconstants.vert.spv delete mode 100644 shaders/hlsl/pushdescriptors/cube.frag delete mode 100644 shaders/hlsl/pushdescriptors/cube.frag.spv delete mode 100644 shaders/hlsl/pushdescriptors/cube.vert delete mode 100644 shaders/hlsl/pushdescriptors/cube.vert.spv delete mode 100644 shaders/hlsl/radialblur/colorpass.frag delete mode 100644 shaders/hlsl/radialblur/colorpass.frag.spv delete mode 100644 shaders/hlsl/radialblur/colorpass.vert delete mode 100644 shaders/hlsl/radialblur/colorpass.vert.spv delete mode 100644 shaders/hlsl/radialblur/phongpass.frag delete mode 100644 shaders/hlsl/radialblur/phongpass.frag.spv delete mode 100644 shaders/hlsl/radialblur/phongpass.vert delete mode 100644 shaders/hlsl/radialblur/phongpass.vert.spv delete mode 100644 shaders/hlsl/radialblur/radialblur.frag delete mode 100644 shaders/hlsl/radialblur/radialblur.frag.spv delete mode 100644 shaders/hlsl/radialblur/radialblur.vert delete mode 100644 shaders/hlsl/radialblur/radialblur.vert.spv delete mode 100644 shaders/hlsl/raytracingbasic/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingbasic/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingbasic/miss.rmiss delete mode 100644 shaders/hlsl/raytracingbasic/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingbasic/raygen.rgen delete mode 100644 shaders/hlsl/raytracingbasic/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingcallable/callable1.rcall delete mode 100644 shaders/hlsl/raytracingcallable/callable1.rcall.spv delete mode 100644 shaders/hlsl/raytracingcallable/callable2.rcall delete mode 100644 shaders/hlsl/raytracingcallable/callable2.rcall.spv delete mode 100644 shaders/hlsl/raytracingcallable/callable3.rcall delete mode 100644 shaders/hlsl/raytracingcallable/callable3.rcall.spv delete mode 100644 shaders/hlsl/raytracingcallable/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingcallable/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingcallable/miss.rmiss delete mode 100644 shaders/hlsl/raytracingcallable/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingcallable/raygen.rgen delete mode 100644 shaders/hlsl/raytracingcallable/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingpositionfetch/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingpositionfetch/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingpositionfetch/miss.rmiss delete mode 100644 shaders/hlsl/raytracingpositionfetch/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingpositionfetch/raygen.rgen delete mode 100644 shaders/hlsl/raytracingpositionfetch/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingreflections/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingreflections/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingreflections/miss.rmiss delete mode 100644 shaders/hlsl/raytracingreflections/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingreflections/raygen.rgen delete mode 100644 shaders/hlsl/raytracingreflections/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingsbtdata/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingsbtdata/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingsbtdata/miss.rmiss delete mode 100644 shaders/hlsl/raytracingsbtdata/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingsbtdata/raygen.rgen delete mode 100644 shaders/hlsl/raytracingsbtdata/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingshadows/closesthit.rchit delete mode 100644 shaders/hlsl/raytracingshadows/closesthit.rchit.spv delete mode 100644 shaders/hlsl/raytracingshadows/miss.rmiss delete mode 100644 shaders/hlsl/raytracingshadows/miss.rmiss.spv delete mode 100644 shaders/hlsl/raytracingshadows/raygen.rgen delete mode 100644 shaders/hlsl/raytracingshadows/raygen.rgen.spv delete mode 100644 shaders/hlsl/raytracingshadows/shadow.rmiss delete mode 100644 shaders/hlsl/raytracingshadows/shadow.rmiss.spv delete mode 100644 shaders/hlsl/renderheadless/triangle.frag delete mode 100644 shaders/hlsl/renderheadless/triangle.frag.spv delete mode 100644 shaders/hlsl/renderheadless/triangle.vert delete mode 100644 shaders/hlsl/renderheadless/triangle.vert.spv delete mode 100644 shaders/hlsl/screenshot/mesh.frag delete mode 100644 shaders/hlsl/screenshot/mesh.frag.spv delete mode 100644 shaders/hlsl/screenshot/mesh.vert delete mode 100644 shaders/hlsl/screenshot/mesh.vert.spv delete mode 100644 shaders/hlsl/shadowmapping/offscreen.frag delete mode 100644 shaders/hlsl/shadowmapping/offscreen.frag.spv delete mode 100644 shaders/hlsl/shadowmapping/offscreen.vert delete mode 100644 shaders/hlsl/shadowmapping/offscreen.vert.spv delete mode 100644 shaders/hlsl/shadowmapping/quad.frag delete mode 100644 shaders/hlsl/shadowmapping/quad.frag.spv delete mode 100644 shaders/hlsl/shadowmapping/quad.vert delete mode 100644 shaders/hlsl/shadowmapping/quad.vert.spv delete mode 100644 shaders/hlsl/shadowmapping/scene.frag delete mode 100644 shaders/hlsl/shadowmapping/scene.frag.spv delete mode 100644 shaders/hlsl/shadowmapping/scene.vert delete mode 100644 shaders/hlsl/shadowmapping/scene.vert.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/debugshadowmap.frag delete mode 100644 shaders/hlsl/shadowmappingcascade/debugshadowmap.frag.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/debugshadowmap.vert delete mode 100644 shaders/hlsl/shadowmappingcascade/debugshadowmap.vert.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/depthpass.frag delete mode 100644 shaders/hlsl/shadowmappingcascade/depthpass.frag.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/depthpass.vert delete mode 100644 shaders/hlsl/shadowmappingcascade/depthpass.vert.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/scene.frag delete mode 100644 shaders/hlsl/shadowmappingcascade/scene.frag.spv delete mode 100644 shaders/hlsl/shadowmappingcascade/scene.vert delete mode 100644 shaders/hlsl/shadowmappingcascade/scene.vert.spv delete mode 100644 shaders/hlsl/shadowmappingomni/cubemapdisplay.frag delete mode 100644 shaders/hlsl/shadowmappingomni/cubemapdisplay.frag.spv delete mode 100644 shaders/hlsl/shadowmappingomni/cubemapdisplay.vert delete mode 100644 shaders/hlsl/shadowmappingomni/cubemapdisplay.vert.spv delete mode 100644 shaders/hlsl/shadowmappingomni/offscreen.frag delete mode 100644 shaders/hlsl/shadowmappingomni/offscreen.frag.spv delete mode 100644 shaders/hlsl/shadowmappingomni/offscreen.vert delete mode 100644 shaders/hlsl/shadowmappingomni/offscreen.vert.spv delete mode 100644 shaders/hlsl/shadowmappingomni/scene.frag delete mode 100644 shaders/hlsl/shadowmappingomni/scene.frag.spv delete mode 100644 shaders/hlsl/shadowmappingomni/scene.vert delete mode 100644 shaders/hlsl/shadowmappingomni/scene.vert.spv delete mode 100644 shaders/hlsl/specializationconstants/uber.frag delete mode 100644 shaders/hlsl/specializationconstants/uber.frag.spv delete mode 100644 shaders/hlsl/specializationconstants/uber.vert delete mode 100644 shaders/hlsl/specializationconstants/uber.vert.spv delete mode 100644 shaders/hlsl/sphericalenvmapping/sem.frag delete mode 100644 shaders/hlsl/sphericalenvmapping/sem.frag.spv delete mode 100644 shaders/hlsl/sphericalenvmapping/sem.vert delete mode 100644 shaders/hlsl/sphericalenvmapping/sem.vert.spv delete mode 100644 shaders/hlsl/ssao/blur.frag delete mode 100644 shaders/hlsl/ssao/blur.frag.spv delete mode 100644 shaders/hlsl/ssao/composition.frag delete mode 100644 shaders/hlsl/ssao/composition.frag.spv delete mode 100644 shaders/hlsl/ssao/fullscreen.vert delete mode 100644 shaders/hlsl/ssao/fullscreen.vert.spv delete mode 100644 shaders/hlsl/ssao/gbuffer.frag delete mode 100644 shaders/hlsl/ssao/gbuffer.frag.spv delete mode 100644 shaders/hlsl/ssao/gbuffer.vert delete mode 100644 shaders/hlsl/ssao/gbuffer.vert.spv delete mode 100644 shaders/hlsl/ssao/ssao.frag delete mode 100644 shaders/hlsl/ssao/ssao.frag.spv delete mode 100644 shaders/hlsl/stencilbuffer/outline.frag delete mode 100644 shaders/hlsl/stencilbuffer/outline.frag.spv delete mode 100644 shaders/hlsl/stencilbuffer/outline.vert delete mode 100644 shaders/hlsl/stencilbuffer/outline.vert.spv delete mode 100644 shaders/hlsl/stencilbuffer/toon.frag delete mode 100644 shaders/hlsl/stencilbuffer/toon.frag.spv delete mode 100644 shaders/hlsl/stencilbuffer/toon.vert delete mode 100644 shaders/hlsl/stencilbuffer/toon.vert.spv delete mode 100644 shaders/hlsl/subpasses/composition.frag delete mode 100644 shaders/hlsl/subpasses/composition.frag.spv delete mode 100644 shaders/hlsl/subpasses/composition.vert delete mode 100644 shaders/hlsl/subpasses/composition.vert.spv delete mode 100644 shaders/hlsl/subpasses/gbuffer.frag delete mode 100644 shaders/hlsl/subpasses/gbuffer.frag.spv delete mode 100644 shaders/hlsl/subpasses/gbuffer.vert delete mode 100644 shaders/hlsl/subpasses/gbuffer.vert.spv delete mode 100644 shaders/hlsl/subpasses/transparent.frag delete mode 100644 shaders/hlsl/subpasses/transparent.frag.spv delete mode 100644 shaders/hlsl/subpasses/transparent.vert delete mode 100644 shaders/hlsl/subpasses/transparent.vert.spv delete mode 100644 shaders/hlsl/terraintessellation/skysphere.frag delete mode 100644 shaders/hlsl/terraintessellation/skysphere.frag.spv delete mode 100644 shaders/hlsl/terraintessellation/skysphere.vert delete mode 100644 shaders/hlsl/terraintessellation/skysphere.vert.spv delete mode 100644 shaders/hlsl/terraintessellation/terrain.frag delete mode 100644 shaders/hlsl/terraintessellation/terrain.frag.spv delete mode 100644 shaders/hlsl/terraintessellation/terrain.tesc delete mode 100644 shaders/hlsl/terraintessellation/terrain.tesc.spv delete mode 100644 shaders/hlsl/terraintessellation/terrain.tese delete mode 100644 shaders/hlsl/terraintessellation/terrain.tese.spv delete mode 100644 shaders/hlsl/terraintessellation/terrain.vert delete mode 100644 shaders/hlsl/terraintessellation/terrain.vert.spv delete mode 100644 shaders/hlsl/tessellation/base.frag delete mode 100644 shaders/hlsl/tessellation/base.frag.spv delete mode 100644 shaders/hlsl/tessellation/base.vert delete mode 100644 shaders/hlsl/tessellation/base.vert.spv delete mode 100644 shaders/hlsl/tessellation/passthrough.tesc delete mode 100644 shaders/hlsl/tessellation/passthrough.tesc.spv delete mode 100644 shaders/hlsl/tessellation/passthrough.tese delete mode 100644 shaders/hlsl/tessellation/passthrough.tese.spv delete mode 100644 shaders/hlsl/tessellation/pntriangles.tesc delete mode 100644 shaders/hlsl/tessellation/pntriangles.tesc.spv delete mode 100644 shaders/hlsl/tessellation/pntriangles.tese delete mode 100644 shaders/hlsl/tessellation/pntriangles.tese.spv delete mode 100644 shaders/hlsl/textoverlay/mesh.frag delete mode 100644 shaders/hlsl/textoverlay/mesh.frag.spv delete mode 100644 shaders/hlsl/textoverlay/mesh.vert delete mode 100644 shaders/hlsl/textoverlay/mesh.vert.spv delete mode 100644 shaders/hlsl/textoverlay/text.frag delete mode 100644 shaders/hlsl/textoverlay/text.frag.spv delete mode 100644 shaders/hlsl/textoverlay/text.vert delete mode 100644 shaders/hlsl/textoverlay/text.vert.spv delete mode 100644 shaders/hlsl/texture/texture.frag delete mode 100644 shaders/hlsl/texture/texture.frag.spv delete mode 100644 shaders/hlsl/texture/texture.vert delete mode 100644 shaders/hlsl/texture/texture.vert.spv delete mode 100644 shaders/hlsl/texture3d/texture3d.frag delete mode 100644 shaders/hlsl/texture3d/texture3d.frag.spv delete mode 100644 shaders/hlsl/texture3d/texture3d.vert delete mode 100644 shaders/hlsl/texture3d/texture3d.vert.spv delete mode 100644 shaders/hlsl/texturearray/instancing.frag delete mode 100644 shaders/hlsl/texturearray/instancing.frag.spv delete mode 100644 shaders/hlsl/texturearray/instancing.vert delete mode 100644 shaders/hlsl/texturearray/instancing.vert.spv delete mode 100644 shaders/hlsl/texturecubemap/reflect.frag delete mode 100644 shaders/hlsl/texturecubemap/reflect.frag.spv delete mode 100644 shaders/hlsl/texturecubemap/reflect.vert delete mode 100644 shaders/hlsl/texturecubemap/reflect.vert.spv delete mode 100644 shaders/hlsl/texturecubemap/skybox.frag delete mode 100644 shaders/hlsl/texturecubemap/skybox.frag.spv delete mode 100644 shaders/hlsl/texturecubemap/skybox.vert delete mode 100644 shaders/hlsl/texturecubemap/skybox.vert.spv delete mode 100644 shaders/hlsl/texturecubemaparray/reflect.frag delete mode 100644 shaders/hlsl/texturecubemaparray/reflect.frag.spv delete mode 100644 shaders/hlsl/texturecubemaparray/reflect.vert delete mode 100644 shaders/hlsl/texturecubemaparray/reflect.vert.spv delete mode 100644 shaders/hlsl/texturecubemaparray/skybox.frag delete mode 100644 shaders/hlsl/texturecubemaparray/skybox.frag.spv delete mode 100644 shaders/hlsl/texturecubemaparray/skybox.vert delete mode 100644 shaders/hlsl/texturecubemaparray/skybox.vert.spv delete mode 100644 shaders/hlsl/texturemipmapgen/texture.frag delete mode 100644 shaders/hlsl/texturemipmapgen/texture.frag.spv delete mode 100644 shaders/hlsl/texturemipmapgen/texture.vert delete mode 100644 shaders/hlsl/texturemipmapgen/texture.vert.spv delete mode 100644 shaders/hlsl/texturesparseresidency/sparseresidency.frag delete mode 100644 shaders/hlsl/texturesparseresidency/sparseresidency.frag.spv delete mode 100644 shaders/hlsl/texturesparseresidency/sparseresidency.vert delete mode 100644 shaders/hlsl/texturesparseresidency/sparseresidency.vert.spv delete mode 100644 shaders/hlsl/triangle/triangle.frag delete mode 100644 shaders/hlsl/triangle/triangle.frag.spv delete mode 100644 shaders/hlsl/triangle/triangle.vert delete mode 100644 shaders/hlsl/triangle/triangle.vert.spv delete mode 100644 shaders/hlsl/variablerateshading/scene.frag delete mode 100644 shaders/hlsl/variablerateshading/scene.frag.spv delete mode 100644 shaders/hlsl/variablerateshading/scene.vert delete mode 100644 shaders/hlsl/variablerateshading/scene.vert.spv delete mode 100644 shaders/hlsl/viewportarray/multiview.geom delete mode 100644 shaders/hlsl/viewportarray/multiview.geom.spv delete mode 100644 shaders/hlsl/viewportarray/scene.frag delete mode 100644 shaders/hlsl/viewportarray/scene.frag.spv delete mode 100644 shaders/hlsl/viewportarray/scene.vert delete mode 100644 shaders/hlsl/viewportarray/scene.vert.spv delete mode 100644 shaders/hlsl/vulkanscene/logo.frag delete mode 100644 shaders/hlsl/vulkanscene/logo.frag.spv delete mode 100644 shaders/hlsl/vulkanscene/logo.vert delete mode 100644 shaders/hlsl/vulkanscene/logo.vert.spv delete mode 100644 shaders/hlsl/vulkanscene/mesh.frag delete mode 100644 shaders/hlsl/vulkanscene/mesh.frag.spv delete mode 100644 shaders/hlsl/vulkanscene/mesh.vert delete mode 100644 shaders/hlsl/vulkanscene/mesh.vert.spv delete mode 100644 shaders/hlsl/vulkanscene/skybox.frag delete mode 100644 shaders/hlsl/vulkanscene/skybox.frag.spv delete mode 100644 shaders/hlsl/vulkanscene/skybox.vert delete mode 100644 shaders/hlsl/vulkanscene/skybox.vert.spv delete mode 100644 shaders/slang/_rename.py delete mode 100644 shaders/slang/base/uioverlay.frag.spv delete mode 100644 shaders/slang/base/uioverlay.slang delete mode 100644 shaders/slang/base/uioverlay.vert.spv delete mode 100644 shaders/slang/bloom/colorpass.frag.spv delete mode 100644 shaders/slang/bloom/colorpass.slang delete mode 100644 shaders/slang/bloom/colorpass.vert.spv delete mode 100644 shaders/slang/bloom/gaussblur.frag.spv delete mode 100644 shaders/slang/bloom/gaussblur.slang delete mode 100644 shaders/slang/bloom/gaussblur.vert.spv delete mode 100644 shaders/slang/bloom/phongpass.frag.spv delete mode 100644 shaders/slang/bloom/phongpass.slang delete mode 100644 shaders/slang/bloom/phongpass.vert.spv delete mode 100644 shaders/slang/bloom/skybox.frag.spv delete mode 100644 shaders/slang/bloom/skybox.slang delete mode 100644 shaders/slang/bloom/skybox.vert.spv delete mode 100644 shaders/slang/bufferdeviceaddress/cube.frag.spv delete mode 100644 shaders/slang/bufferdeviceaddress/cube.slang delete mode 100644 shaders/slang/bufferdeviceaddress/cube.vert.spv delete mode 100644 shaders/slang/compileshaders.py delete mode 100644 shaders/slang/computecloth/cloth.comp.spv delete mode 100644 shaders/slang/computecloth/cloth.frag.spv delete mode 100644 shaders/slang/computecloth/cloth.slang delete mode 100644 shaders/slang/computecloth/cloth.vert.spv delete mode 100644 shaders/slang/computecloth/sphere.frag.spv delete mode 100644 shaders/slang/computecloth/sphere.slang delete mode 100644 shaders/slang/computecloth/sphere.vert.spv delete mode 100644 shaders/slang/computecullandlod/cull.comp.spv delete mode 100644 shaders/slang/computecullandlod/cull.slang delete mode 100644 shaders/slang/computecullandlod/indirectdraw.frag.spv delete mode 100644 shaders/slang/computecullandlod/indirectdraw.slang delete mode 100644 shaders/slang/computecullandlod/indirectdraw.vert.spv delete mode 100644 shaders/slang/computeheadless/headless.comp.spv delete mode 100644 shaders/slang/computeheadless/headless.slang delete mode 100644 shaders/slang/computenbody/particle.frag.spv delete mode 100644 shaders/slang/computenbody/particle.slang delete mode 100644 shaders/slang/computenbody/particle.vert.spv delete mode 100644 shaders/slang/computenbody/particle_calculate.comp.spv delete mode 100644 shaders/slang/computenbody/particle_calculate.slang delete mode 100644 shaders/slang/computenbody/particle_integrate.comp.spv delete mode 100644 shaders/slang/computenbody/particle_integrate.slang delete mode 100644 shaders/slang/computeparticles/particle.comp.spv delete mode 100644 shaders/slang/computeparticles/particle.frag.spv delete mode 100644 shaders/slang/computeparticles/particle.slang delete mode 100644 shaders/slang/computeparticles/particle.vert.spv delete mode 100644 shaders/slang/computeraytracing/raytracing.comp.spv delete mode 100644 shaders/slang/computeraytracing/raytracing.slang delete mode 100644 shaders/slang/computeraytracing/texture.frag.spv delete mode 100644 shaders/slang/computeraytracing/texture.slang delete mode 100644 shaders/slang/computeraytracing/texture.vert.spv delete mode 100644 shaders/slang/computeshader/edgedetect.comp.spv delete mode 100644 shaders/slang/computeshader/edgedetect.slang delete mode 100644 shaders/slang/computeshader/emboss.comp.spv delete mode 100644 shaders/slang/computeshader/emboss.slang delete mode 100644 shaders/slang/computeshader/shared.slang delete mode 100644 shaders/slang/computeshader/sharpen.comp.spv delete mode 100644 shaders/slang/computeshader/sharpen.slang delete mode 100644 shaders/slang/computeshader/texture.frag.spv delete mode 100644 shaders/slang/computeshader/texture.slang delete mode 100644 shaders/slang/computeshader/texture.vert.spv delete mode 100644 shaders/slang/conditionalrender/model.frag.spv delete mode 100644 shaders/slang/conditionalrender/model.slang delete mode 100644 shaders/slang/conditionalrender/model.vert.spv delete mode 100644 shaders/slang/conservativeraster/fullscreen.frag.spv delete mode 100644 shaders/slang/conservativeraster/fullscreen.slang delete mode 100644 shaders/slang/conservativeraster/fullscreen.vert.spv delete mode 100644 shaders/slang/conservativeraster/triangle.frag.spv delete mode 100644 shaders/slang/conservativeraster/triangle.slang delete mode 100644 shaders/slang/conservativeraster/triangle.vert.spv delete mode 100644 shaders/slang/conservativeraster/triangleoverlay.frag.spv delete mode 100644 shaders/slang/conservativeraster/triangleoverlay.slang delete mode 100644 shaders/slang/debugprintf/toon.frag.spv delete mode 100644 shaders/slang/debugprintf/toon.slang delete mode 100644 shaders/slang/debugprintf/toon.vert.spv delete mode 100644 shaders/slang/debugutils/colorpass.frag.spv delete mode 100644 shaders/slang/debugutils/colorpass.slang delete mode 100644 shaders/slang/debugutils/colorpass.vert.spv delete mode 100644 shaders/slang/debugutils/postprocess.frag.spv delete mode 100644 shaders/slang/debugutils/postprocess.slang delete mode 100644 shaders/slang/debugutils/postprocess.vert.spv delete mode 100644 shaders/slang/debugutils/toon.frag.spv delete mode 100644 shaders/slang/debugutils/toon.slang delete mode 100644 shaders/slang/debugutils/toon.vert.spv delete mode 100644 shaders/slang/deferred/deferred.frag.spv delete mode 100644 shaders/slang/deferred/deferred.slang delete mode 100644 shaders/slang/deferred/deferred.vert.spv delete mode 100644 shaders/slang/deferred/mrt.frag.spv delete mode 100644 shaders/slang/deferred/mrt.slang delete mode 100644 shaders/slang/deferred/mrt.vert.spv delete mode 100644 shaders/slang/deferredmultisampling/deferred.frag.spv delete mode 100644 shaders/slang/deferredmultisampling/deferred.slang delete mode 100644 shaders/slang/deferredmultisampling/deferred.vert.spv delete mode 100644 shaders/slang/deferredmultisampling/mrt.frag.spv delete mode 100644 shaders/slang/deferredmultisampling/mrt.slang delete mode 100644 shaders/slang/deferredmultisampling/mrt.vert.spv delete mode 100644 shaders/slang/deferredshadows/deferred.frag.spv delete mode 100644 shaders/slang/deferredshadows/deferred.slang delete mode 100644 shaders/slang/deferredshadows/deferred.vert.spv delete mode 100644 shaders/slang/deferredshadows/mrt.frag.spv delete mode 100644 shaders/slang/deferredshadows/mrt.slang delete mode 100644 shaders/slang/deferredshadows/mrt.vert.spv delete mode 100644 shaders/slang/deferredshadows/shadow.geom.spv delete mode 100644 shaders/slang/deferredshadows/shadow.slang delete mode 100644 shaders/slang/deferredshadows/shadow.vert.spv delete mode 100644 shaders/slang/descriptorbuffer/cube.frag.spv delete mode 100644 shaders/slang/descriptorbuffer/cube.slang delete mode 100644 shaders/slang/descriptorbuffer/cube.vert.spv delete mode 100644 shaders/slang/descriptorindexing/descriptorindexing.frag.spv delete mode 100644 shaders/slang/descriptorindexing/descriptorindexing.slang delete mode 100644 shaders/slang/descriptorindexing/descriptorindexing.vert.spv delete mode 100644 shaders/slang/descriptorsets/cube.frag.spv delete mode 100644 shaders/slang/descriptorsets/cube.slang delete mode 100644 shaders/slang/descriptorsets/cube.vert.spv delete mode 100644 shaders/slang/displacement/base.frag.spv delete mode 100644 shaders/slang/displacement/base.vert.spv delete mode 100644 shaders/slang/displacement/displacement.slang delete mode 100644 shaders/slang/displacement/displacement.tesc.spv delete mode 100644 shaders/slang/displacement/displacement.tese.spv delete mode 100644 shaders/slang/distancefieldfonts/bitmap.frag.spv delete mode 100644 shaders/slang/distancefieldfonts/bitmap.slang delete mode 100644 shaders/slang/distancefieldfonts/bitmap.vert.spv delete mode 100644 shaders/slang/distancefieldfonts/sdf.frag.spv delete mode 100644 shaders/slang/distancefieldfonts/sdf.slang delete mode 100644 shaders/slang/distancefieldfonts/sdf.vert.spv delete mode 100644 shaders/slang/dynamicrendering/texture.frag.spv delete mode 100644 shaders/slang/dynamicrendering/texture.slang delete mode 100644 shaders/slang/dynamicrendering/texture.vert.spv delete mode 100644 shaders/slang/dynamicuniformbuffer/base.frag.spv delete mode 100644 shaders/slang/dynamicuniformbuffer/base.slang delete mode 100644 shaders/slang/dynamicuniformbuffer/base.vert.spv delete mode 100644 shaders/slang/gears/gears.frag.spv delete mode 100644 shaders/slang/gears/gears.slang delete mode 100644 shaders/slang/gears/gears.vert.spv delete mode 100644 shaders/slang/geometryshader/base.frag.spv delete mode 100644 shaders/slang/geometryshader/base.vert.spv delete mode 100644 shaders/slang/geometryshader/mesh.frag.spv delete mode 100644 shaders/slang/geometryshader/mesh.slang delete mode 100644 shaders/slang/geometryshader/mesh.vert.spv delete mode 100644 shaders/slang/geometryshader/normaldebug.geom.spv delete mode 100644 shaders/slang/geometryshader/normaldebug.slang delete mode 100644 shaders/slang/gltfloading/mesh.frag.spv delete mode 100644 shaders/slang/gltfloading/mesh.slang delete mode 100644 shaders/slang/gltfloading/mesh.vert.spv delete mode 100644 shaders/slang/gltfscenerendering/scene.frag.spv delete mode 100644 shaders/slang/gltfscenerendering/scene.slang delete mode 100644 shaders/slang/gltfscenerendering/scene.vert.spv delete mode 100644 shaders/slang/gltfskinning/skinnedmodel.frag.spv delete mode 100644 shaders/slang/gltfskinning/skinnedmodel.slang delete mode 100644 shaders/slang/gltfskinning/skinnedmodel.vert.spv delete mode 100644 shaders/slang/graphicspipelinelibrary/shared.vert.spv delete mode 100644 shaders/slang/graphicspipelinelibrary/uber.frag.spv delete mode 100644 shaders/slang/graphicspipelinelibrary/uber.slang delete mode 100644 shaders/slang/hdr/bloom.frag.spv delete mode 100644 shaders/slang/hdr/bloom.slang delete mode 100644 shaders/slang/hdr/bloom.vert.spv delete mode 100644 shaders/slang/hdr/composition.frag.spv delete mode 100644 shaders/slang/hdr/composition.slang delete mode 100644 shaders/slang/hdr/composition.vert.spv delete mode 100644 shaders/slang/hdr/gbuffer.frag.spv delete mode 100644 shaders/slang/hdr/gbuffer.slang delete mode 100644 shaders/slang/hdr/gbuffer.vert.spv delete mode 100644 shaders/slang/imgui/scene.frag.spv delete mode 100644 shaders/slang/imgui/scene.slang delete mode 100644 shaders/slang/imgui/scene.vert.spv delete mode 100644 shaders/slang/imgui/ui.frag.spv delete mode 100644 shaders/slang/imgui/ui.slang delete mode 100644 shaders/slang/imgui/ui.vert.spv delete mode 100644 shaders/slang/indirectdraw/ground.frag.spv delete mode 100644 shaders/slang/indirectdraw/ground.slang delete mode 100644 shaders/slang/indirectdraw/ground.vert.spv delete mode 100644 shaders/slang/indirectdraw/indirectdraw.frag.spv delete mode 100644 shaders/slang/indirectdraw/indirectdraw.slang delete mode 100644 shaders/slang/indirectdraw/indirectdraw.vert.spv delete mode 100644 shaders/slang/indirectdraw/skysphere.frag.spv delete mode 100644 shaders/slang/indirectdraw/skysphere.slang delete mode 100644 shaders/slang/indirectdraw/skysphere.vert.spv delete mode 100644 shaders/slang/inlineuniformblocks/pbr.frag.spv delete mode 100644 shaders/slang/inlineuniformblocks/pbr.slang delete mode 100644 shaders/slang/inlineuniformblocks/pbr.vert.spv delete mode 100644 shaders/slang/inputattachments/attachmentread.frag.spv delete mode 100644 shaders/slang/inputattachments/attachmentread.slang delete mode 100644 shaders/slang/inputattachments/attachmentread.vert.spv delete mode 100644 shaders/slang/inputattachments/attachmentwrite.frag.spv delete mode 100644 shaders/slang/inputattachments/attachmentwrite.slang delete mode 100644 shaders/slang/inputattachments/attachmentwrite.vert.spv delete mode 100644 shaders/slang/instancing/instancing.frag.spv delete mode 100644 shaders/slang/instancing/instancing.slang delete mode 100644 shaders/slang/instancing/instancing.vert.spv delete mode 100644 shaders/slang/instancing/planet.frag.spv delete mode 100644 shaders/slang/instancing/planet.slang delete mode 100644 shaders/slang/instancing/planet.vert.spv delete mode 100644 shaders/slang/instancing/starfield.frag.spv delete mode 100644 shaders/slang/instancing/starfield.slang delete mode 100644 shaders/slang/instancing/starfield.vert.spv delete mode 100644 shaders/slang/meshshader/meshshader.frag.spv delete mode 100644 shaders/slang/meshshader/meshshader.mesh.spv delete mode 100644 shaders/slang/meshshader/meshshader.slang delete mode 100644 shaders/slang/meshshader/meshshader.task.spv delete mode 100644 shaders/slang/multisampling/mesh.frag.spv delete mode 100644 shaders/slang/multisampling/mesh.slang delete mode 100644 shaders/slang/multisampling/mesh.vert.spv delete mode 100644 shaders/slang/multithreading/phong.frag.spv delete mode 100644 shaders/slang/multithreading/phong.slang delete mode 100644 shaders/slang/multithreading/phong.vert.spv delete mode 100644 shaders/slang/multithreading/starsphere.frag.spv delete mode 100644 shaders/slang/multithreading/starsphere.slang delete mode 100644 shaders/slang/multithreading/starsphere.vert.spv delete mode 100644 shaders/slang/multiview/multiview.frag.spv delete mode 100644 shaders/slang/multiview/multiview.slang delete mode 100644 shaders/slang/multiview/multiview.vert.spv delete mode 100644 shaders/slang/multiview/viewdisplay.frag.spv delete mode 100644 shaders/slang/multiview/viewdisplay.slang delete mode 100644 shaders/slang/multiview/viewdisplay.vert.spv delete mode 100644 shaders/slang/negativeviewportheight/quad.frag.spv delete mode 100644 shaders/slang/negativeviewportheight/quad.slang delete mode 100644 shaders/slang/negativeviewportheight/quad.vert.spv delete mode 100644 shaders/slang/occlusionquery/mesh.frag.spv delete mode 100644 shaders/slang/occlusionquery/mesh.slang delete mode 100644 shaders/slang/occlusionquery/mesh.vert.spv delete mode 100644 shaders/slang/occlusionquery/occluder.frag.spv delete mode 100644 shaders/slang/occlusionquery/occluder.slang delete mode 100644 shaders/slang/occlusionquery/occluder.vert.spv delete mode 100644 shaders/slang/occlusionquery/simple.frag.spv delete mode 100644 shaders/slang/occlusionquery/simple.slang delete mode 100644 shaders/slang/occlusionquery/simple.vert.spv delete mode 100644 shaders/slang/offscreen/mirror.frag.spv delete mode 100644 shaders/slang/offscreen/mirror.slang delete mode 100644 shaders/slang/offscreen/mirror.vert.spv delete mode 100644 shaders/slang/offscreen/phong.frag.spv delete mode 100644 shaders/slang/offscreen/phong.slang delete mode 100644 shaders/slang/offscreen/phong.vert.spv delete mode 100644 shaders/slang/offscreen/quad.frag.spv delete mode 100644 shaders/slang/offscreen/quad.slang delete mode 100644 shaders/slang/offscreen/quad.vert.spv delete mode 100644 shaders/slang/oit/color.frag.spv delete mode 100644 shaders/slang/oit/color.slang delete mode 100644 shaders/slang/oit/color.vert.spv delete mode 100644 shaders/slang/oit/geometry.frag.spv delete mode 100644 shaders/slang/oit/geometry.slang delete mode 100644 shaders/slang/oit/geometry.vert.spv delete mode 100644 shaders/slang/parallaxmapping/parallax.frag.spv delete mode 100644 shaders/slang/parallaxmapping/parallax.slang delete mode 100644 shaders/slang/parallaxmapping/parallax.vert.spv delete mode 100644 shaders/slang/particlesystem/normalmap.frag.spv delete mode 100644 shaders/slang/particlesystem/normalmap.slang delete mode 100644 shaders/slang/particlesystem/normalmap.vert.spv delete mode 100644 shaders/slang/particlesystem/particle.frag.spv delete mode 100644 shaders/slang/particlesystem/particle.slang delete mode 100644 shaders/slang/particlesystem/particle.vert.spv delete mode 100644 shaders/slang/pbrbasic/pbr.frag.spv delete mode 100644 shaders/slang/pbrbasic/pbr.slang delete mode 100644 shaders/slang/pbrbasic/pbr.vert.spv delete mode 100644 shaders/slang/pbribl/filtercube.slang delete mode 100644 shaders/slang/pbribl/filtercube.vert.spv delete mode 100644 shaders/slang/pbribl/genbrdflut.frag.spv delete mode 100644 shaders/slang/pbribl/genbrdflut.slang delete mode 100644 shaders/slang/pbribl/genbrdflut.vert.spv delete mode 100644 shaders/slang/pbribl/irradiancecube.frag.spv delete mode 100644 shaders/slang/pbribl/irradiancecube.slang delete mode 100644 shaders/slang/pbribl/pbribl.frag.spv delete mode 100644 shaders/slang/pbribl/pbribl.slang delete mode 100644 shaders/slang/pbribl/pbribl.vert.spv delete mode 100644 shaders/slang/pbribl/prefilterenvmap.frag.spv delete mode 100644 shaders/slang/pbribl/prefilterenvmap.slang delete mode 100644 shaders/slang/pbribl/skybox.frag.spv delete mode 100644 shaders/slang/pbribl/skybox.slang delete mode 100644 shaders/slang/pbribl/skybox.vert.spv delete mode 100644 shaders/slang/pbrtexture/filtercube.slang delete mode 100644 shaders/slang/pbrtexture/filtercube.vert.spv delete mode 100644 shaders/slang/pbrtexture/genbrdflut.frag.spv delete mode 100644 shaders/slang/pbrtexture/genbrdflut.slang delete mode 100644 shaders/slang/pbrtexture/genbrdflut.vert.spv delete mode 100644 shaders/slang/pbrtexture/irradiancecube.frag.spv delete mode 100644 shaders/slang/pbrtexture/irradiancecube.slang delete mode 100644 shaders/slang/pbrtexture/pbrtexture.frag.spv delete mode 100644 shaders/slang/pbrtexture/pbrtexture.slang delete mode 100644 shaders/slang/pbrtexture/pbrtexture.vert.spv delete mode 100644 shaders/slang/pbrtexture/prefilterenvmap.frag.spv delete mode 100644 shaders/slang/pbrtexture/prefilterenvmap.slang delete mode 100644 shaders/slang/pbrtexture/skybox.frag.spv delete mode 100644 shaders/slang/pbrtexture/skybox.slang delete mode 100644 shaders/slang/pbrtexture/skybox.vert.spv delete mode 100644 shaders/slang/pipelines/phong.frag.spv delete mode 100644 shaders/slang/pipelines/phong.slang delete mode 100644 shaders/slang/pipelines/phong.vert.spv delete mode 100644 shaders/slang/pipelines/toon.frag.spv delete mode 100644 shaders/slang/pipelines/toon.slang delete mode 100644 shaders/slang/pipelines/toon.vert.spv delete mode 100644 shaders/slang/pipelines/wireframe.frag.spv delete mode 100644 shaders/slang/pipelines/wireframe.slang delete mode 100644 shaders/slang/pipelines/wireframe.vert.spv delete mode 100644 shaders/slang/pipelinestatistics/scene.frag.spv delete mode 100644 shaders/slang/pipelinestatistics/scene.slang delete mode 100644 shaders/slang/pipelinestatistics/scene.tesc.spv delete mode 100644 shaders/slang/pipelinestatistics/scene.tese.spv delete mode 100644 shaders/slang/pipelinestatistics/scene.vert.spv delete mode 100644 shaders/slang/pushconstants/pushconstants.frag.spv delete mode 100644 shaders/slang/pushconstants/pushconstants.slang delete mode 100644 shaders/slang/pushconstants/pushconstants.vert.spv delete mode 100644 shaders/slang/pushdescriptors/cube.frag.spv delete mode 100644 shaders/slang/pushdescriptors/cube.slang delete mode 100644 shaders/slang/pushdescriptors/cube.vert.spv delete mode 100644 shaders/slang/radialblur/colorpass.frag.spv delete mode 100644 shaders/slang/radialblur/colorpass.slang delete mode 100644 shaders/slang/radialblur/colorpass.vert.spv delete mode 100644 shaders/slang/radialblur/phongpass.frag.spv delete mode 100644 shaders/slang/radialblur/phongpass.slang delete mode 100644 shaders/slang/radialblur/phongpass.vert.spv delete mode 100644 shaders/slang/radialblur/radialblur.frag.spv delete mode 100644 shaders/slang/radialblur/radialblur.slang delete mode 100644 shaders/slang/radialblur/radialblur.vert.spv delete mode 100644 shaders/slang/rayquery/scene.frag.spv delete mode 100644 shaders/slang/rayquery/scene.slang delete mode 100644 shaders/slang/rayquery/scene.vert.spv delete mode 100644 shaders/slang/raytracingbasic/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingbasic/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingbasic/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingbasic/raytracingbasic.slang delete mode 100644 shaders/slang/raytracingcallable/callable1.rcall.spv delete mode 100644 shaders/slang/raytracingcallable/callable1.slang delete mode 100644 shaders/slang/raytracingcallable/callable2.rcall.spv delete mode 100644 shaders/slang/raytracingcallable/callable2.slang delete mode 100644 shaders/slang/raytracingcallable/callable3.rcall.spv delete mode 100644 shaders/slang/raytracingcallable/callable3.slang delete mode 100644 shaders/slang/raytracingcallable/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingcallable/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingcallable/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingcallable/raytracingcallable.slang delete mode 100644 shaders/slang/raytracinggltf/anyhit.rahit.spv delete mode 100644 shaders/slang/raytracinggltf/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracinggltf/miss.rmiss.spv delete mode 100644 shaders/slang/raytracinggltf/raygen.rgen.spv delete mode 100644 shaders/slang/raytracinggltf/raytracinggltf.slang delete mode 100644 shaders/slang/raytracinggltf/shadow.rmiss.spv delete mode 100644 shaders/slang/raytracinggltf/shadow.slang delete mode 100644 shaders/slang/raytracingintersection/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingintersection/intersection.rint.spv delete mode 100644 shaders/slang/raytracingintersection/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingintersection/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingintersection/raytracingintersection.slang delete mode 100644 shaders/slang/raytracingpositionfetch/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingpositionfetch/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingpositionfetch/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingpositionfetch/raytracingpositionfetch.slang delete mode 100644 shaders/slang/raytracingreflections/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingreflections/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingreflections/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingreflections/raytracingreflections.slang delete mode 100644 shaders/slang/raytracingsbtdata/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingsbtdata/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingsbtdata/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingsbtdata/raytracingsbtdata.slang delete mode 100644 shaders/slang/raytracingshadows/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingshadows/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingshadows/payload.slang delete mode 100644 shaders/slang/raytracingshadows/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingshadows/raytracingshadows.slang delete mode 100644 shaders/slang/raytracingshadows/shadow.rmiss.spv delete mode 100644 shaders/slang/raytracingshadows/shadow.slang delete mode 100644 shaders/slang/raytracingtextures/anyhit.rahit.spv delete mode 100644 shaders/slang/raytracingtextures/closesthit.rchit.spv delete mode 100644 shaders/slang/raytracingtextures/miss.rmiss.spv delete mode 100644 shaders/slang/raytracingtextures/raygen.rgen.spv delete mode 100644 shaders/slang/raytracingtextures/raytracingtextures.slang delete mode 100644 shaders/slang/renderheadless/triangle.frag.spv delete mode 100644 shaders/slang/renderheadless/triangle.slang delete mode 100644 shaders/slang/renderheadless/triangle.vert.spv delete mode 100644 shaders/slang/screenshot/mesh.frag.spv delete mode 100644 shaders/slang/screenshot/mesh.slang delete mode 100644 shaders/slang/screenshot/mesh.vert.spv delete mode 100644 shaders/slang/shaderobjects/phong.frag.spv delete mode 100644 shaders/slang/shaderobjects/phong.slang delete mode 100644 shaders/slang/shaderobjects/phong.vert.spv delete mode 100644 shaders/slang/shadowmapping/offscreen.frag.spv delete mode 100644 shaders/slang/shadowmapping/offscreen.slang delete mode 100644 shaders/slang/shadowmapping/offscreen.vert.spv delete mode 100644 shaders/slang/shadowmapping/quad.frag.spv delete mode 100644 shaders/slang/shadowmapping/quad.slang delete mode 100644 shaders/slang/shadowmapping/quad.vert.spv delete mode 100644 shaders/slang/shadowmapping/scene.frag.spv delete mode 100644 shaders/slang/shadowmapping/scene.slang delete mode 100644 shaders/slang/shadowmapping/scene.vert.spv delete mode 100644 shaders/slang/shadowmappingcascade/debugshadowmap.frag.spv delete mode 100644 shaders/slang/shadowmappingcascade/debugshadowmap.slang delete mode 100644 shaders/slang/shadowmappingcascade/debugshadowmap.vert.spv delete mode 100644 shaders/slang/shadowmappingcascade/depthpass.frag.spv delete mode 100644 shaders/slang/shadowmappingcascade/depthpass.slang delete mode 100644 shaders/slang/shadowmappingcascade/depthpass.vert.spv delete mode 100644 shaders/slang/shadowmappingcascade/scene.frag.spv delete mode 100644 shaders/slang/shadowmappingcascade/scene.slang delete mode 100644 shaders/slang/shadowmappingcascade/scene.vert.spv delete mode 100644 shaders/slang/shadowmappingomni/cubemapdisplay.frag.spv delete mode 100644 shaders/slang/shadowmappingomni/cubemapdisplay.slang delete mode 100644 shaders/slang/shadowmappingomni/cubemapdisplay.vert.spv delete mode 100644 shaders/slang/shadowmappingomni/offscreen.frag.spv delete mode 100644 shaders/slang/shadowmappingomni/offscreen.slang delete mode 100644 shaders/slang/shadowmappingomni/offscreen.vert.spv delete mode 100644 shaders/slang/shadowmappingomni/scene.frag.spv delete mode 100644 shaders/slang/shadowmappingomni/scene.slang delete mode 100644 shaders/slang/shadowmappingomni/scene.vert.spv delete mode 100644 shaders/slang/specializationconstants/uber.frag.spv delete mode 100644 shaders/slang/specializationconstants/uber.slang delete mode 100644 shaders/slang/specializationconstants/uber.vert.spv delete mode 100644 shaders/slang/sphericalenvmapping/sem.frag.spv delete mode 100644 shaders/slang/sphericalenvmapping/sem.slang delete mode 100644 shaders/slang/sphericalenvmapping/sem.vert.spv delete mode 100644 shaders/slang/ssao/blur.frag.spv delete mode 100644 shaders/slang/ssao/blur.slang delete mode 100644 shaders/slang/ssao/composition.frag.spv delete mode 100644 shaders/slang/ssao/composition.slang delete mode 100644 shaders/slang/ssao/fullscreen.slang delete mode 100644 shaders/slang/ssao/fullscreen.vert.spv delete mode 100644 shaders/slang/ssao/gbuffer.frag.spv delete mode 100644 shaders/slang/ssao/gbuffer.slang delete mode 100644 shaders/slang/ssao/gbuffer.vert.spv delete mode 100644 shaders/slang/ssao/ssao.frag.spv delete mode 100644 shaders/slang/ssao/ssao.slang delete mode 100644 shaders/slang/ssao/types.slang delete mode 100644 shaders/slang/stencilbuffer/outline.frag.spv delete mode 100644 shaders/slang/stencilbuffer/outline.slang delete mode 100644 shaders/slang/stencilbuffer/outline.vert.spv delete mode 100644 shaders/slang/stencilbuffer/toon.frag.spv delete mode 100644 shaders/slang/stencilbuffer/toon.slang delete mode 100644 shaders/slang/stencilbuffer/toon.vert.spv delete mode 100644 shaders/slang/subpasses/composition.frag.spv delete mode 100644 shaders/slang/subpasses/composition.slang delete mode 100644 shaders/slang/subpasses/composition.vert.spv delete mode 100644 shaders/slang/subpasses/gbuffer.frag.spv delete mode 100644 shaders/slang/subpasses/gbuffer.slang delete mode 100644 shaders/slang/subpasses/gbuffer.vert.spv delete mode 100644 shaders/slang/subpasses/transparent.frag.spv delete mode 100644 shaders/slang/subpasses/transparent.slang delete mode 100644 shaders/slang/subpasses/transparent.vert.spv delete mode 100644 shaders/slang/terraintessellation/skysphere.frag.spv delete mode 100644 shaders/slang/terraintessellation/skysphere.slang delete mode 100644 shaders/slang/terraintessellation/skysphere.vert.spv delete mode 100644 shaders/slang/terraintessellation/terrain.frag.spv delete mode 100644 shaders/slang/terraintessellation/terrain.slang delete mode 100644 shaders/slang/terraintessellation/terrain.tesc.spv delete mode 100644 shaders/slang/terraintessellation/terrain.tese.spv delete mode 100644 shaders/slang/terraintessellation/terrain.vert.spv delete mode 100644 shaders/slang/tessellation/base.frag.spv delete mode 100644 shaders/slang/tessellation/base.slang delete mode 100644 shaders/slang/tessellation/base.vert.spv delete mode 100644 shaders/slang/tessellation/passthrough.slang delete mode 100644 shaders/slang/tessellation/passthrough.tesc.spv delete mode 100644 shaders/slang/tessellation/passthrough.tese.spv delete mode 100644 shaders/slang/tessellation/pntriangles.slang delete mode 100644 shaders/slang/tessellation/pntriangles.tesc.spv delete mode 100644 shaders/slang/tessellation/pntriangles.tese.spv delete mode 100644 shaders/slang/textoverlay/mesh.frag.spv delete mode 100644 shaders/slang/textoverlay/mesh.slang delete mode 100644 shaders/slang/textoverlay/mesh.vert.spv delete mode 100644 shaders/slang/textoverlay/text.frag.spv delete mode 100644 shaders/slang/textoverlay/text.slang delete mode 100644 shaders/slang/textoverlay/text.vert.spv delete mode 100644 shaders/slang/texture/texture.frag.spv delete mode 100644 shaders/slang/texture/texture.slang delete mode 100644 shaders/slang/texture/texture.vert.spv delete mode 100644 shaders/slang/texture3d/texture3d.frag.spv delete mode 100644 shaders/slang/texture3d/texture3d.slang delete mode 100644 shaders/slang/texture3d/texture3d.vert.spv delete mode 100644 shaders/slang/texturearray/instancing.frag.spv delete mode 100644 shaders/slang/texturearray/instancing.slang delete mode 100644 shaders/slang/texturearray/instancing.vert.spv delete mode 100644 shaders/slang/texturecubemap/reflect.frag.spv delete mode 100644 shaders/slang/texturecubemap/reflect.slang delete mode 100644 shaders/slang/texturecubemap/reflect.vert.spv delete mode 100644 shaders/slang/texturecubemap/skybox.frag.spv delete mode 100644 shaders/slang/texturecubemap/skybox.slang delete mode 100644 shaders/slang/texturecubemap/skybox.vert.spv delete mode 100644 shaders/slang/texturecubemaparray/reflect.frag.spv delete mode 100644 shaders/slang/texturecubemaparray/reflect.slang delete mode 100644 shaders/slang/texturecubemaparray/reflect.vert.spv delete mode 100644 shaders/slang/texturecubemaparray/skybox.frag.spv delete mode 100644 shaders/slang/texturecubemaparray/skybox.slang delete mode 100644 shaders/slang/texturecubemaparray/skybox.vert.spv delete mode 100644 shaders/slang/texturemipmapgen/texture.frag.spv delete mode 100644 shaders/slang/texturemipmapgen/texture.slang delete mode 100644 shaders/slang/texturemipmapgen/texture.vert.spv delete mode 100644 shaders/slang/texturesparseresidency/sparseresidency.frag.spv delete mode 100644 shaders/slang/texturesparseresidency/sparseresidency.slang delete mode 100644 shaders/slang/texturesparseresidency/sparseresidency.vert.spv delete mode 100644 shaders/slang/triangle/triangle.frag.spv delete mode 100644 shaders/slang/triangle/triangle.slang delete mode 100644 shaders/slang/triangle/triangle.vert.spv delete mode 100644 shaders/slang/variablerateshading/scene.frag.spv delete mode 100644 shaders/slang/variablerateshading/scene.slang delete mode 100644 shaders/slang/variablerateshading/scene.vert.spv delete mode 100644 shaders/slang/vertexattributes/scene.frag.spv delete mode 100644 shaders/slang/vertexattributes/scene.slang delete mode 100644 shaders/slang/vertexattributes/scene.vert.spv delete mode 100644 shaders/slang/viewportarray/multiview.geom.spv delete mode 100644 shaders/slang/viewportarray/scene.frag.spv delete mode 100644 shaders/slang/viewportarray/scene.slang delete mode 100644 shaders/slang/viewportarray/scene.vert.spv delete mode 100644 shaders/slang/vulkanscene/logo.frag.spv delete mode 100644 shaders/slang/vulkanscene/logo.slang delete mode 100644 shaders/slang/vulkanscene/logo.vert.spv delete mode 100644 shaders/slang/vulkanscene/mesh.frag.spv delete mode 100644 shaders/slang/vulkanscene/mesh.slang delete mode 100644 shaders/slang/vulkanscene/mesh.vert.spv delete mode 100644 shaders/slang/vulkanscene/skybox.frag.spv delete mode 100644 shaders/slang/vulkanscene/skybox.slang delete mode 100644 shaders/slang/vulkanscene/skybox.vert.spv create mode 100644 src/CMakeLists.txt create mode 100644 src/core/Application.cpp create mode 100644 src/core/Application.h create mode 100644 src/core/Camera.cpp create mode 100644 src/core/Camera.h create mode 100644 src/main.cpp create mode 100644 src/scene/ProceduralGeometry.cpp create mode 100644 src/scene/ProceduralGeometry.h create mode 100644 src/scene/SceneManager.cpp create mode 100644 src/scene/SceneManager.h create mode 100644 src/scene/SceneObject.cpp create mode 100644 src/scene/SceneObject.h create mode 100644 src/ui/UIManager.cpp create mode 100644 src/ui/UIManager.h create mode 100644 src/ui/UISettings.h diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..c48a05d9 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(env)", + "Bash(curl:*)", + "Bash(git remote remove:*)", + "Bash(git remote set-url:*)", + "Bash(git add:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ea312ea6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,147 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with this Vulkan-based 3D engine codebase. + +## Project Overview + +A modern Vulkan-based 3D engine built on Sascha Willems' Vulkan examples framework, featuring procedural shape generation, scene management, and ImGui integration. The engine uses dynamic rendering (VK_KHR_dynamic_rendering) and C++20 standards. + +## Build System + +### Quick Build +```bash +# From project root (inihb/) +mkdir build && cd build +cmake .. && cmake --build . --config Release +``` + +### Platform-Specific +- **Windows**: `cmake .. -G "Visual Studio 16 2019" -A x64` +- **Linux**: `cmake .. && make -j$(nproc)` +- **Output**: `build/bin/Release/ProceduralEngine3D.exe` + +### Dependencies +- Vulkan SDK (required) +- C++20 compatible compiler +- CMake 3.10.0+ + +## Architecture + +### Core Systems +- **Application** (`src/core/Application.cpp`): Main Vulkan application class inheriting from VulkanExampleBase +- **Base Framework** (`base/`): Sascha Willems' Vulkan infrastructure +- **Scene Management** (`src/scene/`): Object and shape management +- **UI System** (`src/ui/`): ImGui-based interface +- **Camera System** (`base/camera.hpp`): Orbit camera with focus functionality + +### Vulkan Features +- Dynamic rendering (no render passes/framebuffers) +- Modern Vulkan 1.3+ practices +- Multi-platform window system integration +- Validation layer support + +## Key Files + +### Core Components +- `src/core/Application.cpp` - Main application class with Vulkan setup +- `base/camera.hpp` - Camera system with orbit mode and F key focus +- `src/scene/SceneManager.cpp` - Scene graph and object management +- `src/ui/UIManager.cpp` - ImGui integration and panels + +### Vulkan Infrastructure (Sascha Willems Base) +- `base/VulkanExampleBase.h` - Main Vulkan framework +- `base/VulkanDevice.hpp` - Vulkan device abstraction +- `base/VulkanSwapChain.hpp` - Swapchain management +- `base/VulkanBuffer.hpp` - Buffer utilities + +### Build Configuration +- `CMakeLists.txt` - Main build configuration +- `BUILD.md` - Detailed build instructions + +## Camera System + +### F Key Focus Feature +The camera system supports focusing on selected objects: +- Press **F** to focus camera on selected object +- Automatically calculates optimal viewing distance +- Switches to orbit mode around focused object +- Mouse rotation orbits around the focused object center + +### Implementation Details +- `camera.focusOnObject()` - Sets orbit center and optimal distance +- `camera.updateOrbitPosition()` - Handles spherical coordinate positioning +- Hybrid system: standard Sascha camera + orbit mode using `glm::lookAt()` + +## Development Guidelines + +### Testing Changes +1. Build with `cmake --build . --config Release` +2. Run from `build/bin/Release/` +3. Use **F** key to test camera focus functionality +4. Check console output for debug information + +### Vulkan Debugging +- Enable validation layers with `-v` command line flag +- Use `--listgpus` to see available Vulkan devices +- Check `VK_LAYER_KHRONOS_validation` environment variable + +### Adding Features +- Follow Sascha Willems patterns for Vulkan code +- Use dynamic rendering (no render passes) +- Implement proper buffer management and synchronization +- Add ImGui panels for new features + +## Common Tasks + +### Build and Run +```bash +cd build +cmake --build . --config Release +bin/Release/ProceduralEngine3D.exe +``` + +### Shader Compilation +Shaders are in GLSL and compiled to SPIR-V: +- Location: `shaders/glsl/` +- Compiled automatically during build +- Use `glslc` or `glslangValidator` for manual compilation + +### Clean Build +```bash +rm -rf build && mkdir build && cd build +cmake .. && cmake --build . --config Release +``` + +### Camera Controls +- **Mouse**: Look around (standard mode) or orbit (after F key focus) +- **WASD**: Move camera +- **F**: Focus on selected object +- **Mouse Wheel**: Zoom (in orbit mode changes distance) + +## Command Line Options + +Available options (use `--help` for full list): +- `-v, --validation`: Enable validation layers +- `-w, --width`: Set window width +- `-h, --height`: Set window height +- `-f, --fullscreen`: Start in fullscreen +- `-g, --gpu`: Select GPU device +- `--listgpus`: List available Vulkan devices + +## Technical Notes + +### Modern Vulkan Usage +- Uses `VK_KHR_dynamic_rendering` extension +- No traditional render passes or framebuffers +- Leverages Vulkan 1.3+ features where available +- Proper synchronization with timeline semaphores + +### Memory Management +- Buffer utilities handle staging automatically +- Proper cleanup in destructors +- Device memory allocation through base framework + +### Threading +- Single-threaded main loop +- Vulkan command buffer recording on main thread +- UI rendering integrated with main render loop \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3455a65d..00a0fcd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,12 @@ cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -set(NAME vulkanExamples) +set(NAME inihb) 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) @@ -179,4 +178,4 @@ ENDIF(WIN32) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/") add_subdirectory(base) -add_subdirectory(examples) +add_subdirectory(src) diff --git a/README.md b/README.md index 41a949cb..b21c505e 100644 --- a/README.md +++ b/README.md @@ -1,511 +1,197 @@ -# Vulkan C++ examples and demos +# Procedural 3D Engine -A comprehensive collection of open source C++ examples for [Vulkan®](https://www.vulkan.org), the low-level 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) +A modern Vulkan-based 3D engine featuring procedural shape generation, scene management, and ImGui integration. Built on top of Sascha Willems' Vulkan examples framework with dynamic rendering support and C++20 standards. ## Table of Contents -+ [Official Khronos Vulkan Samples](#official-khronos-vulkan-samples) -+ [Cloning](#Cloning) -+ [Assets](#Assets) -+ [Building](#Building) -+ [Running](#Running) -+ [Shaders](#Shaders) -+ [A note on synchronization](#a-note-on-synchronization) -+ [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) ++ [Features](#features) ++ [Requirements](#requirements) ++ [Building](#building) ++ [Running](#running) ++ [Architecture](#architecture) ++ [Camera System](#camera-system) ++ [Development](#development) ++ [Vulkan Examples](#vulkan-examples) + [Credits and Attributions](#credits-and-attributions) -## Official Khronos Vulkan Samples +## Features -Khronos has 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)). +- **Modern Vulkan Rendering**: Built on Vulkan 1.3+ with dynamic rendering (VK_KHR_dynamic_rendering) +- **Procedural Shape Generation**: Generate and manipulate 3D shapes procedurally +- **Scene Management**: Hierarchical scene graph with object management +- **ImGui Integration**: Real-time UI for parameter adjustment and debugging +- **Advanced Camera System**: Orbit camera with focus functionality (F key) +- **Cross-Platform**: Windows, Linux, and macOS support +- **C++20 Standards**: Modern C++ codebase with latest language features -You can find this repository at https://github.com/KhronosGroup/Vulkan-Samples +## Requirements -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. +### Software Dependencies +- **Vulkan SDK** 1.3.0 or higher (required) +- **CMake** 3.10.0 or higher +- **C++20** compatible compiler: + - Visual Studio 2019/2022 (Windows) + - GCC 10+ or Clang 13+ (Linux) + - Xcode 13+ (macOS) -## Cloning -This repository contains submodules for external dependencies and assets, 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 -``` +### Hardware Requirements +- Vulkan-compatible GPU +- 2GB+ VRAM recommended +- GPU with dynamic rendering support preferred ## Building -The repository contains everything required to compile and build the examples on Windows, Android, iOS and macOS (using MoltenVK) using a C++ compiler that supports C++20. +### Quick Build (All Platforms) +```bash +# From project root directory +mkdir build && cd build +cmake .. && cmake --build . --config Release +``` -See [BUILD.md](BUILD.md) for details on how to build for the different platforms. +### Platform-Specific Build + +#### Windows (Visual Studio) +```bash +mkdir build && cd build +cmake .. -G "Visual Studio 16 2019" -A x64 +cmake --build . --config Release +``` + +#### Linux +```bash +mkdir build && cd build +cmake .. && make -j$(nproc) +``` + +#### macOS +```bash +mkdir build && cd build +cmake .. && make -j$(sysctl -n hw.ncpu) +``` + +### Output Location +- **Executable**: `build/bin/Release/ProceduralEngine3D.exe` (Windows) or `build/bin/Release/ProceduralEngine3D` (Unix) +- **Assets**: Automatically copied to output directory + +For detailed build instructions, see [BUILD.md](BUILD.md). ## Running -Once built, examples can be run from the bin directory. The list of available command line options can be brought up with `--help`: +### Basic Usage +```bash +# From build output directory +cd build/bin/Release +./ProceduralEngine3D ``` - --help: Show help - -h, --height: Set window height - -v, --validation: Enable validation layers - -vs, --vsync: Enable V-Sync - -f, --fullscreen: Start in fullscreen mode - -w, --width: Set window width - -s, --shaders: Select shader type to use (glsl, slang, hlsl) - -g, --gpu: Select GPU to run on - -gl, --listgpus: Display a list of available Vulkan devices - -b, --benchmark: Run example in benchmark mode - -bw, --benchwarmup: Set warmup time for benchmark mode in seconds - -br, --benchruntime: Set duration time for benchmark mode in seconds - -bf, --benchfilename: Set file name for benchmark results - -bt, --benchframetimes: Save frame times to benchmark results file - -bfs, --benchmarkframes: Only render the given number of frames - -rp, --resourcepath: Set path for dir where assets and shaders folder is present + +### Command Line Options +Use `--help` to see all available options: +```bash +./ProceduralEngine3D --help ``` -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](shaders/glsl), most samples also come with [slang](shaders/slang/) and [HLSL](shaders/hlsl) shader sources, making it easy to compare the differences between those shading languages. The [Rust GPU](https://rust-gpu.github.io/) project maintains [Rust](https://www.rust-lang.org/) shader sources in a [separate repo](https://github.com/Rust-GPU/VulkanShaderExamples/tree/master/shaders/rust). - -## A note on synchronization - -Synchronization in the master branch currently isn't optimal und uses ```vkDeviceQueueWaitIdle``` at the end of each frame. This is a heavy operation and is suboptimal in regards to having CPU and GPU operations run in parallel. I'm currently reworking this in the [this branch](https://github.com/SaschaWillems/Vulkan/tree/sync_rework_second_attempt). While still work-in-progress, if you're interested in a more proper way of synchronization in Vulkan, please take a look at the [PR for that branch](https://github.com/SaschaWillems/Vulkan/pull/1224) to see progress. - -## Examples - -### Basics - -- [Basic triangle using Vulkan 1.0](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. - -- [Basic triangle using Vulkan 1.3](examples/trianglevulkan13//) - - Vulkan 1.3 version of the basic and verbose example for getting a colored triangle rendered to the screen. This makes use of features like dynamic rendering simplifying api usage. - -- [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/particlesystem/) - - 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. - -- [Vertex attributes](examples/vertexattributes/) - - Demonstrates two different ways of passing vertices to the vertex shader using either interleaved or separate vertex attributes. - -### 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 - -All Vulkan implementations support compute shaders, a more generalized way of doing workloads on the GPU. These samples demonstrate how to use those compute shaders. - -- [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 - -Vulkan supports GPUs with dedicated hardware for ray tracing. These sampples show different parts of that functionality. - -- [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. - -- [Ray traced texture mapping](examples/raytracingtextures) - - Renders a texture mapped quad with transparency using the new ray tracing extensions. Shows how to do texture mapping in a closes hit shader, how to cancel intersections for transparency in an any hit shader and how to access mesh data in those shaders using buffer device addresses. - -- [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 tracing intersection shaders](examples/raytracingintersection) - - Uses an intersection shader for procedural geometry. Instead of using actual geometry, this sample on passes bounding boxes and object definitions. An intersection shader is then used to trace against the procedural objects. - -- [Ray traced glTF](examples/raytracinggltf/) - - Renders a textured glTF model using ray traying instead of rasterization. Makes use of frame accumulation for transparency and anti aliasing. - -- [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. - -- [Position fetch](examples/raytracingpositionfetch/) - - Uses the `VK_KHR_ray_tracing_position_fetch` extension to fetch vertex position data from the acceleration structure from within a shader, instead of having to manually unpack vertex information. - -### 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. - -### Extensions - -Vulkan is an extensible api with lots of functionality added by extensions. These samples demonstrate the usage of such extensions. - -**Note:** Certain extensions may become core functionality for newer Vulkan versions. The samples will still work with these. - -- [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 shader printf (VK_KHR_shader_non_semantic_info)](examples/debugprintf/) - - Shows how to use printf in a shader to output additional information per invocation. This information can help debugging shader related issues in tools like RenderDoc. - - **Note:** This sample should be run from a graphics debugger like RenderDoc. - -- [Debug utils (VK_EXT_debug_utils)](examples/debugutils/) - - Shows how to use debug utils for adding labels and colors to Vulkan objects for graphics debuggers. This information helps to identify resources in tools like RenderDoc. - - **Note:** This sample should be run from a graphics debugger like RenderDoc. - -- [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_KHR_fragment_shading_rate)](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. - -- [Dynamic rendering with multi sampling (VK_KHR_dynamic_rendering)](examples/dynamicrenderingmultisampling/) - - Based on the dynamic rendering sample, this sample shows how to do implement multi sampling with dynamic rendering. - -- [Graphics pipeline library (VK_EXT_graphics_pipeline_library)](./examples/graphicspipelinelibrary) - - Uses the graphics pipeline library extensions to improve run-time pipeline creation. Instead of creating the whole pipeline at once, this sample pre builds shared pipeline parts like like vertex input state and fragment output state. These are then used to create full pipelines at runtime, reducing build times and possible hick-ups. - -- [Mesh shaders (VK_EXT_mesh_shader)](./examples/meshshader) - - Basic sample demonstrating how to use the mesh shading pipeline as a replacement for the traditional vertex pipeline. - -- [Descriptor buffers (VK_EXT_descriptor_buffer)](./examples/descriptorbuffer/) - - Basic sample showing how to use descriptor buffers to replace descriptor sets. - -- [Shader objects (VK_EXT_shader_object)](./examples/shaderobjects/) - - Basic sample showing how to use shader objects that can be used to replace pipeline state objects. Instead of baking all state in a PSO, shaders are explicitly loaded and bound as separate objects and state is set using dynamic state extensions. The sample also stores binary shader objets and loads them on consecutive runs. - -- [Host image copy (VK_EXT_host_image_copy)](./examples/hostimagecopy/) - - Shows how to do host image copies, which heavily simplify the host to device image process by fully skipping the staging process. - -- [Buffer device address (VK_KHR_buffer_device_addres)](./examples/bufferdeviceaddress/) - - Demonstrates the use of virtual GPU addresses to directly access buffer data in shader. Instead of e.g. using descriptors to access uniforms, with this extension you simply provide an address to the memory you want to read from in the shader and that address can be arbitrarily changed e.g. via a push constant. - -- [Timeline semaphores (VK_KHR_timeline_semaphore)](./examples/timelinesemaphore/) - - Shows how to use a new semaphore type that has a way of setting and identifying a given point on a timeline. Compared to the core binary semaphores, this simplifies synchronization as a single timeline semaphore can replace multiple binary semaphores. - -### Effects - -Assorted samples showing graphical effects not special to Vulkan. - -- [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. - -### 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. +**Common Options:** +- `-v, --validation`: Enable Vulkan validation layers (recommended for development) +- `-w, --width`: Set window width (default: 1920) +- `-h, --height`: Set window height (default: 1080) +- `-f, --fullscreen`: Start in fullscreen mode +- `-g, --gpu`: Select specific GPU device +- `--listgpus`: Display available Vulkan devices + +### Controls +- **Mouse**: Look around (standard mode) or orbit (after F key focus) +- **WASD**: Move camera +- **F**: Focus camera on selected object +- **Mouse Wheel**: Zoom (in orbit mode changes distance) +- **ImGui panels**: Adjust parameters and debug information + +**Note**: Multi-GPU systems may require using `--listgpus` and `-g` to select a compatible device. + +## Architecture + +### Core Systems +- **Application Core** (`src/core/Application.cpp`): Main Vulkan application class inheriting from VulkanExampleBase +- **Scene Management** (`src/scene/`): Hierarchical scene graph and object management +- **UI System** (`src/ui/`): ImGui-based interface with real-time parameter adjustment +- **Camera System** (`base/camera.hpp`): Advanced orbit camera with focus functionality +- **Base Framework** (`base/`): Sascha Willems' Vulkan infrastructure + +### Vulkan Features +- **Dynamic Rendering**: Uses VK_KHR_dynamic_rendering (no render passes/framebuffers) +- **Modern API Usage**: Vulkan 1.3+ practices with timeline semaphores +- **Multi-platform**: Window system integration for Windows, Linux, macOS +- **Validation Support**: Comprehensive debugging with validation layers + +### Project Structure +``` +├── src/ # Engine source code +│ ├── core/ # Core application classes +│ ├── scene/ # Scene management +│ └── ui/ # ImGui interface +├── base/ # Vulkan base framework +├── shaders/glsl/ # GLSL shader sources +├── assets/ # 3D models and textures +└── build/ # Build output directory +``` + +## Camera System + +### F Key Focus Feature +The camera system supports advanced focusing functionality: +- **Press F**: Focus camera on selected object +- **Automatic Distance**: Calculates optimal viewing distance +- **Orbit Mode**: Mouse rotation orbits around focused object center +- **Hybrid System**: Combines standard camera with orbit mode using `glm::lookAt()` + +### Implementation +- `camera.focusOnObject()`: Sets orbit center and optimal distance +- `camera.updateOrbitPosition()`: Handles spherical coordinate positioning +- Seamless switching between standard and orbit modes + +## Development + +### Testing Changes +```bash +# Build and test +cd build +cmake --build . --config Release +bin/Release/ProceduralEngine3D -v # Enable validation layers +``` + +### Debugging +- Use `-v` flag for Vulkan validation layers +- Check console output for debug information +- Use `--listgpus` to verify device support +- ImGui panels provide real-time debugging information + +### Adding Features +- Follow Sascha Willems patterns for Vulkan code +- Use dynamic rendering (no render passes required) +- Implement proper buffer management and synchronization +- Add ImGui panels for new feature parameters + +## Vulkan Examples + +This engine is built on top of Sascha Willems' comprehensive Vulkan examples. While the main engine focuses on procedural generation and scene management, the original examples remain available for learning Vulkan concepts. + +### Available Examples +The `examples/` directory contains over 100 Vulkan examples covering: + +- **Basics**: Triangle rendering, pipelines, descriptor sets, textures +- **Advanced Rendering**: Shadow mapping, deferred shading, PBR, ray tracing +- **Compute Shaders**: GPU particles, N-body simulation, image processing +- **Modern Features**: Dynamic rendering, mesh shaders, descriptor buffers +- **Extensions**: Conservative rasterization, variable rate shading, timeline semaphores + +### Official Khronos Samples +For the most up-to-date Vulkan learning resources, also check the official Khronos Vulkan Samples repository at: https://github.com/KhronosGroup/Vulkan-Samples + +### Building Individual Examples +```bash +# Build specific example (from build directory) +cmake --build . --target triangle --config Release +``` + +For a complete reference of all available examples with detailed descriptions, see the original Sascha Willems Vulkan repository documentation. Each example demonstrates specific Vulkan concepts and can be built individually using the CMake targets. ## Credits and Attributions See [CREDITS.md](CREDITS.md) for additional credits and attributions. diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index f3fc352a..00000000 --- a/android/.gitignore +++ /dev/null @@ -1,69 +0,0 @@ -# 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/ -**/.cxx/ \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index c774fed5..00000000 --- a/android/build.gradle +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de - * - * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) - */ - -/** Top-level build file where you can add configuration options common to all sub-projects/modules */ - -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.0' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - mavenCentral() - } - // This code is where all the magic happens and fixes the error. - subprojects { - afterEvaluate { project -> - if (project.hasProperty('android')) { - project.android { - if (namespace == null) { - namespace "de.saschawillems." + project.group - } - } - } - } - } -} - -ext { - abiFilters = "arm64-v8a" - minSdkVersion = 21 - targetSdkVersion = 26 - compileSdkVersion = 26 - shaderPath = '../../../shaders/' - assetPath = '../../../assets/' -} \ No newline at end of file diff --git a/android/common/res/drawable/icon.png b/android/common/res/drawable/icon.png deleted file mode 100644 index 882f2a59c04ddf1f99a5fc209d57b4472f8dbb36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25498 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 WX#|75C4Bz - - - - - - - - - - - - - - - - diff --git a/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/base/CMakeLists.txt b/android/examples/base/CMakeLists.txt deleted file mode 100644 index e564d522..00000000 --- a/android/examples/base/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -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 - ${KTX_DIR}/lib/vkloader.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/android/examples/bloom/CMakeLists.txt b/android/examples/bloom/CMakeLists.txt deleted file mode 100644 index ac3ec0e5..00000000 --- a/android/examples/bloom/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/bloom/build.gradle b/android/examples/bloom/build.gradle deleted file mode 100644 index 17ddb414..00000000 --- a/android/examples/bloom/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanBloom" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath +'glsl/bloom' - into 'assets/shaders/glsl/bloom' - include '*.*' - } - - copy { - from rootProject.ext.assetPath +'models' - into 'assets/models' - include 'retroufo.gltf' - } - - copy { - from rootProject.ext.assetPath +'models' - into 'assets/models' - include 'retroufo_glow.gltf' - } - - copy { - from rootProject.ext.assetPath +'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'cubemap_space.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/bloom/src/main/AndroidManifest.xml b/android/examples/bloom/src/main/AndroidManifest.xml deleted file mode 100644 index 233f0056..00000000 --- a/android/examples/bloom/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/bufferdeviceaddress/CMakeLists.txt b/android/examples/bufferdeviceaddress/CMakeLists.txt deleted file mode 100644 index 4f4a35e6..00000000 --- a/android/examples/bufferdeviceaddress/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME bufferdeviceaddress) - -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/android/examples/bufferdeviceaddress/build.gradle b/android/examples/bufferdeviceaddress/build.gradle deleted file mode 100644 index 9fe9a2af..00000000 --- a/android/examples/bufferdeviceaddress/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanBufferDeviceAddress" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/bufferdeviceaddress' - into 'assets/shaders/glsl/bufferdeviceaddress' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate01_color_height_rgba.ktx' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/bufferdeviceaddress/src/main/AndroidManifest.xml b/android/examples/bufferdeviceaddress/src/main/AndroidManifest.xml deleted file mode 100644 index cbc8668f..00000000 --- a/android/examples/bufferdeviceaddress/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/bufferdeviceaddress/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/bufferdeviceaddress/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/bufferdeviceaddress/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computecloth/CMakeLists.txt b/android/examples/computecloth/CMakeLists.txt deleted file mode 100644 index 0b801305..00000000 --- a/android/examples/computecloth/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computecloth/build.gradle b/android/examples/computecloth/build.gradle deleted file mode 100644 index a51092fb..00000000 --- a/android/examples/computecloth/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputecloth" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computecloth' - into 'assets/shaders/glsl/computecloth' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'vulkan_cloth_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computecloth/src/main/AndroidManifest.xml b/android/examples/computecloth/src/main/AndroidManifest.xml deleted file mode 100644 index 859de434..00000000 --- a/android/examples/computecloth/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computecullandlod/CMakeLists.txt b/android/examples/computecullandlod/CMakeLists.txt deleted file mode 100644 index 1bdf0390..00000000 --- a/android/examples/computecullandlod/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computecullandlod/build.gradle b/android/examples/computecullandlod/build.gradle deleted file mode 100644 index 173bb056..00000000 --- a/android/examples/computecullandlod/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputecullandlod" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computecullandlod' - into 'assets/shaders/glsl/computecullandlod' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'suzanne_lods.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computecullandlod/src/main/AndroidManifest.xml b/android/examples/computecullandlod/src/main/AndroidManifest.xml deleted file mode 100644 index 9e85ce96..00000000 --- a/android/examples/computecullandlod/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computeheadless/CMakeLists.txt b/android/examples/computeheadless/CMakeLists.txt deleted file mode 100644 index 4b88223b..00000000 --- a/android/examples/computeheadless/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computeheadless/build.gradle b/android/examples/computeheadless/build.gradle deleted file mode 100644 index 1c68bdcf..00000000 --- a/android/examples/computeheadless/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputeheadless" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computeheadless' - into 'assets/shaders/glsl/computeheadless' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computeheadless/src/main/AndroidManifest.xml b/android/examples/computeheadless/src/main/AndroidManifest.xml deleted file mode 100644 index da1e42b2..00000000 --- a/android/examples/computeheadless/src/main/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computenbody/CMakeLists.txt b/android/examples/computenbody/CMakeLists.txt deleted file mode 100644 index a4898de9..00000000 --- a/android/examples/computenbody/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computenbody/build.gradle b/android/examples/computenbody/build.gradle deleted file mode 100644 index 73ec7863..00000000 --- a/android/examples/computenbody/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputenbody" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computenbody' - into 'assets/shaders/glsl/computenbody' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle01_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_gradient_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computenbody/src/main/AndroidManifest.xml b/android/examples/computenbody/src/main/AndroidManifest.xml deleted file mode 100644 index ccc07122..00000000 --- a/android/examples/computenbody/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computeparticles/CMakeLists.txt b/android/examples/computeparticles/CMakeLists.txt deleted file mode 100644 index 0a0255ff..00000000 --- a/android/examples/computeparticles/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computeparticles/build.gradle b/android/examples/computeparticles/build.gradle deleted file mode 100644 index 58d054a7..00000000 --- a/android/examples/computeparticles/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputeparticles" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computeparticles' - into 'assets/shaders/glsl/computeparticles' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle01_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_gradient_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computeparticles/src/main/AndroidManifest.xml b/android/examples/computeparticles/src/main/AndroidManifest.xml deleted file mode 100644 index e7614e10..00000000 --- a/android/examples/computeparticles/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computeraytracing/CMakeLists.txt b/android/examples/computeraytracing/CMakeLists.txt deleted file mode 100644 index 801d15dd..00000000 --- a/android/examples/computeraytracing/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computeraytracing/build.gradle b/android/examples/computeraytracing/build.gradle deleted file mode 100644 index dd75ddc7..00000000 --- a/android/examples/computeraytracing/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputeRaytracing" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computeraytracing' - into 'assets/shaders/glsl/computeraytracing' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computeraytracing/src/main/AndroidManifest.xml b/android/examples/computeraytracing/src/main/AndroidManifest.xml deleted file mode 100644 index 9286fa3d..00000000 --- a/android/examples/computeraytracing/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/computeshader/CMakeLists.txt b/android/examples/computeshader/CMakeLists.txt deleted file mode 100644 index 688dd589..00000000 --- a/android/examples/computeshader/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/computeshader/build.gradle b/android/examples/computeshader/build.gradle deleted file mode 100644 index 4fe97915..00000000 --- a/android/examples/computeshader/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanComputeshader" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computeshader' - into 'assets/shaders/glsl/computeshader' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'vulkan_11_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/computeshader/src/main/AndroidManifest.xml b/android/examples/computeshader/src/main/AndroidManifest.xml deleted file mode 100644 index dbb77b9f..00000000 --- a/android/examples/computeshader/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/conditionalrender/CMakeLists.txt b/android/examples/conditionalrender/CMakeLists.txt deleted file mode 100644 index bca54576..00000000 --- a/android/examples/conditionalrender/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME conditionalrender) - -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/android/examples/conditionalrender/build.gradle b/android/examples/conditionalrender/build.gradle deleted file mode 100644 index 725a46f6..00000000 --- a/android/examples/conditionalrender/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanConditionalrender" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/conditionalrender' - into 'assets/shaders/glsl/conditionalrender' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/gltf/glTF-Embedded' - into 'assets/models/gltf/glTF-Embedded' - include 'Buggy.gltf' - } -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/conditionalrender/src/main/AndroidManifest.xml b/android/examples/conditionalrender/src/main/AndroidManifest.xml deleted file mode 100644 index 3ab90a77..00000000 --- a/android/examples/conditionalrender/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/conditionalrender/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/conditionalrender/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/conditionalrender/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/conservativeraster/CMakeLists.txt b/android/examples/conservativeraster/CMakeLists.txt deleted file mode 100644 index 70705814..00000000 --- a/android/examples/conservativeraster/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/conservativeraster/build.gradle b/android/examples/conservativeraster/build.gradle deleted file mode 100644 index b7abf2af..00000000 --- a/android/examples/conservativeraster/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanConservativeraster" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/conservativeraster' - into 'assets/shaders/glsl/conservativeraster' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/conservativeraster/src/main/AndroidManifest.xml b/android/examples/conservativeraster/src/main/AndroidManifest.xml deleted file mode 100644 index e9daac07..00000000 --- a/android/examples/conservativeraster/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/debugprintf/CMakeLists.txt b/android/examples/debugprintf/CMakeLists.txt deleted file mode 100644 index 5ce8a32e..00000000 --- a/android/examples/debugprintf/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME debugprintf) - -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/android/examples/debugprintf/build.gradle b/android/examples/debugprintf/build.gradle deleted file mode 100644 index 9a98a61c..00000000 --- a/android/examples/debugprintf/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDebugprintf" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/debugprintf' - into 'assets/shaders/glsl/debugprintf' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/debugprintf/src/main/AndroidManifest.xml b/android/examples/debugprintf/src/main/AndroidManifest.xml deleted file mode 100644 index bfb99c12..00000000 --- a/android/examples/debugprintf/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/debugprintf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/debugprintf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/debugprintf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/debugutils/CMakeLists.txt b/android/examples/debugutils/CMakeLists.txt deleted file mode 100644 index 48ba3a6f..00000000 --- a/android/examples/debugutils/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME debugutils) - -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/android/examples/debugutils/build.gradle b/android/examples/debugutils/build.gradle deleted file mode 100644 index b2a29b0a..00000000 --- a/android/examples/debugutils/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDebugutils" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/debugutils' - into 'assets/shaders/glsl/debugutils' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_glow.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/debugutils/src/main/AndroidManifest.xml b/android/examples/debugutils/src/main/AndroidManifest.xml deleted file mode 100644 index cdd22d06..00000000 --- a/android/examples/debugutils/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/debugutils/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/debugutils/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/debugutils/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/deferred/CMakeLists.txt b/android/examples/deferred/CMakeLists.txt deleted file mode 100644 index 2f9aa0ff..00000000 --- a/android/examples/deferred/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/deferred/build.gradle b/android/examples/deferred/build.gradle deleted file mode 100644 index 46226069..00000000 --- a/android/examples/deferred/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDeferred" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/deferred' - into 'assets/shaders/glsl/deferred' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'deferred_floor.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor01_color_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor01_normal_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'models/armor' - into 'assets/models/armor' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/deferred/src/main/AndroidManifest.xml b/android/examples/deferred/src/main/AndroidManifest.xml deleted file mode 100644 index b1838c4a..00000000 --- a/android/examples/deferred/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/deferredmultisampling/CMakeLists.txt b/android/examples/deferredmultisampling/CMakeLists.txt deleted file mode 100644 index 9c01e50f..00000000 --- a/android/examples/deferredmultisampling/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/deferredmultisampling/build.gradle b/android/examples/deferredmultisampling/build.gradle deleted file mode 100644 index 4d08713e..00000000 --- a/android/examples/deferredmultisampling/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDeferredmultisampling" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/deferredmultisampling' - into 'assets/shaders/glsl/deferredmultisampling' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'deferred_box.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor02_color_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor02_normal_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'models/armor' - into 'assets/models/armor' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/deferredmultisampling/src/main/AndroidManifest.xml b/android/examples/deferredmultisampling/src/main/AndroidManifest.xml deleted file mode 100644 index 49426468..00000000 --- a/android/examples/deferredmultisampling/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/deferredshadows/CMakeLists.txt b/android/examples/deferredshadows/CMakeLists.txt deleted file mode 100644 index 8523380e..00000000 --- a/android/examples/deferredshadows/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/deferredshadows/build.gradle b/android/examples/deferredshadows/build.gradle deleted file mode 100644 index 76ad5f81..00000000 --- a/android/examples/deferredshadows/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDeferredshadows" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/deferredshadows' - into 'assets/shaders/glsl/deferredshadows' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'deferred_box.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor02_color_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor02_normal_*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'models/armor' - into 'assets/models/armor' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/deferredshadows/src/main/AndroidManifest.xml b/android/examples/deferredshadows/src/main/AndroidManifest.xml deleted file mode 100644 index cdb0a40f..00000000 --- a/android/examples/deferredshadows/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/descriptorbuffer/CMakeLists.txt b/android/examples/descriptorbuffer/CMakeLists.txt deleted file mode 100644 index 8b1f3694..00000000 --- a/android/examples/descriptorbuffer/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME descriptorbuffer) - -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/android/examples/descriptorbuffer/build.gradle b/android/examples/descriptorbuffer/build.gradle deleted file mode 100644 index 54ae5720..00000000 --- a/android/examples/descriptorbuffer/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDescriptorbuffer" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/descriptorbuffer' - into 'assets/shaders/glsl/descriptorbuffer' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate01_color_height_rgba.gltf' - include 'crate02_color_height_rgba.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/descriptorbuffer/src/main/AndroidManifest.xml b/android/examples/descriptorbuffer/src/main/AndroidManifest.xml deleted file mode 100644 index 7f1039fb..00000000 --- a/android/examples/descriptorbuffer/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/descriptorbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/descriptorbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/descriptorbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/descriptorindexing/CMakeLists.txt b/android/examples/descriptorindexing/CMakeLists.txt deleted file mode 100644 index 69a813ea..00000000 --- a/android/examples/descriptorindexing/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/descriptorindexing/build.gradle b/android/examples/descriptorindexing/build.gradle deleted file mode 100644 index a56ebce4..00000000 --- a/android/examples/descriptorindexing/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDescriptorindexing" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/descriptorindexing' - into 'assets/shaders/glsl/descriptorindexing' - include '*.*' - } -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/descriptorindexing/src/main/AndroidManifest.xml b/android/examples/descriptorindexing/src/main/AndroidManifest.xml deleted file mode 100644 index a85c1566..00000000 --- a/android/examples/descriptorindexing/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/descriptorsets/CMakeLists.txt b/android/examples/descriptorsets/CMakeLists.txt deleted file mode 100644 index 639667ea..00000000 --- a/android/examples/descriptorsets/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/descriptorsets/build.gradle b/android/examples/descriptorsets/build.gradle deleted file mode 100644 index 7ea2181b..00000000 --- a/android/examples/descriptorsets/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDescriptorsets" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/descriptorsets' - into 'assets/shaders/glsl/descriptorsets' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate01_color_height_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate02_color_height_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/descriptorsets/src/main/AndroidManifest.xml b/android/examples/descriptorsets/src/main/AndroidManifest.xml deleted file mode 100644 index 0dac572e..00000000 --- a/android/examples/descriptorsets/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/displacement/CMakeLists.txt b/android/examples/displacement/CMakeLists.txt deleted file mode 100644 index 300c9dd4..00000000 --- a/android/examples/displacement/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/displacement/build.gradle b/android/examples/displacement/build.gradle deleted file mode 100644 index 0cd19008..00000000 --- a/android/examples/displacement/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDisplacement" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/displacement' - into 'assets/shaders/glsl/displacement' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'displacement_plane.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'stonefloor03_color_height_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/displacement/src/main/AndroidManifest.xml b/android/examples/displacement/src/main/AndroidManifest.xml deleted file mode 100644 index 3b6ca22b..00000000 --- a/android/examples/displacement/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/distancefieldfonts/CMakeLists.txt b/android/examples/distancefieldfonts/CMakeLists.txt deleted file mode 100644 index 7694a122..00000000 --- a/android/examples/distancefieldfonts/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/distancefieldfonts/build.gradle b/android/examples/distancefieldfonts/build.gradle deleted file mode 100644 index 8dc4bb04..00000000 --- a/android/examples/distancefieldfonts/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDistancefieldfonts" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/distancefieldfonts' - into 'assets/shaders/glsl/distancefieldfonts' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'font_sdf_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'font_bitmap_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + './' - into 'assets/./' - include 'font.fnt' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/distancefieldfonts/src/main/AndroidManifest.xml b/android/examples/distancefieldfonts/src/main/AndroidManifest.xml deleted file mode 100644 index 2a844fd4..00000000 --- a/android/examples/distancefieldfonts/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/dynamicrendering/CMakeLists.txt b/android/examples/dynamicrendering/CMakeLists.txt deleted file mode 100644 index 0611870c..00000000 --- a/android/examples/dynamicrendering/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME dynamicrendering) - -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/android/examples/dynamicrendering/build.gradle b/android/examples/dynamicrendering/build.gradle deleted file mode 100644 index 70825b84..00000000 --- a/android/examples/dynamicrendering/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDynamicrendering" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/dynamicrendering' - into 'assets/shaders/glsl/dynamicrendering' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'voyager.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/dynamicrendering/src/main/AndroidManifest.xml b/android/examples/dynamicrendering/src/main/AndroidManifest.xml deleted file mode 100644 index d6c17910..00000000 --- a/android/examples/dynamicrendering/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/dynamicrendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/dynamicrendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/dynamicrendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/dynamicrenderingmultisampling/CMakeLists.txt b/android/examples/dynamicrenderingmultisampling/CMakeLists.txt deleted file mode 100644 index 256b5b4f..00000000 --- a/android/examples/dynamicrenderingmultisampling/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME dynamicrenderingmultisampling) - -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/android/examples/dynamicrenderingmultisampling/build.gradle b/android/examples/dynamicrenderingmultisampling/build.gradle deleted file mode 100644 index de7f4824..00000000 --- a/android/examples/dynamicrenderingmultisampling/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDynamicrenderingmulitsampling" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/dynamicrendering' - into 'assets/shaders/glsl/dynamicrendering' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'voyager.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml b/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml deleted file mode 100644 index ccefddb6..00000000 --- a/android/examples/dynamicrenderingmultisampling/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/dynamicrenderingmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/dynamicstate/CMakeLists.txt b/android/examples/dynamicstate/CMakeLists.txt deleted file mode 100644 index 67dfc8b7..00000000 --- a/android/examples/dynamicstate/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME dynamicstate) - -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/android/examples/dynamicstate/build.gradle b/android/examples/dynamicstate/build.gradle deleted file mode 100644 index eef78dc5..00000000 --- a/android/examples/dynamicstate/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDynamicstate" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pipelines' - into 'assets/shaders/glsl/pipelines' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/dynamicstate/src/main/AndroidManifest.xml b/android/examples/dynamicstate/src/main/AndroidManifest.xml deleted file mode 100644 index b1798ce7..00000000 --- a/android/examples/dynamicstate/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/dynamicstate/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/dynamicstate/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/dynamicstate/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/dynamicuniformbuffer/CMakeLists.txt b/android/examples/dynamicuniformbuffer/CMakeLists.txt deleted file mode 100644 index 80540406..00000000 --- a/android/examples/dynamicuniformbuffer/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/dynamicuniformbuffer/build.gradle b/android/examples/dynamicuniformbuffer/build.gradle deleted file mode 100644 index c743d4bb..00000000 --- a/android/examples/dynamicuniformbuffer/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanDynamicuniformbuffer" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/dynamicuniformbuffer' - into 'assets/shaders/glsl/dynamicuniformbuffer' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml b/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml deleted file mode 100644 index 171ffc86..00000000 --- a/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/gears/CMakeLists.txt b/android/examples/gears/CMakeLists.txt deleted file mode 100644 index c91b527a..00000000 --- a/android/examples/gears/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/gears/build.gradle b/android/examples/gears/build.gradle deleted file mode 100644 index 770a11d1..00000000 --- a/android/examples/gears/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanGears" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/gears' - into 'assets/shaders/glsl/gears' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/gears/src/main/AndroidManifest.xml b/android/examples/gears/src/main/AndroidManifest.xml deleted file mode 100644 index da134b43..00000000 --- a/android/examples/gears/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/geometryshader/CMakeLists.txt b/android/examples/geometryshader/CMakeLists.txt deleted file mode 100644 index a743e06a..00000000 --- a/android/examples/geometryshader/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/geometryshader/build.gradle b/android/examples/geometryshader/build.gradle deleted file mode 100644 index bddb5ab8..00000000 --- a/android/examples/geometryshader/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanGeometryshader" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/geometryshader' - into 'assets/shaders/glsl/geometryshader' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'suzanne.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/geometryshader/src/main/AndroidManifest.xml b/android/examples/geometryshader/src/main/AndroidManifest.xml deleted file mode 100644 index dfbfd2ca..00000000 --- a/android/examples/geometryshader/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/gltfloading/CMakeLists.txt b/android/examples/gltfloading/CMakeLists.txt deleted file mode 100644 index 47229876..00000000 --- a/android/examples/gltfloading/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/gltfloading/build.gradle b/android/examples/gltfloading/build.gradle deleted file mode 100644 index d8327e82..00000000 --- a/android/examples/gltfloading/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanglTFScene" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/gltfloading' - into 'assets/shaders/glsl/gltfloading' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/FlightHelmet/glTF' - into 'assets/models/FlightHelmet/glTF' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/gltfloading/src/main/AndroidManifest.xml b/android/examples/gltfloading/src/main/AndroidManifest.xml deleted file mode 100644 index 090e46c7..00000000 --- a/android/examples/gltfloading/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/gltfscenerendering/CMakeLists.txt b/android/examples/gltfscenerendering/CMakeLists.txt deleted file mode 100644 index e73fa443..00000000 --- a/android/examples/gltfscenerendering/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/gltfscenerendering/build.gradle b/android/examples/gltfscenerendering/build.gradle deleted file mode 100644 index 34660ebc..00000000 --- a/android/examples/gltfscenerendering/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanScenerendering" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath +'glsl/gltfscenerendering' - into 'assets/shaders/glsl/gltfscenerendering' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/sponza' - into 'assets/models/sponza' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/gltfscenerendering/src/main/AndroidManifest.xml b/android/examples/gltfscenerendering/src/main/AndroidManifest.xml deleted file mode 100644 index 6ce148be..00000000 --- a/android/examples/gltfscenerendering/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/gltfskinning/CMakeLists.txt b/android/examples/gltfskinning/CMakeLists.txt deleted file mode 100644 index 32e4f6b1..00000000 --- a/android/examples/gltfskinning/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/gltfskinning/build.gradle b/android/examples/gltfskinning/build.gradle deleted file mode 100644 index 001d53b6..00000000 --- a/android/examples/gltfskinning/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanglTFSkinning" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into "assets/shaders/glsl/base" - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/gltfskinning' - into 'assets/shaders/glsl/gltfskinning' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/CesiumMan/glTF' - into 'assets/models/CesiumMan/glTF' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/gltfskinning/src/main/AndroidManifest.xml b/android/examples/gltfskinning/src/main/AndroidManifest.xml deleted file mode 100644 index a9d112f1..00000000 --- a/android/examples/gltfskinning/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/gradle/outputfilename.gradle b/android/examples/gradle/outputfilename.gradle deleted file mode 100644 index e71624b3..00000000 --- a/android/examples/gradle/outputfilename.gradle +++ /dev/null @@ -1,7 +0,0 @@ -android { - applicationVariants.all { variant -> - variant.outputs.all { - outputFileName = "../../../../../bin/" + outputFileName - } - } -} \ No newline at end of file diff --git a/android/examples/graphicspipelinelibrary/CMakeLists.txt b/android/examples/graphicspipelinelibrary/CMakeLists.txt deleted file mode 100644 index d789ea68..00000000 --- a/android/examples/graphicspipelinelibrary/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME graphicspipelinelibrary) - -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/android/examples/graphicspipelinelibrary/build.gradle b/android/examples/graphicspipelinelibrary/build.gradle deleted file mode 100644 index 71c17e81..00000000 --- a/android/examples/graphicspipelinelibrary/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanGraphicspipelinelibrary" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/graphicspipelinelibrary' - into 'assets/shaders/glsl/graphicspipelinelibrary' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'color_teapot_spheres.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/graphicspipelinelibrary/src/main/AndroidManifest.xml b/android/examples/graphicspipelinelibrary/src/main/AndroidManifest.xml deleted file mode 100644 index 3a2b38ab..00000000 --- a/android/examples/graphicspipelinelibrary/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/graphicspipelinelibrary/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/graphicspipelinelibrary/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/graphicspipelinelibrary/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/hdr/CMakeLists.txt b/android/examples/hdr/CMakeLists.txt deleted file mode 100644 index 6e84bcef..00000000 --- a/android/examples/hdr/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/hdr/build.gradle b/android/examples/hdr/build.gradle deleted file mode 100644 index 76404a06..00000000 --- a/android/examples/hdr/build.gradle +++ /dev/null @@ -1,96 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanHDR" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/hdr' - into 'assets/shaders/glsl/hdr' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures/hdr' - into 'assets/textures/hdr' - include 'uffizi_cube.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/hdr/src/main/AndroidManifest.xml b/android/examples/hdr/src/main/AndroidManifest.xml deleted file mode 100644 index f2604321..00000000 --- a/android/examples/hdr/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/hostimagecopy/CMakeLists.txt b/android/examples/hostimagecopy/CMakeLists.txt deleted file mode 100644 index cdee596e..00000000 --- a/android/examples/hostimagecopy/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME hostimagecopy) - -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/android/examples/hostimagecopy/build.gradle b/android/examples/hostimagecopy/build.gradle deleted file mode 100644 index e59aba39..00000000 --- a/android/examples/hostimagecopy/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanHostImageCopy" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texture' - into 'assets/shaders/glsl/texture' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'metalplate01_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane_z.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/hostimagecopy/src/main/AndroidManifest.xml b/android/examples/hostimagecopy/src/main/AndroidManifest.xml deleted file mode 100644 index fcd8d7c5..00000000 --- a/android/examples/hostimagecopy/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/hostimagecopy/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/hostimagecopy/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/hostimagecopy/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/imgui/CMakeLists.txt b/android/examples/imgui/CMakeLists.txt deleted file mode 100644 index 6ee5d400..00000000 --- a/android/examples/imgui/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/imgui/build.gradle b/android/examples/imgui/build.gradle deleted file mode 100644 index 8e46075f..00000000 --- a/android/examples/imgui/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanImGui" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/imgui' - into 'assets/shaders/glsl/imgui' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenemodels.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenebackground.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenelogos.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/imgui/src/main/AndroidManifest.xml b/android/examples/imgui/src/main/AndroidManifest.xml deleted file mode 100644 index 1b4f2ed1..00000000 --- a/android/examples/imgui/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/indirectdraw/CMakeLists.txt b/android/examples/indirectdraw/CMakeLists.txt deleted file mode 100644 index 5480399b..00000000 --- a/android/examples/indirectdraw/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/indirectdraw/build.gradle b/android/examples/indirectdraw/build.gradle deleted file mode 100644 index e21c98dd..00000000 --- a/android/examples/indirectdraw/build.gradle +++ /dev/null @@ -1,90 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanIndirectdraw" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/indirectdraw' - into 'assets/shaders/glsl/indirectdraw' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plants.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane_circle.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'texturearray_plants_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'ground_dry_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/indirectdraw/src/main/AndroidManifest.xml b/android/examples/indirectdraw/src/main/AndroidManifest.xml deleted file mode 100644 index 60fa8e14..00000000 --- a/android/examples/indirectdraw/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/inlineuniformblocks/CMakeLists.txt b/android/examples/inlineuniformblocks/CMakeLists.txt deleted file mode 100644 index 276636c6..00000000 --- a/android/examples/inlineuniformblocks/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/inlineuniformblocks/build.gradle b/android/examples/inlineuniformblocks/build.gradle deleted file mode 100644 index 0892b747..00000000 --- a/android/examples/inlineuniformblocks/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanInlineuniformblocks" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/inlineuniformblocks' - into 'assets/shaders/glsl/inlineuniformblocks' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml b/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml deleted file mode 100644 index e9745391..00000000 --- a/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/inputattachments/CMakeLists.txt b/android/examples/inputattachments/CMakeLists.txt deleted file mode 100644 index d07704e2..00000000 --- a/android/examples/inputattachments/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/inputattachments/build.gradle b/android/examples/inputattachments/build.gradle deleted file mode 100644 index 9dace85d..00000000 --- a/android/examples/inputattachments/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanInputattachments" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/inputattachments' - into 'assets/shaders/glsl/inputattachments' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/inputattachments/src/main/AndroidManifest.xml b/android/examples/inputattachments/src/main/AndroidManifest.xml deleted file mode 100644 index 0cfc68ca..00000000 --- a/android/examples/inputattachments/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/instancing/CMakeLists.txt b/android/examples/instancing/CMakeLists.txt deleted file mode 100644 index 0949f09c..00000000 --- a/android/examples/instancing/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/instancing/build.gradle b/android/examples/instancing/build.gradle deleted file mode 100644 index 597f79c0..00000000 --- a/android/examples/instancing/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanInstancing" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/instancing' - into 'assets/shaders/glsl/instancing' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'rock01.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'lavaplanet.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'texturearray_rocks*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'lavaplanet*.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/instancing/src/main/AndroidManifest.xml b/android/examples/instancing/src/main/AndroidManifest.xml deleted file mode 100644 index 6a134704..00000000 --- a/android/examples/instancing/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/meshshader/CMakeLists.txt b/android/examples/meshshader/CMakeLists.txt deleted file mode 100644 index f6f0f28d..00000000 --- a/android/examples/meshshader/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME meshshader) - -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/android/examples/meshshader/build.gradle b/android/examples/meshshader/build.gradle deleted file mode 100644 index 81e112cd..00000000 --- a/android/examples/meshshader/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanMeshshader" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/meshshader' - into 'assets/shaders/glsl/meshshader' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/meshshader/src/main/AndroidManifest.xml b/android/examples/meshshader/src/main/AndroidManifest.xml deleted file mode 100644 index 89ad0f7a..00000000 --- a/android/examples/meshshader/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/meshshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/meshshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/meshshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/multisampling/CMakeLists.txt b/android/examples/multisampling/CMakeLists.txt deleted file mode 100644 index 7dd374c7..00000000 --- a/android/examples/multisampling/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/multisampling/build.gradle b/android/examples/multisampling/build.gradle deleted file mode 100644 index c0c620bb..00000000 --- a/android/examples/multisampling/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanMultisampling" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/multisampling' - into 'assets/shaders/glsl/multisampling' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'voyager.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/multisampling/src/main/AndroidManifest.xml b/android/examples/multisampling/src/main/AndroidManifest.xml deleted file mode 100644 index aa113840..00000000 --- a/android/examples/multisampling/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/multithreading/CMakeLists.txt b/android/examples/multithreading/CMakeLists.txt deleted file mode 100644 index e7e07b83..00000000 --- a/android/examples/multithreading/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/multithreading/build.gradle b/android/examples/multithreading/build.gradle deleted file mode 100644 index fbd26b6c..00000000 --- a/android/examples/multithreading/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanMultithreading" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/multithreading' - into 'assets/shaders/glsl/multithreading' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'retroufo_red_lowpoly.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/multithreading/src/main/AndroidManifest.xml b/android/examples/multithreading/src/main/AndroidManifest.xml deleted file mode 100644 index f002469a..00000000 --- a/android/examples/multithreading/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/multiview/CMakeLists.txt b/android/examples/multiview/CMakeLists.txt deleted file mode 100644 index 5280226c..00000000 --- a/android/examples/multiview/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/multiview/build.gradle b/android/examples/multiview/build.gradle deleted file mode 100644 index d2acf842..00000000 --- a/android/examples/multiview/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanMultiview" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/multiview' - into 'assets/shaders/glsl/multiview' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sampleroom.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/multiview/src/main/AndroidManifest.xml b/android/examples/multiview/src/main/AndroidManifest.xml deleted file mode 100644 index 274906e2..00000000 --- a/android/examples/multiview/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/negativeviewportheight/CMakeLists.txt b/android/examples/negativeviewportheight/CMakeLists.txt deleted file mode 100644 index 9554a347..00000000 --- a/android/examples/negativeviewportheight/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/negativeviewportheight/build.gradle b/android/examples/negativeviewportheight/build.gradle deleted file mode 100644 index 85b9642c..00000000 --- a/android/examples/negativeviewportheight/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanNegativeviewportheight" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/negativeviewportheight' - into 'assets/shaders/glsl/negativeviewportheight' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'texture_orientation_ccw_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'texture_orientation_cw_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/negativeviewportheight/src/main/AndroidManifest.xml b/android/examples/negativeviewportheight/src/main/AndroidManifest.xml deleted file mode 100644 index 3455af63..00000000 --- a/android/examples/negativeviewportheight/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/occlusionquery/CMakeLists.txt b/android/examples/occlusionquery/CMakeLists.txt deleted file mode 100644 index b33dbdcb..00000000 --- a/android/examples/occlusionquery/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/occlusionquery/build.gradle b/android/examples/occlusionquery/build.gradle deleted file mode 100644 index 9eaa7e0c..00000000 --- a/android/examples/occlusionquery/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanOcclusionquery" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/occlusionquery' - into 'assets/shaders/glsl/occlusionquery' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane_z.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/occlusionquery/src/main/AndroidManifest.xml b/android/examples/occlusionquery/src/main/AndroidManifest.xml deleted file mode 100644 index ab7386b9..00000000 --- a/android/examples/occlusionquery/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/offscreen/CMakeLists.txt b/android/examples/offscreen/CMakeLists.txt deleted file mode 100644 index 35f62af4..00000000 --- a/android/examples/offscreen/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/offscreen/build.gradle b/android/examples/offscreen/build.gradle deleted file mode 100644 index 9b700c7c..00000000 --- a/android/examples/offscreen/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanOffscreen" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/offscreen' - into 'assets/shaders/glsl/offscreen' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'chinesedragon.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/offscreen/src/main/AndroidManifest.xml b/android/examples/offscreen/src/main/AndroidManifest.xml deleted file mode 100644 index 97f3ce24..00000000 --- a/android/examples/offscreen/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/oit/CMakeLists.txt b/android/examples/oit/CMakeLists.txt deleted file mode 100644 index 86202e17..00000000 --- a/android/examples/oit/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/oit/build.gradle b/android/examples/oit/build.gradle deleted file mode 100644 index 28922c1d..00000000 --- a/android/examples/oit/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanOrderIndependentTransparency" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/oit' - into 'assets/shaders/glsl/oit' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/oit/src/main/AndroidManifest.xml b/android/examples/oit/src/main/AndroidManifest.xml deleted file mode 100644 index 994c2877..00000000 --- a/android/examples/oit/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/parallaxmapping/CMakeLists.txt b/android/examples/parallaxmapping/CMakeLists.txt deleted file mode 100644 index a3204944..00000000 --- a/android/examples/parallaxmapping/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/parallaxmapping/build.gradle b/android/examples/parallaxmapping/build.gradle deleted file mode 100644 index 68100a2a..00000000 --- a/android/examples/parallaxmapping/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanParallaxmapping" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/parallaxmapping' - into 'assets/shaders/glsl/parallaxmapping' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'rocks_normal_height_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'rocks_color*.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/parallaxmapping/src/main/AndroidManifest.xml b/android/examples/parallaxmapping/src/main/AndroidManifest.xml deleted file mode 100644 index a2b56a7a..00000000 --- a/android/examples/parallaxmapping/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/particlesystem/CMakeLists.txt b/android/examples/particlesystem/CMakeLists.txt deleted file mode 100644 index 99a3861e..00000000 --- a/android/examples/particlesystem/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME particlesystem) - -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/android/examples/particlesystem/build.gradle b/android/examples/particlesystem/build.gradle deleted file mode 100644 index 054da9c9..00000000 --- a/android/examples/particlesystem/build.gradle +++ /dev/null @@ -1,90 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanParticlesystem" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/particlesystem' - into 'assets/shaders/glsl/particlesystem' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'fireplace.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_fire.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_smoke.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'fireplace_normalmap*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'fireplace_colormap*.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/particlesystem/src/main/AndroidManifest.xml b/android/examples/particlesystem/src/main/AndroidManifest.xml deleted file mode 100644 index 51a08ade..00000000 --- a/android/examples/particlesystem/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/particlesystem/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/particlesystem/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/particlesystem/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pbrbasic/CMakeLists.txt b/android/examples/pbrbasic/CMakeLists.txt deleted file mode 100644 index fccf7131..00000000 --- a/android/examples/pbrbasic/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pbrbasic/build.gradle b/android/examples/pbrbasic/build.gradle deleted file mode 100644 index 1dd8a954..00000000 --- a/android/examples/pbrbasic/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPBRBasic" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pbrbasic' - into 'assets/shaders/glsl/pbrbasic' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pbrbasic/src/main/AndroidManifest.xml b/android/examples/pbrbasic/src/main/AndroidManifest.xml deleted file mode 100644 index 38e5241a..00000000 --- a/android/examples/pbrbasic/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pbribl/CMakeLists.txt b/android/examples/pbribl/CMakeLists.txt deleted file mode 100644 index daaf7c9c..00000000 --- a/android/examples/pbribl/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pbribl/build.gradle b/android/examples/pbribl/build.gradle deleted file mode 100644 index a2661f5f..00000000 --- a/android/examples/pbribl/build.gradle +++ /dev/null @@ -1,96 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPBRIBL" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pbribl' - into 'assets/shaders/glsl/pbribl' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures/hdr' - into 'assets/textures/hdr' - include 'pisa_cube.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pbribl/src/main/AndroidManifest.xml b/android/examples/pbribl/src/main/AndroidManifest.xml deleted file mode 100644 index af6d2595..00000000 --- a/android/examples/pbribl/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pbrtexture/CMakeLists.txt b/android/examples/pbrtexture/CMakeLists.txt deleted file mode 100644 index 1f08cd14..00000000 --- a/android/examples/pbrtexture/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pbrtexture/build.gradle b/android/examples/pbrtexture/build.gradle deleted file mode 100644 index d1ca4b6f..00000000 --- a/android/examples/pbrtexture/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPBRTexture" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pbrtexture' - into 'assets/shaders/glsl/pbrtexture' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures/hdr' - into 'assets/textures/hdr' - include 'gcanyon_cube.ktx' - } - - copy { - from rootProject.ext.assetPath + 'models/cerberus' - into 'assets/models/cerberus' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pbrtexture/src/main/AndroidManifest.xml b/android/examples/pbrtexture/src/main/AndroidManifest.xml deleted file mode 100644 index 2f048ce4..00000000 --- a/android/examples/pbrtexture/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pipelines/CMakeLists.txt b/android/examples/pipelines/CMakeLists.txt deleted file mode 100644 index b9e2e61d..00000000 --- a/android/examples/pipelines/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pipelines/build.gradle b/android/examples/pipelines/build.gradle deleted file mode 100644 index 5ea5befa..00000000 --- a/android/examples/pipelines/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPipelines" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pipelines' - into 'assets/shaders/glsl/pipelines' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pipelines/src/main/AndroidManifest.xml b/android/examples/pipelines/src/main/AndroidManifest.xml deleted file mode 100644 index 6fb6beed..00000000 --- a/android/examples/pipelines/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pipelinestatistics/CMakeLists.txt b/android/examples/pipelinestatistics/CMakeLists.txt deleted file mode 100644 index c2bbec2b..00000000 --- a/android/examples/pipelinestatistics/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pipelinestatistics/build.gradle b/android/examples/pipelinestatistics/build.gradle deleted file mode 100644 index 37a65c3e..00000000 --- a/android/examples/pipelinestatistics/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPipelinestatistics" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pipelinestatistics' - into 'assets/shaders/glsl/pipelinestatistics' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pipelinestatistics/src/main/AndroidManifest.xml b/android/examples/pipelinestatistics/src/main/AndroidManifest.xml deleted file mode 100644 index 383bdc9a..00000000 --- a/android/examples/pipelinestatistics/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pushconstants/CMakeLists.txt b/android/examples/pushconstants/CMakeLists.txt deleted file mode 100644 index 349ad3c1..00000000 --- a/android/examples/pushconstants/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pushconstants/build.gradle b/android/examples/pushconstants/build.gradle deleted file mode 100644 index 7bc6f50e..00000000 --- a/android/examples/pushconstants/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPushconstants" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pushconstants' - into 'assets/shaders/glsl/pushconstants' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'samplescene.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pushconstants/src/main/AndroidManifest.xml b/android/examples/pushconstants/src/main/AndroidManifest.xml deleted file mode 100644 index ae50caea..00000000 --- a/android/examples/pushconstants/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/pushdescriptors/CMakeLists.txt b/android/examples/pushdescriptors/CMakeLists.txt deleted file mode 100644 index cecf5905..00000000 --- a/android/examples/pushdescriptors/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/pushdescriptors/build.gradle b/android/examples/pushdescriptors/build.gradle deleted file mode 100644 index 87155a70..00000000 --- a/android/examples/pushdescriptors/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanPushdescriptors" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/pushdescriptors' - into 'assets/shaders/glsl/pushdescriptors' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate01_color_height_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'crate02_color_height_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/pushdescriptors/src/main/AndroidManifest.xml b/android/examples/pushdescriptors/src/main/AndroidManifest.xml deleted file mode 100644 index f16f8336..00000000 --- a/android/examples/pushdescriptors/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/radialblur/CMakeLists.txt b/android/examples/radialblur/CMakeLists.txt deleted file mode 100644 index b684bad9..00000000 --- a/android/examples/radialblur/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/radialblur/build.gradle b/android/examples/radialblur/build.gradle deleted file mode 100644 index fcfbe6d1..00000000 --- a/android/examples/radialblur/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRadialblur" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/radialblur' - into 'assets/shaders/glsl/radialblur' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'glowsphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_gradient_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/radialblur/src/main/AndroidManifest.xml b/android/examples/radialblur/src/main/AndroidManifest.xml deleted file mode 100644 index fc5cbf05..00000000 --- a/android/examples/radialblur/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/rayquery/CMakeLists.txt b/android/examples/rayquery/CMakeLists.txt deleted file mode 100644 index 4d29127a..00000000 --- a/android/examples/rayquery/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME rayquery) - -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/android/examples/rayquery/build.gradle b/android/examples/rayquery/build.gradle deleted file mode 100644 index 75b084ca..00000000 --- a/android/examples/rayquery/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRayQuery" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/rayquery' - into 'assets/shaders/glsl/rayquery' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscene_shadow.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/rayquery/src/main/AndroidManifest.xml b/android/examples/rayquery/src/main/AndroidManifest.xml deleted file mode 100644 index c39760fa..00000000 --- a/android/examples/rayquery/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/rayquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/rayquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/rayquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingbasic/CMakeLists.txt b/android/examples/raytracingbasic/CMakeLists.txt deleted file mode 100644 index 25138d07..00000000 --- a/android/examples/raytracingbasic/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/raytracingbasic/build.gradle b/android/examples/raytracingbasic/build.gradle deleted file mode 100644 index 3e2cabc9..00000000 --- a/android/examples/raytracingbasic/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingbasic" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingbasic' - into 'assets/shaders/glsl/raytracingbasic' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingbasic/src/main/AndroidManifest.xml b/android/examples/raytracingbasic/src/main/AndroidManifest.xml deleted file mode 100644 index 0336f20c..00000000 --- a/android/examples/raytracingbasic/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingcallable/CMakeLists.txt b/android/examples/raytracingcallable/CMakeLists.txt deleted file mode 100644 index 02fbd15d..00000000 --- a/android/examples/raytracingcallable/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracingcallable) - -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/android/examples/raytracingcallable/build.gradle b/android/examples/raytracingcallable/build.gradle deleted file mode 100644 index ce9a314e..00000000 --- a/android/examples/raytracingcallable/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingcallable" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingcallable' - into 'assets/shaders/glsl/raytracingcallable' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingcallable/src/main/AndroidManifest.xml b/android/examples/raytracingcallable/src/main/AndroidManifest.xml deleted file mode 100644 index 48486a72..00000000 --- a/android/examples/raytracingcallable/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingcallable/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingcallable/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingcallable/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracinggltf/CMakeLists.txt b/android/examples/raytracinggltf/CMakeLists.txt deleted file mode 100644 index bfe906f1..00000000 --- a/android/examples/raytracinggltf/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracinggltf) - -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/android/examples/raytracinggltf/build.gradle b/android/examples/raytracinggltf/build.gradle deleted file mode 100644 index 2f8df33e..00000000 --- a/android/examples/raytracinggltf/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracinggltf" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracinggltf' - into 'assets/shaders/glsl/raytracinggltf' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/FlightHelmet/glTF' - into 'assets/models/FlightHelmet/glTF' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracinggltf/src/main/AndroidManifest.xml b/android/examples/raytracinggltf/src/main/AndroidManifest.xml deleted file mode 100644 index 8814f938..00000000 --- a/android/examples/raytracinggltf/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/examples/raytracinggltf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracinggltf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracinggltf/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingintersection/CMakeLists.txt b/android/examples/raytracingintersection/CMakeLists.txt deleted file mode 100644 index 5ae76ede..00000000 --- a/android/examples/raytracingintersection/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracingintersection) - -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/android/examples/raytracingintersection/build.gradle b/android/examples/raytracingintersection/build.gradle deleted file mode 100644 index d0219af5..00000000 --- a/android/examples/raytracingintersection/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingintersection" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingintersection' - into 'assets/shaders/glsl/raytracingintersection' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingintersection/src/main/AndroidManifest.xml b/android/examples/raytracingintersection/src/main/AndroidManifest.xml deleted file mode 100644 index 79217a3d..00000000 --- a/android/examples/raytracingintersection/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingintersection/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingintersection/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingintersection/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingpositionfetch/CMakeLists.txt b/android/examples/raytracingpositionfetch/CMakeLists.txt deleted file mode 100644 index 04f57764..00000000 --- a/android/examples/raytracingpositionfetch/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracingpositionfetch) - -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/android/examples/raytracingpositionfetch/build.gradle b/android/examples/raytracingpositionfetch/build.gradle deleted file mode 100644 index b70bb790..00000000 --- a/android/examples/raytracingpositionfetch/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingpositionfetch" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingpositionfetch' - into 'assets/shaders/glsl/raytracingpositionfetch' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingpositionfetch/src/main/AndroidManifest.xml b/android/examples/raytracingpositionfetch/src/main/AndroidManifest.xml deleted file mode 100644 index 9083b536..00000000 --- a/android/examples/raytracingpositionfetch/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingpositionfetch/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingpositionfetch/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingpositionfetch/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingreflections/CMakeLists.txt b/android/examples/raytracingreflections/CMakeLists.txt deleted file mode 100644 index cba5509a..00000000 --- a/android/examples/raytracingreflections/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/raytracingreflections/build.gradle b/android/examples/raytracingreflections/build.gradle deleted file mode 100644 index 55c50d32..00000000 --- a/android/examples/raytracingreflections/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingreflections" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingreflections' - into 'assets/shaders/glsl/raytracingreflections' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'reflection_scene.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingreflections/src/main/AndroidManifest.xml b/android/examples/raytracingreflections/src/main/AndroidManifest.xml deleted file mode 100644 index 1fe9d2d3..00000000 --- a/android/examples/raytracingreflections/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingsbtdata/CMakeLists.txt b/android/examples/raytracingsbtdata/CMakeLists.txt deleted file mode 100644 index 103555e5..00000000 --- a/android/examples/raytracingsbtdata/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracingsbtdata) - -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/android/examples/raytracingsbtdata/build.gradle b/android/examples/raytracingsbtdata/build.gradle deleted file mode 100644 index b495acd8..00000000 --- a/android/examples/raytracingsbtdata/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingsbtdata" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingsbtdata' - into 'assets/shaders/glsl/raytracingsbtdata' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingsbtdata/src/main/AndroidManifest.xml b/android/examples/raytracingsbtdata/src/main/AndroidManifest.xml deleted file mode 100644 index 75e7fea6..00000000 --- a/android/examples/raytracingsbtdata/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingsbtdata/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingsbtdata/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingsbtdata/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingshadows/CMakeLists.txt b/android/examples/raytracingshadows/CMakeLists.txt deleted file mode 100644 index d273e282..00000000 --- a/android/examples/raytracingshadows/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/raytracingshadows/build.gradle b/android/examples/raytracingshadows/build.gradle deleted file mode 100644 index e951cba9..00000000 --- a/android/examples/raytracingshadows/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingshadows" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingshadows' - into 'assets/shaders/glsl/raytracingshadows' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscene_shadow.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingshadows/src/main/AndroidManifest.xml b/android/examples/raytracingshadows/src/main/AndroidManifest.xml deleted file mode 100644 index 9888eec4..00000000 --- a/android/examples/raytracingshadows/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/raytracingtextures/CMakeLists.txt b/android/examples/raytracingtextures/CMakeLists.txt deleted file mode 100644 index cf3fad0c..00000000 --- a/android/examples/raytracingtextures/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME raytracingtextures) - -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/android/examples/raytracingtextures/build.gradle b/android/examples/raytracingtextures/build.gradle deleted file mode 100644 index d9abe667..00000000 --- a/android/examples/raytracingtextures/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRaytracingtextures" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/raytracingtextures' - into 'assets/shaders/glsl/raytracingtextures' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'gratefloor_rgba.ktx' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/raytracingtextures/src/main/AndroidManifest.xml b/android/examples/raytracingtextures/src/main/AndroidManifest.xml deleted file mode 100644 index 6d1b12e2..00000000 --- a/android/examples/raytracingtextures/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/raytracingtextures/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/raytracingtextures/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/raytracingtextures/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/renderheadless/CMakeLists.txt b/android/examples/renderheadless/CMakeLists.txt deleted file mode 100644 index 6e7b3bb1..00000000 --- a/android/examples/renderheadless/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/renderheadless/build.gradle b/android/examples/renderheadless/build.gradle deleted file mode 100644 index 87bc76e8..00000000 --- a/android/examples/renderheadless/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanRenderheadless" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/renderheadless' - into 'assets/shaders/glsl/renderheadless' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/renderheadless/src/main/AndroidManifest.xml b/android/examples/renderheadless/src/main/AndroidManifest.xml deleted file mode 100644 index 918f513b..00000000 --- a/android/examples/renderheadless/src/main/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/screenshot/CMakeLists.txt b/android/examples/screenshot/CMakeLists.txt deleted file mode 100644 index 1ef51c7d..00000000 --- a/android/examples/screenshot/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/screenshot/build.gradle b/android/examples/screenshot/build.gradle deleted file mode 100644 index 196a6aa9..00000000 --- a/android/examples/screenshot/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanScreenshot" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/screenshot' - into 'assets/shaders/glsl/screenshot' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'chinesedragon.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/screenshot/src/main/AndroidManifest.xml b/android/examples/screenshot/src/main/AndroidManifest.xml deleted file mode 100644 index 829bbf20..00000000 --- a/android/examples/screenshot/src/main/AndroidManifest.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/shaderobjects/CMakeLists.txt b/android/examples/shaderobjects/CMakeLists.txt deleted file mode 100644 index 90086789..00000000 --- a/android/examples/shaderobjects/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME shaderobjects) - -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/android/examples/shaderobjects/build.gradle b/android/examples/shaderobjects/build.gradle deleted file mode 100644 index ce056707..00000000 --- a/android/examples/shaderobjects/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanShaderobjects" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/shaderobjects' - into 'assets/shaders/glsl/shaderobjects' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'treasure_smooth.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/shaderobjects/src/main/AndroidManifest.xml b/android/examples/shaderobjects/src/main/AndroidManifest.xml deleted file mode 100644 index 0f6f6c43..00000000 --- a/android/examples/shaderobjects/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/shaderobjects/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/shaderobjects/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/shaderobjects/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/shadowmapping/CMakeLists.txt b/android/examples/shadowmapping/CMakeLists.txt deleted file mode 100644 index 2400cff9..00000000 --- a/android/examples/shadowmapping/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/shadowmapping/build.gradle b/android/examples/shadowmapping/build.gradle deleted file mode 100644 index e25b24a8..00000000 --- a/android/examples/shadowmapping/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanShadowmapping" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/shadowmapping' - into 'assets/shaders/glsl/shadowmapping' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscene_shadow.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'samplescene.gltf' - } - -} - -preBuild.dependsOn copyTask diff --git a/android/examples/shadowmapping/src/main/AndroidManifest.xml b/android/examples/shadowmapping/src/main/AndroidManifest.xml deleted file mode 100644 index 859e5afc..00000000 --- a/android/examples/shadowmapping/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/shadowmappingcascade/CMakeLists.txt b/android/examples/shadowmappingcascade/CMakeLists.txt deleted file mode 100644 index d5ce298f..00000000 --- a/android/examples/shadowmappingcascade/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/shadowmappingcascade/build.gradle b/android/examples/shadowmappingcascade/build.gradle deleted file mode 100644 index ce5064d5..00000000 --- a/android/examples/shadowmappingcascade/build.gradle +++ /dev/null @@ -1,71 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanShadowmappingcascade" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/shadowmappingcascade' - into 'assets/shaders/glsl/shadowmappingcascade' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'terrain_gridlines.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'oaktree.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml b/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml deleted file mode 100644 index 9eb23c03..00000000 --- a/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/shadowmappingomni/CMakeLists.txt b/android/examples/shadowmappingomni/CMakeLists.txt deleted file mode 100644 index 06ff0248..00000000 --- a/android/examples/shadowmappingomni/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/shadowmappingomni/build.gradle b/android/examples/shadowmappingomni/build.gradle deleted file mode 100644 index 019a4341..00000000 --- a/android/examples/shadowmappingomni/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanShadowmappingomni" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/shadowmappingomni' - into 'assets/shaders/glsl/shadowmappingomni' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'shadowscene_fire.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/shadowmappingomni/src/main/AndroidManifest.xml b/android/examples/shadowmappingomni/src/main/AndroidManifest.xml deleted file mode 100644 index 0fe3262a..00000000 --- a/android/examples/shadowmappingomni/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/specializationconstants/CMakeLists.txt b/android/examples/specializationconstants/CMakeLists.txt deleted file mode 100644 index 179842ab..00000000 --- a/android/examples/specializationconstants/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/specializationconstants/build.gradle b/android/examples/specializationconstants/build.gradle deleted file mode 100644 index c48bbd84..00000000 --- a/android/examples/specializationconstants/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanSpecializationconstants" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/specializationconstants' - into 'assets/shaders/glsl/specializationconstants' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'color_teapot_spheres.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'metalplate_nomips_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/specializationconstants/src/main/AndroidManifest.xml b/android/examples/specializationconstants/src/main/AndroidManifest.xml deleted file mode 100644 index aa6e33c0..00000000 --- a/android/examples/specializationconstants/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/sphericalenvmapping/CMakeLists.txt b/android/examples/sphericalenvmapping/CMakeLists.txt deleted file mode 100644 index 810d62e6..00000000 --- a/android/examples/sphericalenvmapping/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/sphericalenvmapping/build.gradle b/android/examples/sphericalenvmapping/build.gradle deleted file mode 100644 index e542ccd4..00000000 --- a/android/examples/sphericalenvmapping/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanSphericalenvmapping" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/sphericalenvmapping' - into 'assets/shaders/glsl/sphericalenvmapping' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'chinesedragon.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'matcap_array_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml b/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml deleted file mode 100644 index 17a4f280..00000000 --- a/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/ssao/CMakeLists.txt b/android/examples/ssao/CMakeLists.txt deleted file mode 100644 index 7a4355d5..00000000 --- a/android/examples/ssao/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/ssao/build.gradle b/android/examples/ssao/build.gradle deleted file mode 100644 index d9f76090..00000000 --- a/android/examples/ssao/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanSSAO" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/ssao' - into 'assets/shaders/glsl/ssao' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/sponza' - into 'assets/models/sponza' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/ssao/src/main/AndroidManifest.xml b/android/examples/ssao/src/main/AndroidManifest.xml deleted file mode 100644 index dad1de75..00000000 --- a/android/examples/ssao/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/stencilbuffer/CMakeLists.txt b/android/examples/stencilbuffer/CMakeLists.txt deleted file mode 100644 index 5f75f0f7..00000000 --- a/android/examples/stencilbuffer/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/stencilbuffer/build.gradle b/android/examples/stencilbuffer/build.gradle deleted file mode 100644 index 10f97582..00000000 --- a/android/examples/stencilbuffer/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanStencilbuffer" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/stencilbuffer' - into 'assets/shaders/glsl/stencilbuffer' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/stencilbuffer/src/main/AndroidManifest.xml b/android/examples/stencilbuffer/src/main/AndroidManifest.xml deleted file mode 100644 index 8b020316..00000000 --- a/android/examples/stencilbuffer/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/subpasses/CMakeLists.txt b/android/examples/subpasses/CMakeLists.txt deleted file mode 100644 index 707fbbc4..00000000 --- a/android/examples/subpasses/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/subpasses/build.gradle b/android/examples/subpasses/build.gradle deleted file mode 100644 index 9e83166d..00000000 --- a/android/examples/subpasses/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanSubpasses" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/subpasses' - into 'assets/shaders/glsl/subpasses' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'samplebuilding.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'samplebuilding_glass.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'colored_glass_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/subpasses/src/main/AndroidManifest.xml b/android/examples/subpasses/src/main/AndroidManifest.xml deleted file mode 100644 index 3a7a1325..00000000 --- a/android/examples/subpasses/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/terraintessellation/CMakeLists.txt b/android/examples/terraintessellation/CMakeLists.txt deleted file mode 100644 index ad3a0bf6..00000000 --- a/android/examples/terraintessellation/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/terraintessellation/build.gradle b/android/examples/terraintessellation/build.gradle deleted file mode 100644 index 4400df2d..00000000 --- a/android/examples/terraintessellation/build.gradle +++ /dev/null @@ -1,84 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTerraintessellation" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/terraintessellation' - into 'assets/shaders/glsl/terraintessellation' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'skysphere*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'terrain_texturearray*.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'terrain_heightmap_r16.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/terraintessellation/src/main/AndroidManifest.xml b/android/examples/terraintessellation/src/main/AndroidManifest.xml deleted file mode 100644 index f40a43ca..00000000 --- a/android/examples/terraintessellation/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/tessellation/CMakeLists.txt b/android/examples/tessellation/CMakeLists.txt deleted file mode 100644 index 0de786e5..00000000 --- a/android/examples/tessellation/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/tessellation/build.gradle b/android/examples/tessellation/build.gradle deleted file mode 100644 index e60902a1..00000000 --- a/android/examples/tessellation/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTessellation" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/tessellation' - into 'assets/shaders/glsl/tessellation' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'deer.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/tessellation/src/main/AndroidManifest.xml b/android/examples/tessellation/src/main/AndroidManifest.xml deleted file mode 100644 index d9733f1d..00000000 --- a/android/examples/tessellation/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/textoverlay/CMakeLists.txt b/android/examples/textoverlay/CMakeLists.txt deleted file mode 100644 index 8f8f962a..00000000 --- a/android/examples/textoverlay/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/textoverlay/build.gradle b/android/examples/textoverlay/build.gradle deleted file mode 100644 index 617bcc81..00000000 --- a/android/examples/textoverlay/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTextoverlay" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/textoverlay' - into 'assets/shaders/glsl/textoverlay' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/textoverlay/src/main/AndroidManifest.xml b/android/examples/textoverlay/src/main/AndroidManifest.xml deleted file mode 100644 index 0d0144e3..00000000 --- a/android/examples/textoverlay/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texture/CMakeLists.txt b/android/examples/texture/CMakeLists.txt deleted file mode 100644 index a743d972..00000000 --- a/android/examples/texture/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texture/build.gradle b/android/examples/texture/build.gradle deleted file mode 100644 index 61f08645..00000000 --- a/android/examples/texture/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexture" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texture' - into 'assets/shaders/glsl/texture' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'metalplate01_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texture/src/main/AndroidManifest.xml b/android/examples/texture/src/main/AndroidManifest.xml deleted file mode 100644 index fc670531..00000000 --- a/android/examples/texture/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texture3d/CMakeLists.txt b/android/examples/texture3d/CMakeLists.txt deleted file mode 100644 index 86a9a06f..00000000 --- a/android/examples/texture3d/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texture3d/build.gradle b/android/examples/texture3d/build.gradle deleted file mode 100644 index 9805bfa5..00000000 --- a/android/examples/texture3d/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexture3d" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texture3d' - into 'assets/shaders/glsl/texture3d' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texture3d/src/main/AndroidManifest.xml b/android/examples/texture3d/src/main/AndroidManifest.xml deleted file mode 100644 index 0d74927b..00000000 --- a/android/examples/texture3d/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texturearray/CMakeLists.txt b/android/examples/texturearray/CMakeLists.txt deleted file mode 100644 index f1932575..00000000 --- a/android/examples/texturearray/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texturearray/build.gradle b/android/examples/texturearray/build.gradle deleted file mode 100644 index 90b528b2..00000000 --- a/android/examples/texturearray/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexturearray" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texturearray' - into 'assets/shaders/glsl/texturearray' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'texturearray_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texturearray/src/main/AndroidManifest.xml b/android/examples/texturearray/src/main/AndroidManifest.xml deleted file mode 100644 index c1a04330..00000000 --- a/android/examples/texturearray/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texturecubemap/CMakeLists.txt b/android/examples/texturecubemap/CMakeLists.txt deleted file mode 100644 index 02f574e3..00000000 --- a/android/examples/texturecubemap/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texturecubemap/build.gradle b/android/examples/texturecubemap/build.gradle deleted file mode 100644 index dbcfc8a9..00000000 --- a/android/examples/texturecubemap/build.gradle +++ /dev/null @@ -1,96 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexturecubemap" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texturecubemap' - into 'assets/shaders/glsl/texturecubemap' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'cubemap_yokohama_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texturecubemap/src/main/AndroidManifest.xml b/android/examples/texturecubemap/src/main/AndroidManifest.xml deleted file mode 100644 index e2914845..00000000 --- a/android/examples/texturecubemap/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texturecubemaparray/CMakeLists.txt b/android/examples/texturecubemaparray/CMakeLists.txt deleted file mode 100644 index 24ca68e0..00000000 --- a/android/examples/texturecubemaparray/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texturecubemaparray/build.gradle b/android/examples/texturecubemaparray/build.gradle deleted file mode 100644 index a9f68f4d..00000000 --- a/android/examples/texturecubemaparray/build.gradle +++ /dev/null @@ -1,96 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexturecubemapArray" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texturecubemaparray' - into 'assets/shaders/glsl/texturecubemaparray' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sphere.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'teapot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'torusknot.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'venus.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'cubemap_array.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texturecubemaparray/src/main/AndroidManifest.xml b/android/examples/texturecubemaparray/src/main/AndroidManifest.xml deleted file mode 100644 index 2e087df9..00000000 --- a/android/examples/texturecubemaparray/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texturemipmapgen/CMakeLists.txt b/android/examples/texturemipmapgen/CMakeLists.txt deleted file mode 100644 index 8a56121b..00000000 --- a/android/examples/texturemipmapgen/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/texturemipmapgen/build.gradle b/android/examples/texturemipmapgen/build.gradle deleted file mode 100644 index 8249c6a3..00000000 --- a/android/examples/texturemipmapgen/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexturemipmapgen" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texturemipmapgen' - into 'assets/shaders/glsl/texturemipmapgen' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'tunnel_cylinder.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'metalplate_nomips_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texturemipmapgen/src/main/AndroidManifest.xml b/android/examples/texturemipmapgen/src/main/AndroidManifest.xml deleted file mode 100644 index 8c914217..00000000 --- a/android/examples/texturemipmapgen/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/texturesparseresidency/CMakeLists.txt b/android/examples/texturesparseresidency/CMakeLists.txt deleted file mode 100644 index 9f5d6571..00000000 --- a/android/examples/texturesparseresidency/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME texturesparseresidency) - -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/android/examples/texturesparseresidency/build.gradle b/android/examples/texturesparseresidency/build.gradle deleted file mode 100644 index 505f4700..00000000 --- a/android/examples/texturesparseresidency/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTexturesparseresidency" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/texturesparseresidency' - into 'assets/shaders/glsl/texturesparseresidency' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'plane.gltf' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/texturesparseresidency/src/main/AndroidManifest.xml b/android/examples/texturesparseresidency/src/main/AndroidManifest.xml deleted file mode 100644 index cc16f382..00000000 --- a/android/examples/texturesparseresidency/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/texturesparseresidency/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/texturesparseresidency/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/texturesparseresidency/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/timelinesemaphore/CMakeLists.txt b/android/examples/timelinesemaphore/CMakeLists.txt deleted file mode 100644 index 2d2bd833..00000000 --- a/android/examples/timelinesemaphore/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME timelinesemaphore) - -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/android/examples/timelinesemaphore/build.gradle b/android/examples/timelinesemaphore/build.gradle deleted file mode 100644 index 3da087d6..00000000 --- a/android/examples/timelinesemaphore/build.gradle +++ /dev/null @@ -1,72 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTimelinesemaphore" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/computenbody' - into 'assets/shaders/glsl/computenbody' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle01_rgba.ktx' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'particle_gradient_rgba.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/timelinesemaphore/src/main/AndroidManifest.xml b/android/examples/timelinesemaphore/src/main/AndroidManifest.xml deleted file mode 100644 index 821c8f6b..00000000 --- a/android/examples/timelinesemaphore/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/timelinesemaphore/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/timelinesemaphore/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/timelinesemaphore/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/triangle/CMakeLists.txt b/android/examples/triangle/CMakeLists.txt deleted file mode 100644 index 13727990..00000000 --- a/android/examples/triangle/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/triangle/build.gradle b/android/examples/triangle/build.gradle deleted file mode 100644 index 5412f397..00000000 --- a/android/examples/triangle/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTriangle" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/triangle' - into 'assets/shaders/glsl/triangle' - include '*.*' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/triangle/src/main/AndroidManifest.xml b/android/examples/triangle/src/main/AndroidManifest.xml deleted file mode 100644 index 020c6835..00000000 --- a/android/examples/triangle/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/trianglevulkan13/CMakeLists.txt b/android/examples/trianglevulkan13/CMakeLists.txt deleted file mode 100644 index e2b2f44e..00000000 --- a/android/examples/trianglevulkan13/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME trianglevulkan13) - -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/android/examples/trianglevulkan13/build.gradle b/android/examples/trianglevulkan13/build.gradle deleted file mode 100644 index 542a3d29..00000000 --- a/android/examples/trianglevulkan13/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanTrianglevulkan13" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/triangle' - into 'assets/shaders/glsl/triangle' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/trianglevulkan13/src/main/AndroidManifest.xml b/android/examples/trianglevulkan13/src/main/AndroidManifest.xml deleted file mode 100644 index 206434ef..00000000 --- a/android/examples/trianglevulkan13/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/trianglevulkan13/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/trianglevulkan13/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/trianglevulkan13/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/variablerateshading/CMakeLists.txt b/android/examples/variablerateshading/CMakeLists.txt deleted file mode 100644 index c6536178..00000000 --- a/android/examples/variablerateshading/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME variablerateshading) - -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/android/examples/variablerateshading/build.gradle b/android/examples/variablerateshading/build.gradle deleted file mode 100644 index 5422e6d6..00000000 --- a/android/examples/variablerateshading/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanVariablerateshading" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/variablerateshading' - into 'assets/shaders/glsl/variablerateshading' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/sponza' - into 'assets/models/sponza' - include '*.*' - } - -} - -preBuild.dependsOn copyTask diff --git a/android/examples/variablerateshading/src/main/AndroidManifest.xml b/android/examples/variablerateshading/src/main/AndroidManifest.xml deleted file mode 100644 index 5f51b1dd..00000000 --- a/android/examples/variablerateshading/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/variablerateshading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/variablerateshading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/variablerateshading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/vertexattributes/CMakeLists.txt b/android/examples/vertexattributes/CMakeLists.txt deleted file mode 100644 index ff8e61bb..00000000 --- a/android/examples/vertexattributes/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 FATAL_ERROR) - - - -set(NAME vertexattributes) - -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/android/examples/vertexattributes/build.gradle b/android/examples/vertexattributes/build.gradle deleted file mode 100644 index 501ad04f..00000000 --- a/android/examples/vertexattributes/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanVertexattributes" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/vertexattributes' - into 'assets/shaders/glsl/vertexattributes' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models/sponza' - into 'assets/models/sponza' - include '*.*' - } - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/vertexattributes/src/main/AndroidManifest.xml b/android/examples/vertexattributes/src/main/AndroidManifest.xml deleted file mode 100644 index c78888df..00000000 --- a/android/examples/vertexattributes/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/vertexattributes/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/vertexattributes/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/vertexattributes/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/viewportarray/CMakeLists.txt b/android/examples/viewportarray/CMakeLists.txt deleted file mode 100644 index bf64ad1f..00000000 --- a/android/examples/viewportarray/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/viewportarray/build.gradle b/android/examples/viewportarray/build.gradle deleted file mode 100644 index fe35d477..00000000 --- a/android/examples/viewportarray/build.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanViewportarray" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/viewportarray' - into 'assets/shaders/glsl/viewportarray' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'sampleroom.gltf' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/viewportarray/src/main/AndroidManifest.xml b/android/examples/viewportarray/src/main/AndroidManifest.xml deleted file mode 100644 index 921b4da7..00000000 --- a/android/examples/viewportarray/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/examples/vulkanscene/CMakeLists.txt b/android/examples/vulkanscene/CMakeLists.txt deleted file mode 100644 index 9a166313..00000000 --- a/android/examples/vulkanscene/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0 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/android/examples/vulkanscene/build.gradle b/android/examples/vulkanscene/build.gradle deleted file mode 100644 index 3a0307a9..00000000 --- a/android/examples/vulkanscene/build.gradle +++ /dev/null @@ -1,90 +0,0 @@ -apply plugin: 'com.android.application' -apply from: '../gradle/outputfilename.gradle' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "de.saschawillems.vulkanVulkanscene" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - ndk { - abiFilters rootProject.ext.abiFilters - } - 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 rootProject.ext.shaderPath + 'glsl/base' - into 'assets/shaders/glsl/base' - include '*.spv' - } - - copy { - from rootProject.ext.shaderPath + 'glsl/vulkanscene' - into 'assets/shaders/glsl/vulkanscene' - include '*.*' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenelogos.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenebackground.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'vulkanscenemodels.gltf' - } - - copy { - from rootProject.ext.assetPath + 'models' - into 'assets/models' - include 'cube.gltf' - } - - copy { - from rootProject.ext.assetPath + 'textures' - into 'assets/textures' - include 'cubemap_vulkan.ktx' - } - - -} - -preBuild.dependsOn copyTask \ No newline at end of file diff --git a/android/examples/vulkanscene/src/main/AndroidManifest.xml b/android/examples/vulkanscene/src/main/AndroidManifest.xml deleted file mode 100644 index 9cb8a7a3..00000000 --- a/android/examples/vulkanscene/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java deleted file mode 100644 index 12e14fc6..00000000 --- a/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 2b474b57..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,17 +0,0 @@ -## For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx1024m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -# -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -#Sun May 28 17:34:07 CST 2023 -android.defaults.buildfeatures.buildconfig=true -android.nonFinalResIds=false -android.nonTransitiveRClass=false -org.gradle.jvmargs=-Xmx4096M diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 328f7a40..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Oct 10 17:55:02 CEST 2024 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index c53aefaa..00000000 --- a/android/gradlew +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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 -# -# https://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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -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" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index 107acd32..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@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 - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@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="-Xmx64m" "-Xms64m" - -@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 execute - -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 execute - -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 - -: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 %* - -: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/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 74697331..00000000 --- a/android/settings.gradle +++ /dev/null @@ -1,9 +0,0 @@ -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/base/CMakeLists.txt b/base/CMakeLists.txt index 90112062..69e66e4a 100644 --- a/base/CMakeLists.txt +++ b/base/CMakeLists.txt @@ -1,8 +1,31 @@ -# Copyright (c) 2016-2025, Sascha Willems -# SPDX-License-Identifier: MIT +# Procedural 3D Engine - Base Library +# Copyright (c) 2025 Your Project +# Licensed under the MIT License +# Based on Vulkan examples by Sascha Willems (MIT License) -file(GLOB BASE_SRC "*.cpp" "*.hpp" "*.h" "../external/imgui/*.cpp") -file(GLOB BASE_HEADERS "*.hpp" "*.h") +file(GLOB_RECURSE BASE_SRC + "*.cpp" + "*.hpp" + "*.h" + "device/*.cpp" + "device/*.h" + "memory/*.cpp" + "memory/*.h" + "core/*.cpp" + "core/*.hpp" + "core/*.h" + "foundation/*.cpp" + "foundation/*.h" +) +file(GLOB_RECURSE BASE_HEADERS + "*.hpp" + "*.h" + "device/*.h" + "memory/*.h" + "core/*.hpp" + "core/*.h" + "foundation/*.h" +) set(KTX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../external/ktx) set(KTX_SOURCES diff --git a/base/VulkanAndroid.cpp b/base/VulkanAndroid.cpp deleted file mode 100644 index 43d8c1c7..00000000 --- a/base/VulkanAndroid.cpp +++ /dev/null @@ -1,366 +0,0 @@ -/* -* Android Vulkan function pointer loader -* -* Copyright (C) 2016-2025 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 - #include - #include - -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_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties; -PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements; -PFN_vkQueueBindSparse vkQueueBindSparse; -PFN_vkCmdBeginRendering vkCmdBeginRendering; -PFN_vkCmdEndRendering vkCmdEndRendering; - -PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; -PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; -PFN_vkCmdFillBuffer vkCmdFillBuffer; - -PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; -PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; -PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; -PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; -PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; -PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; -PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; -PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; -PFN_vkQueuePresentKHR vkQueuePresentKHR; - -PFN_vkResetCommandBuffer vkResetCommandBuffer; - -PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties; - -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(dlsym(libVulkan, "vkEnumerateInstanceExtensionProperties")); - vkEnumerateInstanceLayerProperties = reinterpret_cast(dlsym(libVulkan, "vkEnumerateInstanceLayerProperties")); - vkCreateInstance = reinterpret_cast(dlsym(libVulkan, "vkCreateInstance")); - vkGetInstanceProcAddr = reinterpret_cast(dlsym(libVulkan, "vkGetInstanceProcAddr")); - vkGetDeviceProcAddr = reinterpret_cast(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(vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices")); - vkGetPhysicalDeviceProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties")); - vkGetPhysicalDeviceProperties2 = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2")); - vkEnumerateDeviceLayerProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkEnumerateDeviceLayerProperties")); - vkEnumerateDeviceExtensionProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties")); - vkGetPhysicalDeviceQueueFamilyProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties")); - vkGetPhysicalDeviceFeatures = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures")); - vkGetPhysicalDeviceFeatures2 = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2")); - vkCreateDevice = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDevice")); - vkGetPhysicalDeviceFormatProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties")); - vkGetPhysicalDeviceMemoryProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceMemoryProperties")); - - vkCmdPipelineBarrier = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdPipelineBarrier")); - vkCreateShaderModule = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateShaderModule")); - - vkCreateBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateBuffer")); - vkGetBufferMemoryRequirements = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetBufferMemoryRequirements")); - vkMapMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkMapMemory")); - vkUnmapMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkUnmapMemory")); - vkFlushMappedMemoryRanges = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkFlushMappedMemoryRanges")); - vkInvalidateMappedMemoryRanges = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkInvalidateMappedMemoryRanges")); - vkBindBufferMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkBindBufferMemory")); - vkDestroyBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyBuffer")); - - vkAllocateMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkAllocateMemory")); - vkFreeMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkFreeMemory")); - vkCreateRenderPass = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateRenderPass")); - vkCmdBeginRenderPass = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginRenderPass")); - vkCmdEndRenderPass = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndRenderPass")); - vkCmdNextSubpass = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdNextSubpass")); - vkCmdExecuteCommands = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdExecuteCommands")); - vkCmdClearColorImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdClearColorImage")); - - vkCreateImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateImage")); - vkGetImageMemoryRequirements = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetImageMemoryRequirements")); - vkCreateImageView = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateImageView")); - vkDestroyImageView = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyImageView")); - vkBindImageMemory = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkBindImageMemory")); - vkGetImageSubresourceLayout = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetImageSubresourceLayout")); - vkCmdCopyImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdCopyImage")); - vkCmdBlitImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBlitImage")); - vkDestroyImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyImage")); - - vkCmdClearAttachments = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdClearAttachments")); - - vkCmdCopyBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdCopyBuffer")); - vkCmdCopyBufferToImage = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdCopyBufferToImage")); - - vkCreateSampler = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateSampler")); - vkDestroySampler = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroySampler"));; - - vkCreateSemaphore = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateSemaphore")); - vkDestroySemaphore = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroySemaphore")); - - vkCreateFence = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateFence")); - vkDestroyFence = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyFence")); - vkWaitForFences = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkWaitForFences")); - vkResetFences = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkResetFences"));; - vkResetDescriptorPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkResetDescriptorPool")); - - vkCreateCommandPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateCommandPool")); - vkDestroyCommandPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyCommandPool"));; - - vkAllocateCommandBuffers = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkAllocateCommandBuffers")); - vkBeginCommandBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkBeginCommandBuffer")); - vkEndCommandBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkEndCommandBuffer")); - - vkGetDeviceQueue = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetDeviceQueue")); - vkQueueSubmit = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueSubmit")); - vkQueueWaitIdle = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueWaitIdle")); - - vkDeviceWaitIdle = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDeviceWaitIdle")); - - vkCreateFramebuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateFramebuffer")); - - vkCreatePipelineCache = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreatePipelineCache")); - vkCreatePipelineLayout = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreatePipelineLayout")); - vkCreateGraphicsPipelines = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateGraphicsPipelines")); - vkCreateComputePipelines = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateComputePipelines")); - - vkCreateDescriptorPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDescriptorPool")); - vkCreateDescriptorSetLayout = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDescriptorSetLayout")); - - vkAllocateDescriptorSets = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkAllocateDescriptorSets")); - vkUpdateDescriptorSets = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkUpdateDescriptorSets")); - - vkCmdBindDescriptorSets = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBindDescriptorSets")); - vkCmdBindPipeline = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBindPipeline")); - vkCmdBindVertexBuffers = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBindVertexBuffers")); - vkCmdBindIndexBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBindIndexBuffer")); - - vkCmdSetViewport = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdSetViewport")); - vkCmdSetScissor = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdSetScissor")); - vkCmdSetLineWidth = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdSetLineWidth")); - vkCmdSetDepthBias = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdSetDepthBias")); - vkCmdPushConstants = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdPushConstants"));; - - vkCmdDrawIndexed = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdDrawIndexed")); - vkCmdDraw = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdDraw")); - vkCmdDrawIndexedIndirect = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdDrawIndexedIndirect")); - vkCmdDrawIndirect = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdDrawIndirect")); - vkCmdDispatch = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdDispatch")); - - vkDestroyPipeline = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyPipeline")); - vkDestroyPipelineLayout = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyPipelineLayout"));; - vkDestroyDescriptorSetLayout = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDescriptorSetLayout")); - vkDestroyDevice = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDevice")); - vkDestroyInstance = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyInstance")); - vkDestroyDescriptorPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDescriptorPool")); - vkFreeCommandBuffers = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkFreeCommandBuffers")); - vkDestroyRenderPass = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyRenderPass")); - vkDestroyFramebuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyFramebuffer")); - vkDestroyShaderModule = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyShaderModule")); - vkDestroyPipelineCache = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyPipelineCache")); - - vkCreateQueryPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateQueryPool")); - vkDestroyQueryPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyQueryPool")); - vkGetQueryPoolResults = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetQueryPoolResults")); - - vkCmdBeginQuery = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginQuery")); - vkCmdEndQuery = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndQuery")); - vkCmdResetQueryPool = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdResetQueryPool")); - vkCmdCopyQueryPoolResults = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdCopyQueryPoolResults")); - - vkGetPhysicalDeviceSparseImageFormatProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSparseImageFormatProperties")); - vkGetImageSparseMemoryRequirements = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetImageSparseMemoryRequirements")); - vkQueueBindSparse = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueBindSparse")); - - vkCmdBeginRendering = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginRendering")); - vkCmdEndRendering = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndRendering")); - - vkCreateAndroidSurfaceKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateAndroidSurfaceKHR")); - vkDestroySurfaceKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR")); - - vkCmdFillBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdFillBuffer")); - - vkGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceSupportKHR")); - vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR")); - vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR")); - vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR")); - vkCreateSwapchainKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateSwapchainKHR")); - vkDestroySwapchainKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroySwapchainKHR")); - vkGetSwapchainImagesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetSwapchainImagesKHR")); - vkAcquireNextImageKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkAcquireNextImageKHR")); - vkQueuePresentKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueuePresentKHR")); - - vkResetCommandBuffer = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkResetCommandBuffer")); - - vkGetPhysicalDeviceImageFormatProperties = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceImageFormatProperties")); - } - - 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(); - } - } -} - -#endif diff --git a/base/VulkanAndroid.h b/base/VulkanAndroid.h deleted file mode 100644 index f3201bde..00000000 --- a/base/VulkanAndroid.h +++ /dev/null @@ -1,198 +0,0 @@ -/* -* Android Vulkan function pointer prototypes -* -* Copyright (C) 2016-2025 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 -// While SDK 26 (and up) come with a loader, we also want to support older devices, so we manually load function pointers - -#pragma once - -#ifndef VULKANANDROID_HPP -#define VULKANANDROID_HPP - -#include "vulkan/vulkan.h" - -#if defined(__ANDROID__) - -#include -#include -#include -#include -#include - -// 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_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties; -extern PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements; -extern PFN_vkQueueBindSparse vkQueueBindSparse; -extern PFN_vkCmdBeginRendering vkCmdBeginRendering; -extern PFN_vkCmdEndRendering vkCmdEndRendering; - -extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; -extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; -extern PFN_vkCmdFillBuffer vkCmdFillBuffer; - -extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; -extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; -extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; -extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; -extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; -extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; -extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; -extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; -extern PFN_vkQueuePresentKHR vkQueuePresentKHR; - -extern PFN_vkResetCommandBuffer vkResetCommandBuffer; - -extern PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties; - -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/base/VulkanFrameBuffer.hpp b/base/VulkanFrameBuffer.hpp index d21c02cd..9e420a4a 100644 --- a/base/VulkanFrameBuffer.hpp +++ b/base/VulkanFrameBuffer.hpp @@ -12,8 +12,8 @@ #include #include #include "vulkan/vulkan.h" -#include "VulkanDevice.h" -#include "VulkanTools.h" +#include "device/VulkanDevice.h" +#include "core/VulkanTools.h" namespace vks { diff --git a/base/VulkanRaytracingSample.h b/base/VulkanRaytracingSample.h index 08b57e29..bcbaa46d 100644 --- a/base/VulkanRaytracingSample.h +++ b/base/VulkanRaytracingSample.h @@ -9,9 +9,9 @@ #pragma once #include "vulkan/vulkan.h" -#include "vulkanexamplebase.h" -#include "VulkanTools.h" -#include "VulkanDevice.h" +#include "foundation/vulkanexamplebase.h" +#include "core/VulkanTools.h" +#include "device/VulkanDevice.h" class VulkanRaytracingSample : public VulkanExampleBase { diff --git a/base/VulkanglTFModel.h b/base/VulkanglTFModel.h index 4aad677b..3bfd6fe8 100644 --- a/base/VulkanglTFModel.h +++ b/base/VulkanglTFModel.h @@ -21,7 +21,7 @@ #include #include "vulkan/vulkan.h" -#include "VulkanDevice.h" +#include "device/VulkanDevice.h" #include #include diff --git a/base/camera.hpp b/base/camera.hpp index 4838bde2..4fe9a4b9 100644 --- a/base/camera.hpp +++ b/base/camera.hpp @@ -22,29 +22,39 @@ private: { glm::mat4 currentMatrix = matrices.view; - glm::mat4 rotM = glm::mat4(1.0f); - glm::mat4 transM; + if (useOrbitMode && type == CameraType::lookat) { + // Orbit mode: position camera relative to orbit center using spherical coordinates + updateOrbitPosition(); + + // Use lookAt matrix for orbit mode + matrices.view = glm::lookAt(position, orbitCenter, glm::vec3(0.0f, 1.0f, 0.0f)); + viewPos = glm::vec4(position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f); + } else { + // Standard Sascha camera behavior + 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)); + 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; + 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); } - 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); if (matrices.view != currentMatrix) { updated = true; @@ -58,6 +68,11 @@ public: glm::vec3 position = glm::vec3(); glm::vec4 viewPos = glm::vec4(); + // Orbit camera support + glm::vec3 orbitCenter = glm::vec3(0.0f); + float orbitDistance = 8.0f; + bool useOrbitMode = false; + float rotationSpeed = 1.0f; float movementSpeed = 1.0f; @@ -158,6 +173,61 @@ public: this->movementSpeed = movementSpeed; } + // Update camera position based on orbit center and current rotation + void updateOrbitPosition() + { + if (!useOrbitMode) return; + + // Convert Euler angles to spherical coordinates around orbit center + float pitch = glm::radians(rotation.x); + float yaw = glm::radians(rotation.y); + + // Calculate position relative to orbit center using spherical coordinates + float x = orbitDistance * cos(pitch) * cos(yaw); + float y = orbitDistance * sin(pitch); + float z = orbitDistance * cos(pitch) * sin(yaw); + + position = orbitCenter + glm::vec3(x, y, z); + } + + // Enable orbit mode and set orbit center + void setOrbitMode(glm::vec3 center, float distance) + { + orbitCenter = center; + orbitDistance = distance; + useOrbitMode = true; + updateViewMatrix(); + } + + // Disable orbit mode and return to standard camera + void disableOrbitMode() + { + useOrbitMode = false; + updateViewMatrix(); + } + + // Focus camera on an object using orbit mode + void focusOnObject(glm::vec3 objectCenter, float objectRadius) + { + // Calculate optimal viewing distance based on object radius and FOV + float halfFovRadians = glm::radians(fov * 0.5f); + float distance = objectRadius / glm::tan(halfFovRadians) * 2.5f; + distance = glm::max(distance, objectRadius * 3.0f); + + // Set orbit center to the object center + orbitCenter = objectCenter; + orbitDistance = distance; + useOrbitMode = true; + + // Set camera to a good viewing angle (slightly elevated and angled) + rotation.x = 15.0f; // Slight elevation + rotation.y = 30.0f; // Angled view + rotation.z = 0.0f; + + // Update position and view matrix + updateViewMatrix(); + } + void update(float deltaTime) { updated = false; diff --git a/base/VulkanDebug.cpp b/base/core/VulkanDebug.cpp similarity index 100% rename from base/VulkanDebug.cpp rename to base/core/VulkanDebug.cpp diff --git a/base/VulkanDebug.h b/base/core/VulkanDebug.h similarity index 100% rename from base/VulkanDebug.h rename to base/core/VulkanDebug.h diff --git a/base/VulkanInitializers.hpp b/base/core/VulkanInitializers.hpp similarity index 100% rename from base/VulkanInitializers.hpp rename to base/core/VulkanInitializers.hpp diff --git a/base/VulkanTools.cpp b/base/core/VulkanTools.cpp similarity index 100% rename from base/VulkanTools.cpp rename to base/core/VulkanTools.cpp diff --git a/base/VulkanTools.h b/base/core/VulkanTools.h similarity index 100% rename from base/VulkanTools.h rename to base/core/VulkanTools.h diff --git a/base/VulkanDevice.cpp b/base/device/VulkanDevice.cpp similarity index 99% rename from base/VulkanDevice.cpp rename to base/device/VulkanDevice.cpp index 8c1e9755..f76aca3a 100644 --- a/base/VulkanDevice.cpp +++ b/base/device/VulkanDevice.cpp @@ -12,7 +12,7 @@ // SRS - Enable beta extensions and make VK_KHR_portability_subset visible #define VK_ENABLE_BETA_EXTENSIONS #endif -#include +#include "VulkanDevice.h" #include namespace vks diff --git a/base/VulkanDevice.h b/base/device/VulkanDevice.h similarity index 97% rename from base/VulkanDevice.h rename to base/device/VulkanDevice.h index 87597687..70d1f739 100644 --- a/base/VulkanDevice.h +++ b/base/device/VulkanDevice.h @@ -10,8 +10,8 @@ #pragma once -#include "VulkanBuffer.h" -#include "VulkanTools.h" +#include "../memory/VulkanBuffer.h" +#include "../core/VulkanTools.h" #include "vulkan/vulkan.h" #include #include diff --git a/base/VulkanSwapChain.cpp b/base/device/VulkanSwapChain.cpp similarity index 100% rename from base/VulkanSwapChain.cpp rename to base/device/VulkanSwapChain.cpp diff --git a/base/VulkanSwapChain.h b/base/device/VulkanSwapChain.h similarity index 99% rename from base/VulkanSwapChain.h rename to base/device/VulkanSwapChain.h index cb1386a3..aa252972 100644 --- a/base/VulkanSwapChain.h +++ b/base/device/VulkanSwapChain.h @@ -17,7 +17,7 @@ #include #include -#include "VulkanTools.h" +#include "../core/VulkanTools.h" #ifdef __ANDROID__ #include "VulkanAndroid.h" diff --git a/base/VulkanUIOverlay.cpp b/base/foundation/VulkanUIOverlay.cpp similarity index 100% rename from base/VulkanUIOverlay.cpp rename to base/foundation/VulkanUIOverlay.cpp diff --git a/base/VulkanUIOverlay.h b/base/foundation/VulkanUIOverlay.h similarity index 94% rename from base/VulkanUIOverlay.h rename to base/foundation/VulkanUIOverlay.h index 1a4bf557..ee6730b9 100644 --- a/base/VulkanUIOverlay.h +++ b/base/foundation/VulkanUIOverlay.h @@ -17,10 +17,10 @@ #include #include -#include "VulkanTools.h" -#include "VulkanDebug.h" -#include "VulkanBuffer.h" -#include "VulkanDevice.h" +#include "../core/VulkanTools.h" +#include "../core/VulkanDebug.h" +#include "../memory/VulkanBuffer.h" +#include "../device/VulkanDevice.h" #include "../external/imgui/imgui.h" diff --git a/base/vulkanexamplebase.cpp b/base/foundation/vulkanexamplebase.cpp similarity index 99% rename from base/vulkanexamplebase.cpp rename to base/foundation/vulkanexamplebase.cpp index 87b3fde0..b08330b0 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/foundation/vulkanexamplebase.cpp @@ -1304,7 +1304,7 @@ HWND VulkanExampleBase::setupWindow(HINSTANCE hinstance, WNDPROC wndproc) SetWindowPos(window, 0, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE); } - ShowWindow(window, SW_SHOW); + ShowWindow(window, SW_MAXIMIZE); SetForegroundWindow(window); SetFocus(window); diff --git a/base/vulkanexamplebase.h b/base/foundation/vulkanexamplebase.h similarity index 97% rename from base/vulkanexamplebase.h rename to base/foundation/vulkanexamplebase.h index 7f742be5..40cba822 100644 --- a/base/vulkanexamplebase.h +++ b/base/foundation/vulkanexamplebase.h @@ -9,7 +9,8 @@ #pragma once #ifdef _WIN32 -#pragma comment(linker, "/subsystem:windows") +// Note: Removed /subsystem:windows pragma to allow console applications +// #pragma comment(linker, "/subsystem:windows") #include #include #include @@ -63,17 +64,17 @@ #include "CommandLineParser.hpp" #include "keycodes.hpp" -#include "VulkanTools.h" -#include "VulkanDebug.h" +#include "../core/VulkanTools.h" +#include "../core/VulkanDebug.h" #include "VulkanUIOverlay.h" -#include "VulkanSwapChain.h" -#include "VulkanBuffer.h" -#include "VulkanDevice.h" -#include "VulkanTexture.h" +#include "../device/VulkanSwapChain.h" +#include "../memory/VulkanBuffer.h" +#include "../device/VulkanDevice.h" +#include "../memory/VulkanTexture.h" -#include "VulkanInitializers.hpp" -#include "camera.hpp" -#include "benchmark.hpp" +#include "../core/VulkanInitializers.hpp" +#include "../camera.hpp" +#include "../benchmark.hpp" class VulkanExampleBase { @@ -163,8 +164,8 @@ public: bool prepared = false; bool resized = false; bool viewUpdated = false; - uint32_t width = 1280; - uint32_t height = 720; + uint32_t width = 1920; + uint32_t height = 1080; vks::UIOverlay ui; CommandLineParser commandLineParser; diff --git a/base/VulkanBuffer.cpp b/base/memory/VulkanBuffer.cpp similarity index 100% rename from base/VulkanBuffer.cpp rename to base/memory/VulkanBuffer.cpp diff --git a/base/VulkanBuffer.h b/base/memory/VulkanBuffer.h similarity index 97% rename from base/VulkanBuffer.h rename to base/memory/VulkanBuffer.h index bd2a421f..c2e4468a 100644 --- a/base/VulkanBuffer.h +++ b/base/memory/VulkanBuffer.h @@ -13,7 +13,7 @@ #include #include "vulkan/vulkan.h" -#include "VulkanTools.h" +#include "../core/VulkanTools.h" namespace vks { diff --git a/base/VulkanTexture.cpp b/base/memory/VulkanTexture.cpp similarity index 99% rename from base/VulkanTexture.cpp rename to base/memory/VulkanTexture.cpp index 01093b07..6e107ac5 100644 --- a/base/VulkanTexture.cpp +++ b/base/memory/VulkanTexture.cpp @@ -6,7 +6,7 @@ * This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT) */ -#include +#include "VulkanTexture.h" namespace vks { diff --git a/base/VulkanTexture.h b/base/memory/VulkanTexture.h similarity index 97% rename from base/VulkanTexture.h rename to base/memory/VulkanTexture.h index 09cf56f2..1601dec7 100644 --- a/base/VulkanTexture.h +++ b/base/memory/VulkanTexture.h @@ -19,8 +19,8 @@ #include #include "VulkanBuffer.h" -#include "VulkanDevice.h" -#include "VulkanTools.h" +#include "../device/VulkanDevice.h" +#include "../core/VulkanTools.h" #if defined(__ANDROID__) # include diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index baf7e7fa..c6f8ca88 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -87,100 +87,7 @@ function(buildExamples) endfunction(buildExamples) set(EXAMPLES - bloom - bufferdeviceaddress - computecloth - computecullandlod - computeheadless - computenbody - computeparticles - computeraytracing - computeshader - conditionalrender - conservativeraster - debugprintf - debugutils - deferred - deferredmultisampling - deferredshadows - descriptorbuffer - descriptorindexing - descriptorsets - displacement - distancefieldfonts - dynamicrendering - dynamicrenderingmultisampling - dynamicstate - dynamicuniformbuffer - gears - geometryshader - gltfloading - gltfscenerendering - gltfskinning - graphicspipelinelibrary - hdr - hostimagecopy imgui - indirectdraw - inlineuniformblocks - inputattachments - instancing - meshshader - multisampling - multithreading - multiview - negativeviewportheight - occlusionquery - offscreen - oit - parallaxmapping - particlesystem - pbrbasic - pbribl - pbrtexture - pipelines - pipelinestatistics - pushconstants - pushdescriptors - radialblur - rayquery - raytracingbasic - raytracingcallable - raytracinggltf - raytracingintersection - raytracingreflections - raytracingpositionfetch - raytracingsbtdata - raytracingshadows - raytracingtextures - renderheadless - screenshot - shaderobjects - shadowmapping - shadowmappingomni - shadowmappingcascade - specializationconstants - sphericalenvmapping - ssao - stencilbuffer - subpasses - terraintessellation - tessellation - textoverlay - texture - texture3d - texturearray - texturecubemap - texturecubemaparray - texturemipmapgen - texturesparseresidency - timelinesemaphore - triangle - trianglevulkan13 - variablerateshading - vertexattributes - viewportarray - vulkanscene ) buildExamples() diff --git a/examples/bloom/bloom.cpp b/examples/bloom/bloom.cpp deleted file mode 100644 index b4b27560..00000000 --- a/examples/bloom/bloom.cpp +++ /dev/null @@ -1,727 +0,0 @@ -/* -* Vulkan Example - Implements a separable two-pass fullscreen blur (also known as bloom) -* -* Copyright (C) 2016 - 2023 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" - - -// 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 framebuffers; - } offscreenPass; - - VulkanExample() : VulkanExampleBase() - { - 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; - if (vks::tools::formatHasStencil(depthFormat)) { - depthStencilView.subresourceRange.aspectMask |= 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 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 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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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); - - // 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layouts - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo; - - // 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts.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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts.scene)); - - // Sets - VkDescriptorSetAllocateInfo descriptorSetAllocInfo; - std::vector 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - // 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.blur, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.blur)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 = static_cast(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(); - setupDescriptors(); - preparePipelines(); - 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/examples/bufferdeviceaddress/bufferdeviceaddress.cpp b/examples/bufferdeviceaddress/bufferdeviceaddress.cpp deleted file mode 100644 index 160bdc86..00000000 --- a/examples/bufferdeviceaddress/bufferdeviceaddress.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/* -* Vulkan Example - Buffer device address -* -* This sample shows how to read data from a buffer device address (aka "reference") instead of using uniforms -* The application passes buffer device addresses to the shader via push constants, and the shader then simply reads the data behind that address -* See cube.vert for the shader side of things -* -* Copyright (C) 2024 by Sascha Willems - www.saschawillems.de -* -*/ - -#include "vulkanexamplebase.h" -#include "VulkanglTFModel.h" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool animate = true; - - struct Cube { - glm::mat4 modelMatrix; - vks::Buffer buffer; - glm::vec3 rotation; - VkDeviceAddress bufferDeviceAddress{}; - }; - std::array cubes{}; - - vks::Texture2D texture; - vkglTF::Model model; - - // Global matrices - struct Scene { - glm::mat4 mvp; - vks::Buffer buffer; - VkDeviceAddress bufferDeviceAddress{}; - } scene; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR{ VK_NULL_HANDLE }; - VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{}; - - // This sample passes the buffer references ("pointer") using push constants, the shader then reads data from that buffer address - struct PushConstantBlock { - // Reference to the global matrices - VkDeviceAddress sceneReference; - // Reference to the per model matrices - VkDeviceAddress modelReference; - }; - - VulkanExample() : VulkanExampleBase() - { - title = "Buffer device address"; - 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)); - - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledInstanceExtensions.push_back(VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_DEVICE_GROUP_EXTENSION_NAME); - - enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; - enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE; - - deviceCreatepNextChain = &enabledBufferDeviceAddresFeatures; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - texture.destroy(); - for (auto cube : cubes) { - cube.buffer.destroy(); - } - scene.buffer.destroy(); - } - } - - 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; - model.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags); - texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - - // We pass all data via buffer device addresses, so we only allocate descriptors for the images - void setupDescriptors() - { - // Pool - std::vector descriptorPoolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(descriptorPoolSizes, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texture.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // The buffer addresses will be passed to the shader using push constants - // That way it's very easy to do a draw call, change the reference to another buffer (or part of that buffer) and do the next draw call using different data - VkPushConstantRange pushConstantRange{}; - pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - pushConstantRange.offset = 0; - pushConstantRange.size = sizeof(PushConstantBlock); - - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(); - pipelineLayoutCI.pushConstantRangeCount = 1; - pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; - pipelineLayoutCI.setLayoutCount = 1; - pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - const std::vector 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(dynamicStateEnables.size()), 0); - std::array shaderStages = { - loadShader(getShadersPath() + "bufferdeviceaddress/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "bufferdeviceaddress/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - - 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color }); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); - } - - void prepareBuffers() - { - // Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &scene.buffer, sizeof(glm::mat4))); - VK_CHECK_RESULT(scene.buffer.map()); - - // Get the device of this buffer that is later on passed to the shader (aka "reference") - VkBufferDeviceAddressInfo bufferDeviceAdressInfo{}; - bufferDeviceAdressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - bufferDeviceAdressInfo.buffer = scene.buffer.buffer; - scene.bufferDeviceAddress = vkGetBufferDeviceAddressKHR(device, &bufferDeviceAdressInfo); - - for (auto& cube : cubes) { - // Note that we don't use this buffer for uniforms but rather pass it's address as a reference to the shader, so isntead of the uniform buffer usage we use a different flag - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &cube.buffer, sizeof(glm::mat4))); - VK_CHECK_RESULT(cube.buffer.map()); - - // Get the device of this buffer that is later on passed to the shader (aka "reference") - bufferDeviceAdressInfo.buffer = cube.buffer.buffer; - cube.bufferDeviceAddress = vkGetBufferDeviceAddressKHR(device, &bufferDeviceAdressInfo); - } - updateBuffers(); - } - - void updateBuffers() - { - scene.mvp = camera.matrices.perspective * camera.matrices.view; - memcpy(scene.buffer.mapped, &scene, sizeof(glm::mat4)); - - cubes[0].modelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f)); - cubes[1].modelMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(1.5f, 0.5f, 0.0f)); - - for (auto& cube : cubes) { - cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); - cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - cube.modelMatrix = glm::rotate(cube.modelMatrix, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); - cube.modelMatrix = glm::scale(cube.modelMatrix, glm::vec3(0.25f)); - memcpy(cube.buffer.mapped, &cube.modelMatrix, sizeof(glm::mat4)); - } - } - - void prepare() - { - VulkanExampleBase::prepare(); - - // We need this extension function to get the address of a buffer so we can pass it to the shader - vkGetBufferDeviceAddressKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR")); - - loadAssets(); - prepareBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = 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); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - - model.bindBuffers(drawCmdBuffers[i]); - - // Instead of using descriptors to pass global and per-model matrices to the shader, we can now simply pass buffer references via push constants - // The shader then simply reads data from the address of that reference - PushConstantBlock references{}; - // Pass pointer to the global matrix via a buffer device address - references.sceneReference = scene.bufferDeviceAddress; - - for (auto& cube : cubes) { - // Pass pointer to this cube's data buffer via a buffer device address - // So instead of having to bind different descriptors, we only pass a different device address - // This doesn't have to be an address from a different buffer, but could very well be just another address in the same buffer - references.modelReference = cube.bufferDeviceAddress; - vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstantBlock), &references); - - model.draw(drawCmdBuffers[i]); - } - - 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(); - } - - virtual void render() - { - if (!prepared) - return; - draw(); - if (animate && !paused) { - 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 && !paused)) { - updateBuffers(); - } - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Animate", &animate); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/computecloth/computecloth.cpp b/examples/computecloth/computecloth.cpp deleted file mode 100755 index f8b98801..00000000 --- a/examples/computecloth/computecloth.cpp +++ /dev/null @@ -1,768 +0,0 @@ -/* -* Vulkan Example - Compute shader cloth simulation -* -* A compute shader updates a shader storage buffer that contains particles held together by springs and also does basic -* collision detection against a sphere. This storage buffer is then used as the vertex input for the graphics part of the sample -* -* Copyright (C) 2016-2025 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" - - -class VulkanExample : public VulkanExampleBase -{ -public: - uint32_t readSet{ 0 }; - uint32_t indexCount{ 0 }; - bool simulateWind{ false }; - // This will be set to true, if the device has a dedicated queue from a compute only queue family - // With such a queue graphics and compute workloads can run in parallel, but this also requires additional barriers (often called "async compute") - // These barriers will release and acquire the resources used in graphics and compute between the different queue families - bool dedicatedComputeQueue{ false }; - - vks::Texture2D textureCloth; - vkglTF::Model modelSphere; - - // The cloth is made from a grid of particles - struct Particle { - glm::vec4 pos; - glm::vec4 vel; - glm::vec4 uv; - glm::vec4 normal; - }; - - // Cloth definition parameters - struct Cloth { - glm::uvec2 gridsize{ 60, 60 }; - glm::vec2 size{ 5.0f, 5.0f }; - } cloth; - - // We put the resource "types" into structs to make this sample easier to understand - - // We use two buffers for our cloth simulation: One with the input cloth data and one for outputting updated values - // The compute pipeline will update the output buffer, and the graphics pipeline will it as a vertex buffer - struct StorageBuffers { - vks::Buffer input; - vks::Buffer output; - } storageBuffers; - - // Resources for the graphics part of the example - struct Graphics { - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - struct Pipelines { - VkPipeline cloth{ VK_NULL_HANDLE }; - VkPipeline sphere{ VK_NULL_HANDLE }; - } pipelines; - // The vertices will be stored in the shader storage buffers, so we only need an index buffer in this structure - vks::Buffer indices; - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::vec4 lightPos{ -2.0f, 4.0f, -2.0f, 1.0f }; - } uniformData; - vks::Buffer uniformBuffer; - } graphics; - - // Resources for the compute part of the example - // Number of compute command buffers: set to 1 for serialized processing or 2 for in-parallel with graphics queue - static constexpr size_t computeCommandBufferCount = 2 ; - struct Compute { - typedef struct Semaphores_t { - VkSemaphore ready{ VK_NULL_HANDLE }; - VkSemaphore complete{ VK_NULL_HANDLE }; - } semaphores_t; - std::array semaphores{}; - VkQueue queue{ VK_NULL_HANDLE }; - VkCommandPool commandPool{ VK_NULL_HANDLE }; - std::array commandBuffers{}; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - std::array descriptorSets{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - struct UniformData { - float deltaT{ 0.0f }; - // These arguments define the spring setup for the cloth piece - // Changing these changes how the cloth reacts - float particleMass{ 0.1f }; - float springStiffness{ 2000.0f }; - float damping{ 0.25f }; - float restDistH{ 0 }; - float restDistV{ 0 }; - float restDistD{ 0 }; - float sphereRadius{ 1.0f }; - glm::vec4 spherePos{ 0.0f, 0.0f, 0.0f, 0.0f }; - glm::vec4 gravity{ 0.0f, 9.8f, 0.0f, 0.0f }; - glm::ivec2 particleCount{ 0 }; - } uniformData; - vks::Buffer uniformBuffer; - } compute; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // Graphics - graphics.indices.destroy(); - 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.uniformBuffer.destroy(); - vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); - vkDestroyPipeline(device, compute.pipeline, nullptr); - for (uint32_t i = 0; i < compute.semaphores.size(); i++) { - vkDestroySemaphore(device, compute.semaphores[i].ready, nullptr); - vkDestroySemaphore(device, compute.semaphores[i].complete, nullptr); - } - vkDestroyCommandPool(device, compute.commandPool, nullptr); - - // SSBOs - storageBuffers.input.destroy(); - storageBuffers.output.destroy(); - } - } - - // 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, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) - { - if (dedicatedComputeQueue) { - VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); - bufferBarrier.srcAccessMask = srcAccessMask; - bufferBarrier.dstAccessMask = dstAccessMask; - bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - bufferBarrier.size = VK_WHOLE_SIZE; - - std::vector bufferBarriers; - bufferBarrier.buffer = storageBuffers.input.buffer; - bufferBarriers.push_back(bufferBarrier); - bufferBarrier.buffer = storageBuffers.output.buffer; - bufferBarriers.push_back(bufferBarrier); - vkCmdPipelineBarrier(commandBuffer, - srcStageMask, - dstStageMask, - VK_FLAGS_NONE, - 0, nullptr, - static_cast(bufferBarriers.size()), bufferBarriers.data(), - 0, nullptr); - } - } - - void addComputeToComputeBarriers(VkCommandBuffer commandBuffer, uint32_t readSet) - { - VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); - bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.size = VK_WHOLE_SIZE; - std::vector bufferBarriers; - if (readSet == 0) - { - // SRS - we have written to output.buffer and need a memory barrier before reading it - // - don't need a memory barrier for input.buffer, the execution barrier is enough - bufferBarrier.buffer = storageBuffers.output.buffer; - bufferBarriers.push_back(bufferBarrier); - } - else //if (readSet == 1) - { - // SRS - we have written to input.buffer and need a memory barrier before reading it - // - don't need a memory barrier for output.buffer, the execution barrier is enough - bufferBarrier.buffer = storageBuffers.input.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(bufferBarriers.size()), bufferBarriers.data(), - 0, nullptr); - } - - void addComputeToGraphicsBarriers(VkCommandBuffer commandBuffer, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) - { - if (dedicatedComputeQueue) { - VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier(); - bufferBarrier.srcAccessMask = srcAccessMask; - bufferBarrier.dstAccessMask = dstAccessMask; - bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - bufferBarrier.size = VK_WHOLE_SIZE; - std::vector bufferBarriers; - bufferBarrier.buffer = storageBuffers.input.buffer; - bufferBarriers.push_back(bufferBarrier); - bufferBarrier.buffer = storageBuffers.output.buffer; - bufferBarriers.push_back(bufferBarrier); - vkCmdPipelineBarrier( - commandBuffer, - srcStageMask, - dstStageMask, - VK_FLAGS_NONE, - 0, nullptr, - static_cast(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], 0, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT); - - // 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 - 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, &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_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - - } - - void buildComputeCommandBuffer() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - cmdBufInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; - - for (uint32_t i = 0; i < compute.commandBuffers.size(); i++) { - - VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffers[i], &cmdBufInfo)); - - // Acquire the storage buffers from the graphics queue - addGraphicsToComputeBarriers(compute.commandBuffers[i], 0, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); - - 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 - // SRS - Iterations **must** be an even number, so that readSet starts at 1 and the final result ends up in output.buffer with readSet equal to 0 - 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], readSet); - } - - } - - // release the storage buffers back to the graphics queue - addComputeToGraphicsBarriers(compute.commandBuffers[i], VK_ACCESS_SHADER_WRITE_BIT, 0, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); - vkEndCommandBuffer(compute.commandBuffers[i]); - } - } - - // Setup and fill the shader storage buffers containing the particles - // These buffers are used as shader storage buffers in the compute shader (to update them) and as vertex input in the vertex shader (to display them) - void prepareStorageBuffers() - { - std::vector 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); - - // Set up a flat cloth that 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); - } - } - - 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()); - - // SSBOs will be used both as storage buffers (compute) and vertex buffers (graphics) - 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, - &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, - &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, storageBuffers.output.buffer, 1, ©Region); - // 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, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - - // Indices - std::vector 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(indices.size()) * sizeof(uint32_t); - indexCount = static_cast(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, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - } - - // Prepare the resources used for the graphics part of the sample - void prepareGraphics() - { - // Uniform buffer for passing data to the vertex shader - vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &graphics.uniformBuffer, sizeof(Graphics::UniformData)); - VK_CHECK_RESULT(graphics.uniformBuffer.map()); - - // Descriptor pool - std::vector 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)); - - // Descriptor layout - std::vector 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)); - - // Decscriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - // Pipeline - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - // Rendering pipeline - std::array shaderStages = { - loadShader(getShadersPath() + "computecloth/cloth.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "computecloth/cloth.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass); - - // Vertex Input - std::vector inputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) - }; - // Attribute descriptions based on the particles of the cloth - std::vector 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(inputBindings.size()); - inputState.pVertexBindingDescriptions = inputBindings.data(); - inputState.vertexAttributeDescriptionCount = static_cast(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(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(inputAttributes.size()); - inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - inputAssemblyState.primitiveRestartEnable = VK_FALSE; - rasterizationState.polygonMode = VK_POLYGON_MODE_FILL; - shaderStages = { - loadShader(getShadersPath() + "computecloth/sphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "computecloth/sphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.sphere)); - - buildCommandBuffers(); - } - - // Prepare the resources used for the compute part of the sample - void prepareCompute() - { - // Create a compute capable device queue - vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); - - // Uniform buffer for passing data to the compute shader - vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &compute.uniformBuffer, sizeof(Compute::UniformData)); - VK_CHECK_RESULT(compute.uniformBuffer.map()); - - // Set some initial values - float dx = cloth.size.x / (cloth.gridsize.x - 1); - float dy = cloth.size.y / (cloth.gridsize.y - 1); - - compute.uniformData.restDistH = dx; - compute.uniformData.restDistV = dy; - compute.uniformData.restDistD = sqrtf(dx * dx + dy * dy); - compute.uniformData.particleCount = cloth.gridsize; - - // Create compute pipeline - std::vector 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 computeWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &storageBuffers.input.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &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, &storageBuffers.output.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &storageBuffers.input.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor) - }; - - vkUpdateDescriptorSets(device, static_cast(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, static_cast(compute.commandBuffers.size())); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffers[0])); - - // Semaphores for graphics / compute synchronization - VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo(); - for (uint32_t i = 0; i < compute.semaphores.size(); i++) { - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphores[i].ready)); - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphores[i].complete)); - } - - // Build a single command buffer containing the compute dispatch commands - buildComputeCommandBuffer(); - } - - void updateComputeUBO() - { - if (!paused) { - // SRS - Clamp frameTimer to max 20ms refresh period (e.g. if blocked on resize), otherwise image breakup can occur - compute.uniformData.deltaT = fmin(frameTimer, 0.02f) * 0.0025f; - - if (simulateWind) { - std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::uniform_real_distribution rd(1.0f, 12.0f); - compute.uniformData.gravity.x = cos(glm::radians(-timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); - compute.uniformData.gravity.z = sin(glm::radians(timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine)); - } - else { - compute.uniformData.gravity.x = 0.0f; - compute.uniformData.gravity.z = 0.0f; - } - } - else { - compute.uniformData.deltaT = 0.0f; - } - memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData)); - } - - void updateGraphicsUBO() - { - graphics.uniformData.projection = camera.matrices.perspective; - graphics.uniformData.view = camera.matrices.view; - memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData)); - } - - void draw() - { - // As we use both graphics and compute, frame submission is a bit more involved - // We'll be using semaphores to synchronize between the compute shader updating the cloth and the graphics pipeline drawing it - - static bool firstDraw = true; - static uint32_t computeSubmitIndex{ 0 }, graphicsSubmitIndex{ 0 }; - if (computeCommandBufferCount > 1) - { - // SRS - if we are double buffering the compute queue, swap the compute command buffer indices - graphicsSubmitIndex = computeSubmitIndex; - computeSubmitIndex = 1 - graphicsSubmitIndex; - } - - VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo(); - VkPipelineStageFlags computeWaitDstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - if (!firstDraw) { - computeSubmitInfo.waitSemaphoreCount = 1; - computeSubmitInfo.pWaitSemaphores = &compute.semaphores[computeSubmitIndex].ready; - computeSubmitInfo.pWaitDstStageMask = &computeWaitDstStageMask; - } - else { - firstDraw = false; - if (computeCommandBufferCount > 1) - { - // SRS - if we are double buffering the compute queue, submit extra command buffer at start - computeSubmitInfo.signalSemaphoreCount = 1; - computeSubmitInfo.pSignalSemaphores = &compute.semaphores[graphicsSubmitIndex].complete; - computeSubmitInfo.commandBufferCount = 1; - computeSubmitInfo.pCommandBuffers = &compute.commandBuffers[graphicsSubmitIndex]; - - VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE)); - - // Add an extra set of acquire and release barriers to the graphics queue, - // so that when the second compute command buffer executes for the first time - // it doesn't complain about a lack of a corresponding "acquire" to its "release" and vice versa - VkCommandBuffer barrierCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - addComputeToGraphicsBarriers(barrierCmd, 0, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT); - addGraphicsToComputeBarriers(barrierCmd, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); - vulkanDevice->flushCommandBuffer(barrierCmd, queue, true); - } - } - computeSubmitInfo.signalSemaphoreCount = 1; - computeSubmitInfo.pSignalSemaphores = &compute.semaphores[computeSubmitIndex].complete; - computeSubmitInfo.commandBufferCount = 1; - computeSubmitInfo.pCommandBuffers = &compute.commandBuffers[computeSubmitIndex]; - - 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[graphicsSubmitIndex].complete - }; - VkSemaphore signalSemaphores[2] = { - semaphores.renderComplete, compute.semaphores[graphicsSubmitIndex].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 - // You can use DEBUG_FORCE_SHARED_GRAPHICS_COMPUTE_QUEUE preprocessor define to force graphics and compute from 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 - dedicatedComputeQueue = vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute; - loadAssets(); - prepareStorageBuffers(); - prepareGraphics(); - prepareCompute(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateGraphicsUBO(); - updateComputeUBO(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Simulate wind", &simulateWind); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/computecullandlod/computecullandlod.cpp b/examples/computecullandlod/computecullandlod.cpp deleted file mode 100644 index 82a829e8..00000000 --- a/examples/computecullandlod/computecullandlod.cpp +++ /dev/null @@ -1,822 +0,0 @@ -/* -* Vulkan Example - Compute shader culling and LOD using indirect rendering -* -* Copyright (C) 2016-2025 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" - - -// Total number of objects (^3) in the scene -#if defined(__ANDROID__) -constexpr auto OBJECT_COUNT = 32; -#else -constexpr auto OBJECT_COUNT = 64; -#endif - -constexpr auto 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{ 0.0f }; - float scale{ 1.0f }; - }; - - // 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 indirectCommands; - - struct { - glm::mat4 projection; - glm::mat4 modelview; - glm::vec4 cameraPos; - glm::vec4 frustumPlanes[6]; - } uboScene; - - struct { - vks::Buffer scene; - } uniformData; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - // 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() - { - title = "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() - { - if (device) { - vkDestroyPipeline(device, pipeline, 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; - } - // This is required for for using firstInstance - enabledFeatures.drawIndirectFirstInstance = 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)); - - // Acquire barrier - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkBufferMemoryBarrier buffer_barrier = - { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - nullptr, - 0, - VK_ACCESS_INDIRECT_COMMAND_READ_BIT, - vulkanDevice->queueFamilyIndices.compute, - vulkanDevice->queueFamilyIndices.graphics, - indirectCommandsBuffer.buffer, - 0, - indirectCommandsBuffer.descriptor.range - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - - 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, pipeline); - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &lodModel.vertices.buffer, offsets); - vkCmdBindVertexBuffers(drawCmdBuffers[i], 1, 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, static_cast(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]); - - // Release barrier - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkBufferMemoryBarrier buffer_barrier = - { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - nullptr, - VK_ACCESS_INDIRECT_COMMAND_READ_BIT, - 0, - vulkanDevice->queueFamilyIndices.graphics, - vulkanDevice->queueFamilyIndices.compute, - indirectCommandsBuffer.buffer, - 0, - indirectCommandsBuffer.descriptor.range - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - - 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)); - - // Acquire barrier - // Add memory barrier to ensure that the indirect commands have been consumed before the compute shader updates them - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkBufferMemoryBarrier buffer_barrier = - { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - nullptr, - 0, - VK_ACCESS_SHADER_WRITE_BIT, - vulkanDevice->queueFamilyIndices.graphics, - vulkanDevice->queueFamilyIndices.compute, - indirectCommandsBuffer.buffer, - 0, - indirectCommandsBuffer.descriptor.range - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 1, &buffer_barrier, - 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); - - // Clear the buffer that the compute shader pass will write statistics and draw calls to - vkCmdFillBuffer(compute.commandBuffer, indirectDrawCountBuffer.buffer, 0, indirectCommandsBuffer.descriptor.range, 0); - - // This barrier ensures that the fill command is finished before the compute shader can start writing to the buffer - VkMemoryBarrier memoryBarrier = vks::initializers::memoryBarrier(); - memoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_FLAGS_NONE, - 1, &memoryBarrier, - 0, nullptr, - 0, nullptr); - - // 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); - - // Release barrier - // Add memory barrier to ensure that the compute shader has finished writing the indirect command buffer before it's consumed - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkBufferMemoryBarrier buffer_barrier = - { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - nullptr, - VK_ACCESS_SHADER_WRITE_BIT, - 0, - vulkanDevice->queueFamilyIndices.compute, - vulkanDevice->queueFamilyIndices.graphics, - indirectCommandsBuffer.buffer, - 0, - indirectCommandsBuffer.descriptor.range - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - - // todo: barrier for indirect stats buffer? - - vkEndCommandBuffer(compute.commandBuffer); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformData.scene.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // 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 bindingDescriptions; - std::vector 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(0, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - // Binding point 1: Instanced data at per-instance rate - vks::initializers::vertexInputBindingDescription(1, 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(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, pos)), // Location 0: Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, normal)), // Location 1: Normal - vks::initializers::vertexInputAttributeDescription(0, 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(1, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)), // Location 4: Position - vks::initializers::vertexInputAttributeDescription(1, 4, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)), // Location 5: Scale - }; - inputState.pVertexBindingDescriptions = bindingDescriptions.data(); - inputState.pVertexAttributeDescriptions = attributeDescriptions.data(); - inputState.vertexBindingDescriptionCount = static_cast(bindingDescriptions.size()); - inputState.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); - - // Indirect (and instanced) 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_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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); - std::array shaderStages = { - loadShader(getShadersPath() + "computecullandlod/indirectdraw.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "computecullandlod/indirectdraw.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - 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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); - } - - void prepareBuffers() - { - objectCount = OBJECT_COUNT * OBJECT_COUNT * OBJECT_COUNT; - - vks::Buffer stagingBuffer; - - std::vector 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(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_BUFFER_USAGE_TRANSFER_DST_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)); - - // Copy from staging buffer to instance buffer - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = {}; - copyRegion.size = stagingBuffer.size; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, instanceBuffer.buffer, 1, ©Region); - // 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" - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { VkBufferMemoryBarrier buffer_barrier = - { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - nullptr, - VK_ACCESS_INDIRECT_COMMAND_READ_BIT, - 0, - vulkanDevice->queueFamilyIndices.graphics, - vulkanDevice->queueFamilyIndices.compute, - indirectCommandsBuffer.buffer, - 0, - indirectCommandsBuffer.descriptor.range - }; - - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - 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 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(); - } - - 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 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(setLayoutBindings.size())); - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - - std::vector 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(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - - // 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(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() - { - 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 prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareBuffers(); - setupDescriptors(); - preparePipelines(); - prepareCompute(); - buildCommandBuffers(); - prepared = true; - } - - 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 stageFlags = { - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - }; - std::array waitSemaphores = { - semaphores.presentComplete, // Wait for presentation to finished - compute.semaphore // Wait for compute to finish - }; - - submitInfo.pWaitSemaphores = waitSemaphores.data(); - submitInfo.waitSemaphoreCount = static_cast(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)); - } - - virtual void render() - { - if (!prepared) - { - return; - } - updateUniformBuffer(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Freeze frustum", &fixedFrustum); - } - 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() diff --git a/examples/computeheadless/computeheadless.cpp b/examples/computeheadless/computeheadless.cpp deleted file mode 100644 index d0805037..00000000 --- a/examples/computeheadless/computeheadless.cpp +++ /dev/null @@ -1,636 +0,0 @@ -/* -* Vulkan Example - Minimal headless compute example -* -* Copyright (C) 2017-2025 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 -#include -#include -#include -#include "VulkanAndroid.h" -#endif - -#include -#include -#include -#include -#include -#include -#include - -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) -#define VK_ENABLE_BETA_EXTENSIONS -#endif -#include -#include "VulkanTools.h" -#include "CommandLineParser.hpp" - -#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; -} - -CommandLineParser commandLineParser; - -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{}; - - std::string shaderDir = "glsl"; - - 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; - break; - } - } - 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 - - if (commandLineParser.isSet("shaders")) { - shaderDir = commandLineParser.getValueAsString("shaders", "glsl"); - } - - VkApplicationInfo appInfo = {}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Vulkan headless example"; - appInfo.pEngineName = "VulkanExample"; - appInfo.apiVersion = VK_API_VERSION_1_0; - // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions - if (shaderDir == "slang") { - appInfo.apiVersion = VK_API_VERSION_1_1; - } - - /* - Vulkan instance creation (without surface extensions) - */ - VkInstanceCreateInfo instanceCreateInfo = {}; - instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instanceCreateInfo.pApplicationInfo = &appInfo; - - uint32_t layerCount = 1; - const char* validationLayers[] = { "VK_LAYER_KHRONOS_validation" }; - - std::vector instanceExtensions = {}; -#if DEBUG - // Check if layers are available - uint32_t instanceLayerCount; - vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); - std::vector 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; - } - } - - if (layersAvailable) { - instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); - instanceCreateInfo.ppEnabledLayerNames = validationLayers; - instanceCreateInfo.enabledLayerCount = layerCount; - } -#endif -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // SRS - When running on macOS with MoltenVK, enable VK_KHR_get_physical_device_properties2 (required by VK_KHR_portability_subset) - instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); -#if defined(VK_KHR_portability_enumeration) - // SRS - When running on macOS with MoltenVK and VK_KHR_portability_enumeration is defined and supported by the instance, enable the extension and the flag - uint32_t instanceExtCount = 0; - vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, nullptr); - if (instanceExtCount > 0) - { - std::vector extensions(instanceExtCount); - if (vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, &extensions.front()) == VK_SUCCESS) - { - for (VkExtensionProperties extension : extensions) - { - if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) - { - instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - break; - } - } - } - } -#endif -#endif - instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size(); - instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data(); - 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(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 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 queueFamilyProperties(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data()); - for (uint32_t i = 0; i < static_cast(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; - std::vector deviceExtensions = {}; - - // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions - if (shaderDir == "slang") { - deviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME); - deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); - } - -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset) - // When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension - uint32_t deviceExtCount = 0; - vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, nullptr); - if (deviceExtCount > 0) - { - std::vector extensions(deviceExtCount); - if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, &extensions.front()) == VK_SUCCESS) - { - for (VkExtensionProperties extension : extensions) - { - if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) - { - deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); - break; - } - } - } - } -#endif - deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size(); - deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); - 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 computeInput(BUFFER_ELEMENTS); - std::vector 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, ©Cmd)); - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); - - VkBufferCopy copyRegion = {}; - copyRegion.size = bufferSize; - vkCmdCopyBuffer(copyCmd, hostBuffer, deviceBuffer, 1, ©Region); - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - - VkSubmitInfo submitInfo = vks::initializers::submitInfo(); - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ©Cmd; - 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, ©Cmd); - } - - /* - Prepare compute pipeline - */ - { - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1), - }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - std::vector 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 computeWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &bufferDescriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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); - - const std::string shadersPath = getShaderBasePath() + shaderDir + "/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, ©Region); - - // 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(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_pollOnce(-1, NULL, &events, (void**)&source)) > ALOOPER_POLL_TIMEOUT) { - if (source != NULL) { - source->process(androidapp, source); - } - if (androidapp->destroyRequested != 0) { - break; - } - } -} -#else -int main(int argc, char* argv[]) { - commandLineParser.add("help", { "--help" }, 0, "Show help"); - commandLineParser.add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl, hlsl or slang)"); - commandLineParser.parse(argc, argv); - if (commandLineParser.isSet("help")) { - commandLineParser.printHelp(); - std::cin.get(); - return 0; - } - VulkanExample *vulkanExample = new VulkanExample(); - std::cout << "Finished. Press enter to terminate..."; - std::cin.get(); - delete(vulkanExample); - return 0; -} -#endif diff --git a/examples/computenbody/computenbody.cpp b/examples/computenbody/computenbody.cpp deleted file mode 100644 index dba6934b..00000000 --- a/examples/computenbody/computenbody.cpp +++ /dev/null @@ -1,677 +0,0 @@ -/* -* Vulkan Example - Compute shader N-body simulation using two passes and shared compute shader memory -* -* This sample shows how to combine compute and graphics for doing N-body particle simulaton -* It calculates the particle system movement using two separate compute passes: calculating particle positions and integrating particles -* For that a shader storage buffer is used which is then used as a vertex buffer for drawing the particle system with a graphics pipeline -* To optimize performance, the compute shaders use shared memory -* -* Copyright (C) 2016-2023 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(__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: - struct Textures { - vks::Texture2D particle; - vks::Texture2D gradient; - } textures{}; - - // Particle Definition - struct Particle { - glm::vec4 pos; // xyz = position, w = mass - glm::vec4 vel; // xyz = velocity, w = gradient texture position - }; - uint32_t numParticles{ 0 }; - - // We use a shader storage buffer object to store the particlces - // This is updated by the compute pipeline and displayed as a vertex buffer by the graphics pipeline - vks::Buffer storageBuffer; - - // Resources for the graphics part of the example - struct Graphics { - 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 - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::vec2 screenDim; - } uniformData; - vks::Buffer uniformBuffer; // Contains scene matrices - } graphics; - - // Resources for the compute part of the example - struct Compute { - uint32_t queueFamilyIndex; // Used to check if compute and graphics queue families differ and require additional barriers - 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) - struct UniformData { // Compute shader uniform block object - float deltaT{ 0.0f }; // Frame delta time - int32_t particleCount{ 0 }; - // Parameters used to control the behaviour of the particle system - float gravity{ 0.002f }; - float power{ 0.75f }; - float soften{ 0.05f }; - } uniformData; - vks::Buffer uniformBuffer; // Uniform buffer object containing particle system parameters - } compute; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // 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.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); - - storageBuffer.destroy(); - - 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_TOP_OF_PIPE_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], 0, 1, &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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_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 = storageBuffer.buffer; - bufferBarrier.size = 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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() - { - // We mark a few particles as attractors that move along a given path, these will pull in the other particles - std::vector 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), - }; - - numParticles = static_cast(attractors.size()) * PARTICLES_PER_ATTRACTOR; - - // Initial particle positions - std::vector particleBuffer(numParticles); - - std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::normal_distribution rndDist(0.0f, 1.0f); - - for (uint32_t i = 0; i < static_cast(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(attractors.size()); - } - } - - compute.uniformData.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()); - // The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline - 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, &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, storageBuffer.buffer, 1, ©Region); - // 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - } - - void prepareGraphics() - { - // 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::UniformData)); - VK_CHECK_RESULT(graphics.uniformBuffer.map()); - - // Descriptor pool - std::vector 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(poolSizes, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); - - // Descriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - // Pipeline - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Vertex Input state - std::vector inputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector inputAttributes = { - // Location 0 : Position - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, pos)), - // Location 1 : Velocity (used for color gradient lookup) - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, vel)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(inputBindings.size()); - vertexInputState.pVertexBindingDescriptions = inputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(inputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = inputAttributes.data(); - - // Shaders - 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 = &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(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)); - - // We use a semaphore to synchronize compute and graphics - VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo(); - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore)); - - // Signal the semaphore for the first run - 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)); - - buildCommandBuffers(); - } - - 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); - - // 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::UniformData)); - VK_CHECK_RESULT(compute.uniformBuffer.map()); - - // Create compute pipeline - // Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index) - - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - - std::vector computeWriteDescriptorSets = { - // Binding 0 : Particle position storage buffer - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &storageBuffer.descriptor), - // Binding 1 : Uniform buffer - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,1,&compute.uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - - // Create pipelines - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); - - // 1st pass - computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computenbody/particle_calculate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); - - // We want to use as much shared memory for the compute shader invocations as available, so we calculate it based on the device limits and pass it to the shader via specialization constants - uint32_t sharedDataSize = std::min((uint32_t)1024, (uint32_t)(vulkanDevice->properties.limits.maxComputeSharedMemorySize / sizeof(glm::vec4))); - VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t)); - VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(int32_t), &sharedDataSize); - 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)); - - // Build a single command buffer containing the compute dispatch commands - buildComputeCommandBuffer(); - } - - void updateComputeUniformBuffers() - { - compute.uniformData.deltaT = paused ? 0.0f : frameTimer * 0.05f; - memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData)); - } - - void updateGraphicsUniformBuffers() - { - graphics.uniformData.projection = camera.matrices.perspective; - graphics.uniformData.view = camera.matrices.view; - graphics.uniformData.screenDim = glm::vec2((float)width, (float)height); - memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData)); - } - - 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(); - prepareStorageBuffers(); - prepareGraphics(); - prepareCompute(); - prepared = true; - } - - 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(); - } - - virtual void render() - { - if (!prepared) - return; - updateComputeUniformBuffers(); - updateGraphicsUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/computeparticles/computeparticles.cpp b/examples/computeparticles/computeparticles.cpp deleted file mode 100644 index 2d97b987..00000000 --- a/examples/computeparticles/computeparticles.cpp +++ /dev/null @@ -1,646 +0,0 @@ -/* -* Vulkan Example - Attraction based compute shader particle system -* -* Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher) -* -* Copyright (C) 2016-2024 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(__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; - - // 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 - }; - - // We use a shader storage buffer object to store the particlces - // This is updated by the compute pipeline and displayed as a vertex buffer by the graphics pipeline - vks::Buffer storageBuffer; - - // Resources for the graphics part of the example - struct Graphics { - 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 Compute { - uint32_t queueFamilyIndex; // Used to check if compute and graphics queue families differ and require additional barriers - 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 - vks::Buffer uniformBuffer; // Uniform buffer object containing particle system parameters - struct UniformData { // 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; - } uniformData; - } compute; - - VulkanExample() : VulkanExampleBase() - { - title = "Compute shader particle system"; - } - - ~VulkanExample() - { - if (device) { - // Graphics - vkDestroyPipeline(device, graphics.pipeline, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - vkDestroySemaphore(device, graphics.semaphore, nullptr); - - // Compute - 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); - - storageBuffer.destroy(); - 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_TOP_OF_PIPE_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], 0, 1, &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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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 rndDist(-1.0f, 1.0f); - - // Initial particle positions - std::vector 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, - &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, storageBuffer.buffer, 1, ©Region); - // 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - } - - // The descriptor pool will be shared between graphics and compute - void setupDescriptorPool() - { - std::vector 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(poolSizes, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - - void prepareGraphics() - { - prepareStorageBuffers(); - prepareUniformBuffers(); - - // Descriptor set layout - std::vector setLayoutBindings = { - // Binding 0 : Particle color map - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), - // Binding 1 : Particle gradient ramp - 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)); - - // Descriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - // Pipeline - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Vertex Input state - std::vector inputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector inputAttributes = { - // Location 0 : Position - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, pos)), - // Location 1 : Velocity (used for color gradient lookup) - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, gradientPos)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(inputBindings.size()); - vertexInputState.pVertexBindingDescriptions = inputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(inputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = inputAttributes.data(); - - 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 = &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(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)); - - // 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() - { - // 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 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout,1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - std::vector computeWriteDescriptorSets = { - // Binding 0 : Particle position storage buffer - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 0, - &storageBuffer.descriptor), - // Binding 1 : Uniform buffer - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 1, - &compute.uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL); - - // Create pipeline - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - 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)); - - // 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::UniformData)); - // Map for host access - VK_CHECK_RESULT(compute.uniformBuffer.map()); - - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - compute.uniformData.deltaT = paused ? 0.0f : frameTimer * 2.5f; - if (!attachToCursor) - { - compute.uniformData.destX = sin(glm::radians(timer * 360.0f)) * 0.75f; - compute.uniformData.destY = 0.0f; - } - else - { - float normalizedMx = (mouseState.position.x - static_cast(width / 2)) / static_cast(width / 2); - float normalizedMy = (mouseState.position.y - static_cast(height / 2)) / static_cast(height / 2); - compute.uniformData.destX = normalizedMx; - compute.uniformData.destY = normalizedMy; - } - - memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData)); - } - - 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(); - // 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/examples/computeraytracing/computeraytracing.cpp b/examples/computeraytracing/computeraytracing.cpp deleted file mode 100644 index cab1f861..00000000 --- a/examples/computeraytracing/computeraytracing.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/* -* Vulkan Example - Compute shader based ray tracing -* -* This samples implements a basic ray tracer with materials and reflections using a compute shader -* Shader storage buffers are used to pass geometry information for spheres and planes to the computer shader -* The compute shader then uses these as the scene geometry for ray tracing and outputs the results to a storage image -* The graphics part of the sample then displays that image full screen -* Not to be confused with actual hardware accelerated ray tracing -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -class VulkanExample : public VulkanExampleBase -{ -public: - // The compute shader will store the ray traced output to a storage image - vks::Texture storageImage{}; - - // Resources for the graphics part of the example. The graphics pipeline simply displays the compute shader output - struct Graphics { - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - } graphics; - - // Resources for the compute part of the example - struct Compute { - // Object properties for planes and spheres are passed via a shade storage buffer - // There is no vertex data, the compute shader calculates the primitives on the fly - vks::Buffer objectStorageBuffer; - vks::Buffer uniformBuffer; // Uniform buffer object containing scene parameters - VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics) - VkCommandPool commandPool{ VK_NULL_HANDLE }; // Use a separate command pool (queue family may differ from the one used for graphics) - VkCommandBuffer commandBuffer{ VK_NULL_HANDLE }; // Command buffer storing the dispatch commands and barriers - VkFence fence{ VK_NULL_HANDLE }; // Synchronization fence to avoid rewriting compute CB if still in use - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline - VkPipeline pipeline{ VK_NULL_HANDLE }; // Compute raytracing pipeline - struct UniformDataCompute { // Compute shader uniform block object - glm::vec3 lightPos; - float aspectRatio{ 1.0f }; - 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; - glm::mat4 _pad; - } uniformData; - } compute; - - // Definitions for scene objects - // The sample uses spheres and planes that are passed to the compute shader via a shader storage buffer - // The computer shader uses the object type to select different calculations - enum class SceneObjectType { Sphere = 0, Plane = 1 }; - // Spheres and planes are described by different properties, we use a union for this - union SceneObjectProperty { - glm::vec4 positionAndRadius; - glm::vec4 normalAndDistance; - }; - struct SceneObject { - SceneObjectProperty objectProperties{}; - glm::vec3 diffuse; - float specular{ 1.0f }; - uint32_t id{ 0 }; - uint32_t objectType{ 0 }; - // Due to alignment rules we need to pad to make the element align at 16-bytes - glm::ivec2 _pad; - }; - - VulkanExample() : VulkanExampleBase() - { - title = "Compute shader ray tracing"; - 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() - { - if (device) { - // 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.objectStorageBuffer.destroy(); - - storageImage.destroy(); - } - } - - // Prepare a storage image that is used to store the compute shader ray tracing output - void prepareStorageImage() - { -#if defined(__ANDROID__) - // Use a smaller image on Android for performance reasons - const uint32_t textureSize = 1024; -#else - const uint32_t textureSize = 2048; -#endif - - const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; - - // Get device properties for the requested texture format - VkFormatProperties formatProperties; - vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); - // Check if requested image format supports image storage operations required for storing pixel from the compute shader - assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); - - // Prepare blit target texture - storageImage.width = textureSize; - storageImage.height = textureSize; - - VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = format; - imageCreateInfo.extent = { textureSize, textureSize, 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, &storageImage.image)); - vkGetImageMemoryRequirements(device, storageImage.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, &storageImage.deviceMemory)); - VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0)); - - VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.imageLayout); - // 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" - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkImageMemoryBarrier imageMemoryBarrier = {}; - imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = storageImage.image; - imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = 0; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - vkCmdPipelineBarrier( - layoutCmd, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } - 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, &storageImage.sampler)); - - // Create image view - VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo(); - view.viewType = VK_IMAGE_VIEW_TYPE_2D; - view.format = format; - view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.image = storageImage.image; - VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.view)); - - // Initialize a descriptor for later use - storageImage.descriptor.imageLayout = storageImage.imageLayout; - storageImage.descriptor.imageView = storageImage.view; - storageImage.descriptor.sampler = storageImage.sampler; - storageImage.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 = storageImage.image; - imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - // Acquire barrier for graphics queue - imageMemoryBarrier.srcAccessMask = 0; - imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } - else - { - // Combined barrier on single queue family - 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, (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]); - - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - // Release barrier from graphics queue - imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = 0; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - - } - - void buildComputeCommandBuffer() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo)); - - VkImageMemoryBarrier imageMemoryBarrier = {}; - imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = storageImage.image; - imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - // Acquire barrier for compute queue - imageMemoryBarrier.srcAccessMask = 0; - imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } - - 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, storageImage.width / 16, storageImage.height / 16, 1); - - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - // Release barrier from compute queue - imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = 0; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } - - vkEndCommandBuffer(compute.commandBuffer); - } - - // Setup and fill the compute shader storage buffes containing object definitions for the raytraced scene - void prepareStorageBuffers() - { - // Id used to identify objects by the ray tracing shader - uint32_t currentId = 0; - - std::vector sceneObjects{}; - - // Add some spheres to the scene - //std::vector spheres; - // Lambda to simplify object creation - auto addSphere = [&sceneObjects, ¤tId](glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) { - SceneObject sphere{}; - sphere.id = currentId++; - sphere.objectProperties.positionAndRadius = glm::vec4(pos, radius); - sphere.diffuse = diffuse; - sphere.specular = specular; - sphere.objectType = (uint32_t)SceneObjectType::Sphere; - sceneObjects.push_back(sphere); - }; - - auto addPlane = [&sceneObjects, ¤tId](glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) { - SceneObject plane{}; - plane.id = currentId++; - plane.objectProperties.normalAndDistance = glm::vec4(normal, distance); - plane.diffuse = diffuse; - plane.specular = specular; - plane.objectType = (uint32_t)SceneObjectType::Plane; - sceneObjects.push_back(plane); - }; - - addSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); - addSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f); - addSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f); - - const float roomDim = 4.0f; - addPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); - addPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); - addPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f); - addPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f); - addPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f); - addPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); - - VkDeviceSize storageBufferSize = sceneObjects.size() * sizeof(SceneObject); - - // Copy the data to the device - 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, sceneObjects.data()); - vulkanDevice->createBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &compute.objectStorageBuffer, storageBufferSize); - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = { 0, 0, storageBufferSize}; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.objectStorageBuffer.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - } - - // The descriptor pool will be shared between graphics and compute - void setupDescriptorPool() - { - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - - // Prepare the graphics resources used to display the ray traced output of the compute shader - void prepareGraphics() - { - // Setup descriptors - - // The graphics pipeline uses one set and one binding - // Binding 0: Storage image with raytraced output as a sampled image for displaying it - - std::vector 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, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &storageImage.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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); - - VkPipelineVertexInputStateCreateInfo emptyInputState{}; - emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass, 0); - 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 = static_cast(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - pipelineCreateInfo.renderPass = renderPass; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline)); - } - - // Prepare the compute resources 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); - - // Setup descriptors - - // The compute pipeline uses one set and four bindings - // Binding 0: Storage image for raytraced output - // Binding 1: Uniform buffer with parameters - // Binding 2: Shader storage buffer with scene object definitions - - std::vector setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0), - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1), - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - std::vector computeWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &storageImage.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &compute.uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, &compute.objectStorageBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - - // Create the compute shader pipeline - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - 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 from the graphics one - 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_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &compute.fence)); - - // Build a single command buffer containing the compute dispatch commands - buildComputeCommandBuffer(); - } - - 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::UniformDataCompute)); - } - - void updateUniformBuffers() - { - compute.uniformData.aspectRatio = (float)width / (float)height; - compute.uniformData.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.uniformData.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; - compute.uniformData.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.uniformData.camera.pos = camera.position * -1.0f; - VK_CHECK_RESULT(compute.uniformBuffer.map()); - memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformDataCompute)); - compute.uniformBuffer.unmap(); - } - - void prepare() - { - VulkanExampleBase::prepare(); - prepareStorageImage(); - prepareStorageBuffers(); - prepareUniformBuffers(); - setupDescriptorPool(); - prepareGraphics(); - prepareCompute(); - buildCommandBuffers(); - prepared = true; - } - - void draw() - { - // Submit compute commands - // Use a fence to ensure that compute command buffer has finished executing before using it again - VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo(); - computeSubmitInfo.commandBufferCount = 1; - computeSubmitInfo.pCommandBuffers = &compute.commandBuffer; - - VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, compute.fence)); - - vkWaitForFences(device, 1, &compute.fence, VK_TRUE, UINT64_MAX); - vkResetFences(device, 1, &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(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/computeshader/computeshader.cpp b/examples/computeshader/computeshader.cpp deleted file mode 100644 index 42c8c07e..00000000 --- a/examples/computeshader/computeshader.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/* -* Vulkan Example - Compute shader image processing -* -* This sample uses a compute shader to apply different filters to an image -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -// Vertex layout for this example -struct Vertex { - float pos[3]; - float uv[2]; -}; - -class VulkanExample : public VulkanExampleBase -{ -public: - // Input image - vks::Texture2D textureColorMap; - // Storage image that the compute shader uses to apply the filter effect to - vks::Texture2D storageImage; - - // Resources for the graphics part of the example - struct Graphics { - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Image display shader binding layout - VkDescriptorSet descriptorSetPreCompute{ VK_NULL_HANDLE }; // Image display shader bindings before compute shader image manipulation - VkDescriptorSet descriptorSetPostCompute{ VK_NULL_HANDLE }; // Image display shader bindings after compute shader image manipulation - VkPipeline pipeline{ VK_NULL_HANDLE }; // Image display pipeline - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the graphics pipeline - VkSemaphore semaphore{ VK_NULL_HANDLE }; // Execution dependency between compute & graphic submission - // Used to pass data to the graphics shaders - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - } uniformData; - vks::Buffer uniformBuffer; - } graphics; - - // Resources for the compute part of the example - struct Compute { - VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics) - VkCommandPool commandPool{ VK_NULL_HANDLE }; // Use a separate command pool (queue family may differ from the one used for graphics) - VkCommandBuffer commandBuffer{ VK_NULL_HANDLE }; // Command buffer storing the dispatch commands and barriers - VkSemaphore semaphore{ VK_NULL_HANDLE }; // Execution dependency between compute & graphic submission - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline - std::vector 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{ 0 }; - uint32_t vertexBufferSize{ 0 }; - - std::vector filterNames{}; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // Graphics - vkDestroyPipeline(device, graphics.pipeline, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - vkDestroySemaphore(device, graphics.semaphore, nullptr); - graphics.uniformBuffer.destroy(); - - // 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(); - - textureColorMap.destroy(); - storageImage.destroy(); - } - } - - // Prepare a storage image that is used to store the compute shader filter - void prepareStorageImage() - { - const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; - - VkFormatProperties formatProperties; - // Get device properties for the requested texture format - vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); - // Check if requested image format supports image storage operations required for storing pixel from the compute shader - assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); - - // Prepare blit target texture - storageImage.width = textureColorMap.width; - storageImage.height = textureColorMap.height; - - VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = format; - imageCreateInfo.extent = { storageImage.width, storageImage.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 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(); - } - VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &storageImage.image)); - - VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); - VkMemoryRequirements memReqs; - vkGetImageMemoryRequirements(device, storageImage.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, &storageImage.deviceMemory)); - VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0)); - - // Transition image to the general layout, so we can use it as a storage image in the compute shader - VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.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 = 1.0f; - sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &storageImage.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.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.image = storageImage.image; - VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.view)); - - // Initialize a descriptor for later use - storageImage.descriptor.imageLayout = storageImage.imageLayout; - storageImage.descriptor.imageView = storageImage.view; - storageImage.descriptor.sampler = storageImage.sampler; - storageImage.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 = storageImage.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], 0, 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, storageImage.width / 16, storageImage.height / 16, 1); - - vkEndCommandBuffer(compute.commandBuffer); - } - - // Setup vertices for a single uv-mapped quad used to display the input and output images - void generateQuad() - { - // Setup vertices for a single uv-mapped quad made from two triangles - std::vector 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 indices = { 0,1,2, 2,3,0 }; - indexCount = static_cast(indices.size()); - - // Create buffers and upload data to the GPU - - 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, vertices.size() * sizeof(Vertex), vertices.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, indices.size() * sizeof(uint32_t), indices.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, &vertexBuffer, vertices.size() * sizeof(Vertex))); - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t))); - - // Copy from host do device - vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue); - vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue); - - // Clean up - stagingBuffers.vertices.destroy(); - stagingBuffers.indices.destroy(); - } - - // The descriptor pool will be shared between graphics and compute - void setupDescriptorPool() - { - std::vector 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)); - } - - // Prepare the graphics resources used to display the ray traced output of the compute shader - void prepareGraphics() - { - // Create a 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)); - - // Setup descriptors - - // The graphics pipeline uses two sets with two bindings - // One set for displaying the input image and one set for displaying the output image with the compute filter applied - // Binding 0: Vertex shader uniform buffer - // Binding 1: Sampled image (before/after compute filter is applied) - - std::vector 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)); - - 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 baseImageWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureColorMap.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(baseImageWriteDescriptorSets.size()), baseImageWriteDescriptorSets.data(), 0, nullptr); - - // Final image (after compute shader processing) - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPostCompute)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &storageImage.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Graphics pipeline used to display the images (before and after the compute effect is applied) - - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Shaders - 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); - - // Vertex input state - std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline)); - } - - 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 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 pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - std::vector 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, &storageImage.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - - // Create compute shader pipelines - VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); - - // One pipeline for each available image filter - filterNames = { "emboss", "edgedetect", "sharpen" }; - for (auto& shaderName : filterNames) { - 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(); - } - - 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, &graphics.uniformBuffer, sizeof(Graphics::UniformData))); - // Map persistent - VK_CHECK_RESULT(graphics.uniformBuffer.map()); - } - - void updateUniformBuffers() - { - // We need to adjust the perspective as this sample displays two viewports side-by-side - camera.setPerspective(60.0f, (float)width * 0.5f / (float)height, 1.0f, 256.0f); - graphics.uniformData.projection = camera.matrices.perspective; - graphics.uniformData.modelView = camera.matrices.view; - memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - generateQuad(); - prepareUniformBuffers(); - prepareStorageImage(); - setupDescriptorPool(); - prepareGraphics(); - prepareCompute(); - buildCommandBuffers(); - prepared = true; - } - - 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(); - } - - virtual void render() - { - if (!prepared) - { - return; - } - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->comboBox("Shader", &compute.pipelineIndex, filterNames)) { - buildComputeCommandBuffer(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/conditionalrender/conditionalrender.cpp b/examples/conditionalrender/conditionalrender.cpp deleted file mode 100644 index e180c809..00000000 --- a/examples/conditionalrender/conditionalrender.cpp +++ /dev/null @@ -1,365 +0,0 @@ -/* -* 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-2024 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT{ VK_NULL_HANDLE }; - PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{ VK_NULL_HANDLE }; - - vkglTF::Model scene; - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - } uniformData; - vks::Buffer uniformBuffer; - - std::vector conditionalVisibility{}; - vks::Buffer conditionalBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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 - */ - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME); - } - - ~VulkanExample() - { - if (device) { - 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 descriptorsets = { - descriptorSet, - node->mesh->uniformBuffer.descriptorSet - }; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast(descriptorsets.size()), descriptorsets.data(), 0, NULL); - - vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(primitive->material.baseColorFactor), &primitive->material.baseColorFactor); - - /* - [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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); - - // Layouts - std::vector 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(setLayoutBindings.size()); - descriptorLayoutCI.pBindings = setLayoutBindings.data(); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); - - // Sets - VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - std::array 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), 0); - pipelineLayoutCI.pushConstantRangeCount = 1; - pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 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 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(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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = glm::scale(camera.matrices.view, glm::vec3(0.1f , -0.1f, 0.1f)); - uniformData.model = glm::translate(glm::mat4(1.0f), scene.dimensions.min); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - 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 * overlay->scale, 400.0f * overlay->scale), 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() diff --git a/examples/conservativeraster/conservativeraster.cpp b/examples/conservativeraster/conservativeraster.cpp deleted file mode 100644 index 63217393..00000000 --- a/examples/conservativeraster/conservativeraster.cpp +++ /dev/null @@ -1,686 +0,0 @@ -/* -* 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 (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -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{ 0 }; - } triangle; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - } uniformData; - vks::Buffer uniformBuffer; - - struct PipelineLayouts { - VkPipelineLayout scene{ VK_NULL_HANDLE }; - VkPipelineLayout fullscreen{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct Pipelines { - VkPipeline triangle{ VK_NULL_HANDLE }; - VkPipeline triangleConservativeRaster{ VK_NULL_HANDLE }; - VkPipeline triangleOverlay{ VK_NULL_HANDLE }; - VkPipeline fullscreen{ VK_NULL_HANDLE }; - } pipelines; - - struct DescriptorSetLayouts { - VkDescriptorSetLayout scene{ VK_NULL_HANDLE }; - VkDescriptorSetLayout fullscreen{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - - struct DescriptorSets { - VkDescriptorSet scene{ VK_NULL_HANDLE }; - VkDescriptorSet fullscreen{ VK_NULL_HANDLE }; - } 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() - { - 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() - { - if (device) { - 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); - - uniformBuffer.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() - { - // We "magnify" the offscreen rendered triangle so that the conservative rasterization feature is easier to see - const int32_t magnification = 16; - - offscreenPass.width = width / magnification; - offscreenPass.height = height / magnification; - - const VkFormat fbColorFormat = VK_FORMAT_R8G8B8A8_UNORM; - - // 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 = fbColorFormat; - 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 = fbColorFormat; - 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 attchmentDescriptions = {}; - // Color attachment - attchmentDescriptions[0].format = fbColorFormat; - 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 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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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 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(vertexBuffer.size()) * sizeof(Vertex); - std::vector indexBuffer = { 0, 1, 2 }; - triangle.indexCount = static_cast(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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layouts - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo descriptorLayout; - - // Scene rendering - setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), // Binding 0: Vertex shader uniform buffer - }; - descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.scene)); - - // Fullscreen pass - setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0) // Binding 0: Fragment shader image sampler - }; - descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.fullscreen)); - - // Sets - VkDescriptorSetAllocateInfo descriptorSetAllocInfo; - - // Scene rendering - descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.scene)); - std::vector offScreenWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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 writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.fullscreen, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &offscreenPass.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo; - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.fullscreen, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.fullscreen)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - 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(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 vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - }; - std::vector 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(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayouts.fullscreen, renderPass, 0); - 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(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, - &uniformBuffer, - sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffersScene(); - } - - void updateUniformBuffersScene() - { - uniformData.projection = camera.matrices.perspective; - uniformData.model = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffersScene(); - draw(); - } - - 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/examples/debugprintf/debugprintf.cpp b/examples/debugprintf/debugprintf.cpp deleted file mode 100644 index 951337a3..00000000 --- a/examples/debugprintf/debugprintf.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* -* Vulkan Example - Example for using printf in shaders to help debugging. Can be used in conjunction with a debugging app like RenderDoc (https://renderdoc.org) -* -* See this whitepaper for details: https://www.lunarg.com/wp-content/uploads/2021/08/Using-Debug-Printf-02August2021.pdf -* -* Copyright (C) 2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -/* -* The only change required for printf in shaders on the application is enabling the VK_KHR_shader_non_semantic_info extensions -* The actual printing is done in the shaders (see toon.vert from the glsl/hlsl) folder -* For glsl shaders that use this feature, the GL_EXT_debug_printf extension needs to be enabled -*/ - -#include "vulkanexamplebase.h" -#include "VulkanglTFModel.h" - - -class VulkanExample : public VulkanExampleBase -{ -public: - vks::Buffer uniformBuffer; - vkglTF::Model scene; - - struct UBOVS { - glm::mat4 projection; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(0.0f, 5.0f, 15.0f, 1.0f); - } uboVS; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - title = "Debug output with shader printf"; - 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); - - // Using printf requires the non semantic info extension to be enabled - enabledDeviceExtensions.push_back(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME); - -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_EXAMPLE_XCODE_GENERATED) - // SRS - Force validation on since shader printf provided by VK_LAYER_KHRONOS_validation on macOS - settings.validation = true; - - // Use layer settings extension to configure Validation Layer - enabledInstanceExtensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); - - // SRS - Enable the Validation Layer's printf feature - VkLayerSettingEXT layerSetting; - layerSetting.pLayerName = "VK_LAYER_KHRONOS_validation"; - layerSetting.pSettingName = "enables"; - layerSetting.type = VK_LAYER_SETTING_TYPE_STRING_EXT; - layerSetting.valueCount = 1; - - // Make static so layer setting reference remains valid after leaving constructor scope - static const char *layerEnables = "VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT"; - layerSetting.pValues = &layerEnables; - enabledLayerSettings.push_back(layerSetting); - - // SRS - RenderDoc not available on macOS so redirect printf output to stdout - layerSetting.pSettingName = "printf_to_stdout"; - layerSetting.type = VK_LAYER_SETTING_TYPE_BOOL32_EXT; - layerSetting.valueCount = 1; - - // Make static so layer setting reference remains valid after leaving constructor scope - static const VkBool32 layerSettingOn = VK_TRUE; - layerSetting.pValues = &layerSettingOn; - enabledLayerSettings.push_back(layerSetting); - - // Enable required features and set API version for Validation Layer printf - enabledFeatures.fragmentStoresAndAtomics = VK_TRUE; - enabledFeatures.vertexPipelineStoresAndAtomics = VK_TRUE; - - apiVersion = VK_API_VERSION_1_1; -#endif - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - } - } - - void loadAssets() - { - scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY); - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VkClearValue clearValues[2]{}; - - for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) - { - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - - 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); - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Toon shading 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages = { - loadShader(getShadersPath() + "debugprintf/toon.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "debugprintf/toon.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - - 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(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)); - } - - // 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(uboVS))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - 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(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Info")) { - overlay->text("Please run this sample with a graphics debugger attached"); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/debugutils/debugutils.cpp b/examples/debugutils/debugutils.cpp deleted file mode 100644 index ab700453..00000000 --- a/examples/debugutils/debugutils.cpp +++ /dev/null @@ -1,775 +0,0 @@ -/* -* Vulkan Example - Example for the VK_EXT_debug_utils extension. Can be used in conjunction with a debugging app like RenderDoc (https://renderdoc.org) -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool wireframe = true; - bool glow = true; - - struct Models { - vkglTF::Model scene, sceneGlow; - } models; - - struct UBOVS { - glm::mat4 projection; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(0.0f, 5.0f, 15.0f, 1.0f); - } uniformData; - vks::Buffer uniformBuffer; - - struct Pipelines { - VkPipeline toonshading; - VkPipeline color; - VkPipeline wireframe; - VkPipeline postprocess; - } pipelines{}; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - // Framebuffer for offscreen rendering - struct FrameBufferAttachment { - VkImage image; - VkDeviceMemory memory; - VkImageView view; - }; - struct OffscreenPass { - int32_t width, height; - VkFramebuffer frameBuffer; - FrameBufferAttachment color, depth; - VkRenderPass renderPass; - VkSampler sampler; - VkDescriptorImageInfo descriptor; - } offscreenPass{}; - - // Function pointers for the VK_EXT_debug_utils_extension - - bool debugUtilsSupported = false; - - PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT{ nullptr }; - PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT{ nullptr }; - PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{ nullptr }; - PFN_vkCmdInsertDebugUtilsLabelEXT vkCmdInsertDebugUtilsLabelEXT{ nullptr }; - PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{ nullptr }; - PFN_vkQueueBeginDebugUtilsLabelEXT vkQueueBeginDebugUtilsLabelEXT{ nullptr }; - PFN_vkQueueInsertDebugUtilsLabelEXT vkQueueInsertDebugUtilsLabelEXT{ nullptr }; - PFN_vkQueueEndDebugUtilsLabelEXT vkQueueEndDebugUtilsLabelEXT{ nullptr }; - PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{ nullptr }; - - VulkanExample() : VulkanExampleBase() - { - title = "Debugging with VK_EXT_debug_utils"; - 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() - { - if (device) { - 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.memory, nullptr); - - // Depth attachment - vkDestroyImageView(device, offscreenPass.depth.view, nullptr); - vkDestroyImage(device, offscreenPass.depth.image, nullptr); - vkFreeMemory(device, offscreenPass.depth.memory, nullptr); - - vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr); - vkDestroySampler(device, offscreenPass.sampler, nullptr); - vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr); - } - } - - /* - Debug utils functions - */ - - // Checks if debug utils are supported (usually only when a graphics debugger is active) and does the setup necessary to use this debug utils - void setupDebugUtils() - { - // Check if the debug utils extension is present (which is the case if run from a graphics debugger) - bool extensionPresent = false; - uint32_t extensionCount; - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); - std::vector extensions(extensionCount); - vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); - for (auto& extension : extensions) { - if (strcmp(extension.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { - extensionPresent = true; - break; - } - } - - if (extensionPresent) { - // As with an other extension, function pointers need to be manually loaded - vkCreateDebugUtilsMessengerEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - vkDestroyDebugUtilsMessengerEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); - vkCmdBeginDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT")); - vkCmdInsertDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdInsertDebugUtilsLabelEXT")); - vkCmdEndDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT")); - vkQueueBeginDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueBeginDebugUtilsLabelEXT")); - vkQueueInsertDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueInsertDebugUtilsLabelEXT")); - vkQueueEndDebugUtilsLabelEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkQueueEndDebugUtilsLabelEXT")); - vkSetDebugUtilsObjectNameEXT = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkSetDebugUtilsObjectNameEXT")); - - // Set flag if at least one function pointer is present - debugUtilsSupported = (vkCreateDebugUtilsMessengerEXT != VK_NULL_HANDLE); - } - else { - std::cout << "Warning: " << VK_EXT_DEBUG_UTILS_EXTENSION_NAME << " not present, debug utils are disabled."; - std::cout << "Try running the sample from inside a Vulkan graphics debugger (e.g. RenderDoc)" << std::endl; - } - } - - // The debug utils extensions allows us to put labels into command buffers and queues (to e.g. mark regions of interest) and to name Vulkan objects - // We wrap these into functions for convenience - - // Functions for putting labels into a command buffer - // Labels consist of a name and an optional color - // How or if these are diplayed depends on the debugger used (RenderDoc e.g. displays both) - - void cmdBeginLabel(VkCommandBuffer command_buffer, const char* label_name, std::vector color) - { - if (!debugUtilsSupported) { - return; - } - VkDebugUtilsLabelEXT label = { VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; - label.pLabelName = label_name; - memcpy(label.color, color.data(), sizeof(float) * 4); - vkCmdBeginDebugUtilsLabelEXT(command_buffer, &label); - } - - void cmdInsertLabel(VkCommandBuffer command_buffer, const char* label_name, std::vector color) - { - if (!debugUtilsSupported) { - return; - } - VkDebugUtilsLabelEXT label = { VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; - label.pLabelName = label_name; - memcpy(label.color, color.data(), sizeof(float) * 4); - vkCmdInsertDebugUtilsLabelEXT(command_buffer, &label); - } - - void cmdEndLabel(VkCommandBuffer command_buffer) - { - if (!debugUtilsSupported) { - return; - } - vkCmdEndDebugUtilsLabelEXT(command_buffer); - } - - // Functions for putting labels into a queue - // Labels consist of a name and an optional color - // How or if these are diplayed depends on the debugger used (RenderDoc e.g. displays both) - - void queueBeginLabel(VkQueue queue, const char* label_name, std::vector color) - { - if (!debugUtilsSupported) { - return; - } - VkDebugUtilsLabelEXT label = { VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; - label.pLabelName = label_name; - memcpy(label.color, color.data(), sizeof(float) * 4); - vkQueueBeginDebugUtilsLabelEXT(queue, &label); - } - - void queueInsertLabel(VkQueue queue, const char* label_name, std::vector color) - { - if (!debugUtilsSupported) { - return; - } - VkDebugUtilsLabelEXT label = { VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT }; - label.pLabelName = label_name; - memcpy(label.color, color.data(), sizeof(float) * 4); - vkQueueInsertDebugUtilsLabelEXT(queue, &label); - } - - void queueEndLabel(VkQueue queue) - { - if (!debugUtilsSupported) { - return; - } - vkQueueEndDebugUtilsLabelEXT(queue); - } - - // Function for naming Vulkan objects - // In Vulkan, all objects (that can be named) are opaque unsigned 64 bit handles, and can be cased to uint64_t - - void setObjectName(VkDevice device, VkObjectType object_type, uint64_t object_handle, const char* object_name) - { - if (!debugUtilsSupported) { - return; - } - VkDebugUtilsObjectNameInfoEXT name_info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; - name_info.objectType = object_type; - name_info.objectHandle = object_handle; - name_info.pObjectName = object_name; - vkSetDebugUtilsObjectNameEXT(device, &name_info); - } - - // Prepare a texture target and framebuffer for offscreen rendering - void prepareOffscreen() - { - const uint32_t dim = 256; - const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; - - offscreenPass.width = 256; - offscreenPass.height = 256; - - // 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 = colorFormat; - 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.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.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 = 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.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.memory, 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 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_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 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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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 loadAssets() - { - const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; - models.scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags); - models.sceneGlow.loadFromFile(getAssetPath() + "models/treasure_glow.gltf", vulkanDevice, queue, glTFLoadingFlags); - } - - // We use a custom draw function so we can insert debug labels with the names of the glTF nodes - void drawModel(vkglTF::Model &model, VkCommandBuffer cmdBuffer) - { - model.bindBuffers(cmdBuffer); - for (auto i = 0; i < model.nodes.size(); i++) - { - // Insert a label for the current model's name - cmdInsertLabel(cmdBuffer, model.nodes[i]->name.c_str(), { 0.0f, 0.0f, 0.0f, 0.0f }); - model.drawNode(model.nodes[i], cmdBuffer); - } - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VkClearValue clearValues[2]; - - 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; - - cmdBeginLabel(drawCmdBuffers[i], "Off-screen scene rendering", { 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, &descriptorSet, 0, nullptr); - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.color); - - drawModel(models.sceneGlow, drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - cmdEndLabel(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; - - cmdBeginLabel(drawCmdBuffers[i], "Render scene", { 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, &descriptorSet, 0, nullptr); - - // Solid rendering - - cmdBeginLabel(drawCmdBuffers[i], "Toon shading draw", { 0.78f, 0.74f, 0.9f, 1.0f }); - - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.toonshading); - drawModel(models.scene, drawCmdBuffers[i]); - - cmdEndLabel(drawCmdBuffers[i]); - - // Wireframe rendering - if (wireframe) - { - cmdBeginLabel(drawCmdBuffers[i], "Wireframe draw", { 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); - drawModel(models.scene, drawCmdBuffers[i]); - - cmdEndLabel(drawCmdBuffers[i]); - - scissor.offset.x = 0; - scissor.extent.width = width; - vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - } - - // Post processing - if (glow) - { - cmdBeginLabel(drawCmdBuffers[i], "Apply post processing", { 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); - - cmdEndLabel(drawCmdBuffers[i]); - } - - cmdBeginLabel(drawCmdBuffers[i], "UI overlay", { 0.23f, 0.65f, 0.28f, 1.0f }); - drawUI(drawCmdBuffers[i]); - cmdEndLabel(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - cmdEndLabel(drawCmdBuffers[i]); - - } - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - void setupDescriptors() - { - // Pool - std::vector 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, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Color map - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() + "debugutils/toon.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "debugutils/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() + "debugutils/colorpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "debugutils/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() + "debugutils/postprocess.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "debugutils/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)); - } - - // For convencience we name our Vulkan objects in a single place - void nameDebugObjects() - { - // Name some objects for debugging - setObjectName(device, VK_OBJECT_TYPE_IMAGE, (uint64_t)offscreenPass.color.image, "Off-screen color framebuffer"); - setObjectName(device, VK_OBJECT_TYPE_IMAGE, (uint64_t)offscreenPass.depth.image, "Off-screen depth framebuffer"); - setObjectName(device, VK_OBJECT_TYPE_SAMPLER, (uint64_t)offscreenPass.sampler, "Off-screen framebuffer default sampler"); - - setObjectName(device, VK_OBJECT_TYPE_BUFFER, (uint64_t)uniformBuffer.buffer, "Scene uniform buffer block"); - setObjectName(device, VK_OBJECT_TYPE_BUFFER, (uint64_t)models.scene.vertices.buffer, "Scene vertex buffer"); - setObjectName(device, VK_OBJECT_TYPE_BUFFER, (uint64_t)models.scene.indices.buffer, "Scene index buffer"); - setObjectName(device, VK_OBJECT_TYPE_BUFFER, (uint64_t)models.sceneGlow.vertices.buffer, "Glow vertex buffer"); - setObjectName(device, VK_OBJECT_TYPE_BUFFER, (uint64_t)models.sceneGlow.indices.buffer, "Glow index buffer"); - - // Shader module count starts at 2 when UI overlay in base class is enabled - uint32_t moduleIndex = settings.overlay ? 2 : 0; - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 0], "Toon shading vertex shader"); - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 1], "Toon shading fragment shader"); - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 2], "Color-only vertex shader"); - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 3], "Color-only fragment shader"); - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 4], "Postprocess vertex shader"); - setObjectName(device, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)shaderModules[moduleIndex + 5], "Postprocess fragment shader"); - - setObjectName(device, VK_OBJECT_TYPE_PIPELINE_LAYOUT, (uint64_t)pipelineLayout, "Shared pipeline layout"); - setObjectName(device, VK_OBJECT_TYPE_PIPELINE, (uint64_t)pipelines.toonshading, "Toon shading pipeline"); - setObjectName(device, VK_OBJECT_TYPE_PIPELINE, (uint64_t)pipelines.color, "Color only pipeline"); - if (deviceFeatures.fillModeNonSolid) { - setObjectName(device, VK_OBJECT_TYPE_PIPELINE, (uint64_t)pipelines.wireframe, "Wireframe rendering pipeline"); - } - setObjectName(device, VK_OBJECT_TYPE_PIPELINE, (uint64_t)pipelines.postprocess, "Post processing pipeline"); - - setObjectName(device, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, (uint64_t)descriptorSetLayout, "Shared descriptor set layout"); - setObjectName(device, VK_OBJECT_TYPE_DESCRIPTOR_SET, (uint64_t)descriptorSet, "Shared descriptor set"); - } - - // 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(uniformData))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.model = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void draw() - { - queueBeginLabel(queue, "Graphics queue command buffer submission", { 1.0f, 1.0f, 1.0f, 1.0f }); - VulkanExampleBase::prepareFrame(); - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - VulkanExampleBase::submitFrame(); - queueEndLabel(queue); - } - - void prepare() - { - VulkanExampleBase::prepare(); - setupDebugUtils(); - loadAssets(); - prepareOffscreen(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - nameDebugObjects(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Info")) { - overlay->text("VK_EXT_debug_utils %s", (debugUtilsSupported? "supported" : "not supported")); - } - if (overlay->header("Settings")) { - if (overlay->checkBox("Glow", &glow)) { - buildCommandBuffers(); - } - if (deviceFeatures.fillModeNonSolid) { - if (overlay->checkBox("Wireframe", &wireframe)) { - buildCommandBuffers(); - } - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/deferred/deferred.cpp b/examples/deferred/deferred.cpp deleted file mode 100644 index 379d56f4..00000000 --- a/examples/deferred/deferred.cpp +++ /dev/null @@ -1,804 +0,0 @@ -/* -* Vulkan Example - Deferred shading with multiple render targets (aka G-Buffer) example -* -* This samples shows how to do deferred rendering. Unlike forward rendering, different components like -* albedo, normals, world positions are rendered to offscreen images which are then put together and lit -* in a composition pass -* Use the dropdown in the ui to switch between the final composition pass or the separate components -* -* -* Copyright (C) 2016-2023 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" - -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 UniformDataOffscreen { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - glm::vec4 instancePos[3]; - } uniformDataOffscreen; - - struct Light { - glm::vec4 position; - glm::vec3 color; - float radius; - }; - - struct UniformDataComposition { - Light lights[6]; - glm::vec4 viewPos; - int debugDisplayTarget = 0; - } uniformDataComposition; - - struct { - vks::Buffer offscreen; - vks::Buffer composition; - } uniformBuffers; - - struct { - VkPipeline offscreen{ VK_NULL_HANDLE }; - VkPipeline composition{ VK_NULL_HANDLE }; - } pipelines; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - - struct { - VkDescriptorSet model{ VK_NULL_HANDLE }; - VkDescriptorSet floor{ VK_NULL_HANDLE }; - VkDescriptorSet composition{ VK_NULL_HANDLE }; - } descriptorSets; - - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // Framebuffers holding the deferred attachments - struct FrameBufferAttachment { - VkImage image; - VkDeviceMemory mem; - VkImageView view; - VkFormat format; - }; - struct FrameBuffer { - int32_t width, height; - VkFramebuffer frameBuffer; - // One attachment for every component required for a deferred rendering setup - FrameBufferAttachment position, normal, albedo; - FrameBufferAttachment depth; - VkRenderPass renderPass; - } offScreenFrameBuf{}; - - // One sampler for the frame buffer color attachments - VkSampler colorSampler{ VK_NULL_HANDLE }; - - VkCommandBuffer offScreenCmdBuffer{ VK_NULL_HANDLE }; - - // Semaphore used to synchronize between offscreen and final scene rendering - VkSemaphore offscreenSemaphore{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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; - if (format >= VK_FORMAT_D16_UNORM_S8_UINT) - aspectMask |=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() - { - // Note: Instead of using fixed sizes, one could also match the window size and recreate the attachments on resize - offScreenFrameBuf.width = 2048; - offScreenFrameBuf.height = 2048; - - // 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 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 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(colorReferences.size()); - subpass.pDepthStencilAttachment = &depthReference; - - // Use subpass dependencies for attachment layout transitions - std::array 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(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 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(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 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(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); - - // Floor - vkCmdBindDescriptorSets(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.floor, 0, nullptr); - models.floor.draw(offScreenCmdBuffer); - - // We render multiple instances of a model - 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, &descriptorSets.composition, 0, nullptr); - - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition); - - // Final composition - // This is done by simply drawing a full screen quad - // The fragment shader then combines the deferred attachments into the final image - // 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layouts - std::vector 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)); - - // Sets - std::vector 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, &descriptorSets.composition)); - writeDescriptorSets = { - // Binding 1 : Position texture target - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition), - // Binding 2 : Normals texture target - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal), - // Binding 3 : Albedo texture target - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo), - // Binding 4 : Fragment shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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(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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 blendAttachmentStates = { - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE) - }; - - colorBlendState.attachmentCount = static_cast(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(UniformDataOffscreen))); - - // 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(UniformDataComposition))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffers.offscreen.map()); - VK_CHECK_RESULT(uniformBuffers.composition.map()); - - // Setup instanced model positions - uniformDataOffscreen.instancePos[0] = glm::vec4(0.0f); - uniformDataOffscreen.instancePos[1] = glm::vec4(-4.0f, 0.0, -4.0f, 0.0f); - uniformDataOffscreen.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() - { - uniformDataOffscreen.projection = camera.matrices.perspective; - uniformDataOffscreen.view = camera.matrices.view; - uniformDataOffscreen.model = glm::mat4(1.0f); - memcpy(uniformBuffers.offscreen.mapped, &uniformDataOffscreen, sizeof(UniformDataOffscreen)); - } - - // Update lights and parameters passed to the composition shaders - void updateUniformBufferComposition() - { - // White - uniformDataComposition.lights[0].position = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f); - uniformDataComposition.lights[0].color = glm::vec3(1.5f); - uniformDataComposition.lights[0].radius = 15.0f * 0.25f; - // Red - uniformDataComposition.lights[1].position = glm::vec4(-2.0f, 0.0f, 0.0f, 0.0f); - uniformDataComposition.lights[1].color = glm::vec3(1.0f, 0.0f, 0.0f); - uniformDataComposition.lights[1].radius = 15.0f; - // Blue - uniformDataComposition.lights[2].position = glm::vec4(2.0f, -1.0f, 0.0f, 0.0f); - uniformDataComposition.lights[2].color = glm::vec3(0.0f, 0.0f, 2.5f); - uniformDataComposition.lights[2].radius = 5.0f; - // Yellow - uniformDataComposition.lights[3].position = glm::vec4(0.0f, -0.9f, 0.5f, 0.0f); - uniformDataComposition.lights[3].color = glm::vec3(1.0f, 1.0f, 0.0f); - uniformDataComposition.lights[3].radius = 2.0f; - // Green - uniformDataComposition.lights[4].position = glm::vec4(0.0f, -0.5f, 0.0f, 0.0f); - uniformDataComposition.lights[4].color = glm::vec3(0.0f, 1.0f, 0.2f); - uniformDataComposition.lights[4].radius = 5.0f; - // Yellow - uniformDataComposition.lights[5].position = glm::vec4(0.0f, -1.0f, 0.0f, 0.0f); - uniformDataComposition.lights[5].color = glm::vec3(1.0f, 0.7f, 0.3f); - uniformDataComposition.lights[5].radius = 25.0f; - - // Animate the lights - if (!paused) { - uniformDataComposition.lights[0].position.x = sin(glm::radians(360.0f * timer)) * 5.0f; - uniformDataComposition.lights[0].position.z = cos(glm::radians(360.0f * timer)) * 5.0f; - - uniformDataComposition.lights[1].position.x = -4.0f + sin(glm::radians(360.0f * timer) + 45.0f) * 2.0f; - uniformDataComposition.lights[1].position.z = 0.0f + cos(glm::radians(360.0f * timer) + 45.0f) * 2.0f; - - uniformDataComposition.lights[2].position.x = 4.0f + sin(glm::radians(360.0f * timer)) * 2.0f; - uniformDataComposition.lights[2].position.z = 0.0f + cos(glm::radians(360.0f * timer)) * 2.0f; - - uniformDataComposition.lights[4].position.x = 0.0f + sin(glm::radians(360.0f * timer + 90.0f)) * 5.0f; - uniformDataComposition.lights[4].position.z = 0.0f - cos(glm::radians(360.0f * timer + 45.0f)) * 5.0f; - - uniformDataComposition.lights[5].position.x = 0.0f + sin(glm::radians(-360.0f * timer + 135.0f)) * 10.0f; - uniformDataComposition.lights[5].position.z = 0.0f - cos(glm::radians(-360.0f * timer - 45.0f)) * 10.0f; - } - - // Current view position - uniformDataComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f); - - uniformDataComposition.debugDisplayTarget = debugDisplayTarget; - - memcpy(uniformBuffers.composition.mapped, &uniformDataComposition, sizeof(UniformDataComposition)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareOffscreenFramebuffer(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - buildDeferredCommandBuffer(); - prepared = true; - } - - 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 rendering - // 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(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBufferComposition(); - updateUniformBufferOffscreen(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->comboBox("Display", &debugDisplayTarget, { "Final composition", "Position", "Normals", "Albedo", "Specular" }); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/deferredmultisampling/deferredmultisampling.cpp b/examples/deferredmultisampling/deferredmultisampling.cpp deleted file mode 100644 index 02dbe78e..00000000 --- a/examples/deferredmultisampling/deferredmultisampling.cpp +++ /dev/null @@ -1,638 +0,0 @@ -/* -* Vulkan Example - Multi sampling with explicit resolve for deferred shading example -* -* This sample adds hardware accelerated multi sampling to the deferred rendering sample -* -* Copyright (C) 2023 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" - -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 UniformDataOffscreen { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - glm::vec4 instancePos[3]; - } uniformDataOffscreen; - - struct Light { - glm::vec4 position; - glm::vec3 color; - float radius; - }; - - struct UniformDataComposition { - Light lights[6]; - glm::vec4 viewPos; - int32_t debugDisplayTarget = 0; - } uniformDataComposition; - - struct { - vks::Buffer offscreen; - vks::Buffer composition; - } uniformBuffers; - - struct { - VkPipeline deferred{ VK_NULL_HANDLE }; // Deferred lighting calculation - VkPipeline deferredNoMSAA{ VK_NULL_HANDLE }; // Deferred lighting calculation with explicit MSAA resolve - VkPipeline offscreen{ VK_NULL_HANDLE }; // (Offscreen) scene rendering (fill G-Buffers) - VkPipeline offscreenSampleShading{ VK_NULL_HANDLE }; // (Offscreen) scene rendering (fill G-Buffers) with sample shading rate enabled - } pipelines; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - - struct { - VkDescriptorSet model{ VK_NULL_HANDLE }; - VkDescriptorSet background{ VK_NULL_HANDLE }; - VkDescriptorSet composition{ VK_NULL_HANDLE }; - } descriptorSets; - - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - 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() - { - 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); - } - - ~VulkanExample() - { - if (device) { - // 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); - -#if defined(__ANDROID__) - // Use max. screen dimension as deferred framebuffer size - offscreenframeBuffers->width = std::max(width, height); - offscreenframeBuffers->height = std::max(width, height); -#else - offscreenframeBuffers->width = 2048; - offscreenframeBuffers->height = 2048; -#endif - - // Four attachments (3 color, 1 depth) - vks::AttachmentCreateInfo attachmentInfo = {}; - attachmentInfo.width = offscreenframeBuffers->width; - attachmentInfo.height = offscreenframeBuffers->height; - 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 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(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); - - // 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, &descriptorSets.composition, 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, 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - // Deferred shading layout - std::vector 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)); - - // Sets - std::vector 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, &descriptorSets.composition)); - writeDescriptorSets = { - // Binding 1: World space position texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition), - // Binding 2: World space normals texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal), - // Binding 3: Albedo texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo), - // Binding 4: Fragment shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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(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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 blendAttachmentStates = { - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE) - }; - - colorBlendState.attachmentCount = static_cast(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(UniformDataOffscreen))); - // 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(UniformDataComposition)));; - // Map persistent - VK_CHECK_RESULT(uniformBuffers.offscreen.map()); - VK_CHECK_RESULT(uniformBuffers.composition.map()); - - // Setup instanced model positions - uniformDataOffscreen.instancePos[0] = glm::vec4(0.0f); - uniformDataOffscreen.instancePos[1] = glm::vec4(-4.0f, 0.0, -4.0f, 0.0f); - uniformDataOffscreen.instancePos[2] = glm::vec4(4.0f, 0.0, -4.0f, 0.0f); - - // Update - updateUniformBufferDeferred(); - } - - void updateUniformBufferOffscreen() - { - uniformDataOffscreen.projection = camera.matrices.perspective; - uniformDataOffscreen.view = camera.matrices.view; - uniformDataOffscreen.model = glm::mat4(1.0f); - memcpy(uniformBuffers.offscreen.mapped, &uniformDataOffscreen, sizeof(UniformDataOffscreen)); - } - - // Update deferred composition fragment shader light position and parameters uniform block - void updateUniformBufferDeferred() - { - // White - uniformDataComposition.lights[0].position = glm::vec4(0.0f, 0.0f, 5.0f, 0.0f); - uniformDataComposition.lights[0].color = glm::vec3(1.5f); - uniformDataComposition.lights[0].radius = 15.0f * 0.25f; - // Red - uniformDataComposition.lights[1].position = glm::vec4(-2.30f, 0.0f, 1.05f, 0.0f); - uniformDataComposition.lights[1].color = glm::vec3(1.0f, 0.0f, 0.0f); - uniformDataComposition.lights[1].radius = 15.0f; - // Blue - uniformDataComposition.lights[2].position = glm::vec4(4.0f, -1.0f, 2.0f, 0.0f); - uniformDataComposition.lights[2].color = glm::vec3(0.0f, 0.0f, 2.5f); - uniformDataComposition.lights[2].radius = 5.0f; - // Yellow - uniformDataComposition.lights[3].position = glm::vec4(0.0f, -0.9f, 0.5f, 0.0f); - uniformDataComposition.lights[3].color = glm::vec3(1.0f, 1.0f, 0.0f); - uniformDataComposition.lights[3].radius = 2.0f; - // Green - uniformDataComposition.lights[4].position = glm::vec4(5.0f, -0.5f, -3.53f, 0.0f); - uniformDataComposition.lights[4].color = glm::vec3(0.0f, 1.0f, 0.2f); - uniformDataComposition.lights[4].radius = 5.0f; - // Yellow - uniformDataComposition.lights[5].position = glm::vec4(7.07f, -1.0f, 7.07f, 0.0f); - uniformDataComposition.lights[5].color = glm::vec3(1.0f, 0.7f, 0.3f); - uniformDataComposition.lights[5].radius = 25.0f; - - // Current view position - uniformDataComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f); - uniformDataComposition.debugDisplayTarget = debugDisplayTarget; - - memcpy(uniformBuffers.composition.mapped, &uniformDataComposition, sizeof(UniformDataComposition)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - sampleCount = getMaxUsableSampleCount(); - loadAssets(); - deferredSetup(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - buildDeferredCommandBuffer(); - prepared = true; - } - - 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(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBufferOffscreen(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->comboBox("Display", &debugDisplayTarget, { "Final composition", "Position", "Normals", "Albedo", "Specular" })) { - updateUniformBufferDeferred(); - } - 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); - // Note: Vulkan offers up to 64 bits, but we don't want to go higher than 8xMSAA in this sample) - 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/examples/deferredshadows/deferredshadows.cpp b/examples/deferredshadows/deferredshadows.cpp deleted file mode 100644 index 3cc9fc6b..00000000 --- a/examples/deferredshadows/deferredshadows.cpp +++ /dev/null @@ -1,778 +0,0 @@ -/* -* Vulkan Example - Deferred shading with shadows from multiple light sources using geometry shader instancing -* -* This sample adds dynamic shadows (using shadow maps) to a deferred rendering setup -* -* Copyright (C) 2016-2023 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" - -// 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 UniformDataOffscreen { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - glm::vec4 instancePos[3]; - int layer{ 0 }; - } uniformDataOffscreen; - - // 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 UniformDataShadows { - glm::mat4 mvp[LIGHT_COUNT]; - glm::vec4 instancePos[3]; - } uniformDataShadows; - - struct Light { - glm::vec4 position; - glm::vec4 target; - glm::vec4 color; - glm::mat4 viewMatrix; - }; - - struct UniformDataComposition { - glm::vec4 viewPos; - Light lights[LIGHT_COUNT]; - uint32_t useShadows = 1; - int32_t debugDisplayTarget = 0; - } uniformDataComposition; - - struct { - vks::Buffer offscreen; - vks::Buffer composition; - vks::Buffer shadowGeometryShader; - } uniformBuffers; - - struct { - VkPipeline deferred{ VK_NULL_HANDLE }; - VkPipeline offscreen{ VK_NULL_HANDLE }; - VkPipeline shadowpass{ VK_NULL_HANDLE }; - } pipelines; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - - struct { - VkDescriptorSet model{ VK_NULL_HANDLE }; - VkDescriptorSet background{ VK_NULL_HANDLE }; - VkDescriptorSet shadow{ VK_NULL_HANDLE }; - VkDescriptorSet composition{ VK_NULL_HANDLE }; - } descriptorSets; - - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - struct { - // Framebuffer resources for the deferred pass - vks::Framebuffer *deferred; - // Framebuffer resources for the shadow pass - vks::Framebuffer *shadow; - } frameBuffers{}; - - VkCommandBuffer offScreenCmdBuffer{ VK_NULL_HANDLE }; - - // Semaphore used to synchronize between offscreen and final scene rendering - VkSemaphore offscreenSemaphore{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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; - } - - ~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); - - // Shadowmap properties -#if defined(__ANDROID__) - // Use smaller shadow maps on mobile due to performance reasons - frameBuffers.shadow->width = 1024; - frameBuffers.shadow->height = 1024; -#else - frameBuffers.shadow->width = 2048; - frameBuffers.shadow->height = 2048; -#endif - - // Find a suitable depth format - VkFormat shadowMapFormat; - VkBool32 validShadowMapFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &shadowMapFormat); - assert(validShadowMapFormat); - - // 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 = shadowMapFormat; - attachmentInfo.width = frameBuffers.shadow->width; - attachmentInfo.height = frameBuffers.shadow->height; - 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); - -#if defined(__ANDROID__) - // Use max. screen dimension as deferred framebuffer size - frameBuffers.deferred->width = std::max(width, height); - frameBuffers.deferred->height = std::max(width, height); -#else - frameBuffers.deferred->width = 2048; - frameBuffers.deferred->height = 2048; -#endif - - // Four attachments (3 color, 1 depth) - vks::AttachmentCreateInfo attachmentInfo = {}; - attachmentInfo.width = frameBuffers.deferred->width; - attachmentInfo.height = frameBuffers.deferred->height; - 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) - { - // 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 (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(); - - VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); - std::array 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(offScreenCmdBuffer, &cmdBufInfo)); - - viewport = vks::initializers::viewport((float)frameBuffers.shadow->width, (float)frameBuffers.shadow->height, 0.0f, 1.0f); - vkCmdSetViewport(offScreenCmdBuffer, 0, 1, &viewport); - - scissor = vks::initializers::rect2D(frameBuffers.shadow->width, frameBuffers.shadow->height, 0, 0); - vkCmdSetScissor(offScreenCmdBuffer, 0, 1, &scissor); - - // Set depth bias (aka "Polygon offset") - vkCmdSetDepthBias( - offScreenCmdBuffer, - depthBiasConstant, - 0.0f, - depthBiasSlope); - - vkCmdBeginRenderPass(offScreenCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shadowpass); - renderScene(offScreenCmdBuffer, true); - vkCmdEndRenderPass(offScreenCmdBuffer); - - // 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(clearValues.size()); - renderPassBeginInfo.pClearValues = clearValues.data(); - - vkCmdBeginRenderPass(offScreenCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - viewport = vks::initializers::viewport((float)frameBuffers.deferred->width, (float)frameBuffers.deferred->height, 0.0f, 1.0f); - vkCmdSetViewport(offScreenCmdBuffer, 0, 1, &viewport); - - scissor = vks::initializers::rect2D(frameBuffers.deferred->width, frameBuffers.deferred->height, 0, 0); - vkCmdSetScissor(offScreenCmdBuffer, 0, 1, &scissor); - - vkCmdBindPipeline(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen); - renderScene(offScreenCmdBuffer, false); - 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.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); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.composition, 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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 12), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 16) - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo =vks::initializers::descriptorPoolCreateInfo(poolSizes, 4); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Sets - std::vector 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, &descriptorSets.composition)); - writeDescriptorSets = { - // Binding 1: World space position texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition), - // Binding 2: World space normals texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal), - // Binding 3: Albedo texture - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo), - // Binding 4: Fragment shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor), - // Binding 5: Shadow map - vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5, &texDescriptorShadowMap), - }; - vkUpdateDescriptorSets(device, static_cast(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(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(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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 blendAttachmentStates = - { - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE) - }; - colorBlendState.attachmentCount = static_cast(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 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(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(UniformDataOffscreen))); - - // 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(UniformDataComposition))); - - // 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(UniformDataShadows))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffers.offscreen.map()); - VK_CHECK_RESULT(uniformBuffers.composition.map()); - VK_CHECK_RESULT(uniformBuffers.shadowGeometryShader.map()); - - // Setup instanced model positions - uniformDataOffscreen.instancePos[0] = glm::vec4(0.0f); - uniformDataOffscreen.instancePos[1] = glm::vec4(-7.0f, 0.0, -4.0f, 0.0f); - uniformDataOffscreen.instancePos[2] = glm::vec4(4.0f, 0.0, -6.0f, 0.0f); - } - - void updateUniformBufferOffscreen() - { - uniformDataOffscreen.projection = camera.matrices.perspective; - uniformDataOffscreen.view = camera.matrices.view; - uniformDataOffscreen.model = glm::mat4(1.0f); - memcpy(uniformBuffers.offscreen.mapped, &uniformDataOffscreen, sizeof(uniformDataOffscreen)); - } - - 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() - { - uniformDataComposition.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)); - uniformDataComposition.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)); - uniformDataComposition.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 deferred composition fragment shader light position and parameters uniform block - void updateUniformBufferDeferred() - { - // Animate - uniformDataComposition.lights[0].position.x = -14.0f + std::abs(sin(glm::radians(timer * 360.0f)) * 20.0f); - uniformDataComposition.lights[0].position.z = 15.0f + cos(glm::radians(timer *360.0f)) * 1.0f; - - uniformDataComposition.lights[1].position.x = 14.0f - std::abs(sin(glm::radians(timer * 360.0f)) * 2.5f); - uniformDataComposition.lights[1].position.z = 13.0f + cos(glm::radians(timer *360.0f)) * 4.0f; - - uniformDataComposition.lights[2].position.x = 0.0f + sin(glm::radians(timer *360.0f)) * 4.0f; - uniformDataComposition.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(uniformDataComposition.lights[i].position), glm::vec3(uniformDataComposition.lights[i].target), glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 shadowModel = glm::mat4(1.0f); - - uniformDataShadows.mvp[i] = shadowProj * shadowView * shadowModel; - uniformDataComposition.lights[i].viewMatrix = uniformDataShadows.mvp[i]; - } - - memcpy(uniformDataShadows.instancePos, uniformDataOffscreen.instancePos, sizeof(UniformDataOffscreen::instancePos)); - memcpy(uniformBuffers.shadowGeometryShader.mapped, &uniformDataShadows, sizeof(UniformDataShadows)); - - uniformDataComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);; - uniformDataComposition.debugDisplayTarget = debugDisplayTarget; - - memcpy(uniformBuffers.composition.mapped, &uniformDataComposition, sizeof(uniformDataComposition)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - deferredSetup(); - shadowSetup(); - initLights(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - buildDeferredCommandBuffer(); - prepared = true; - } - - 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 = &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(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBufferDeferred(); - updateUniformBufferOffscreen(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->comboBox("Display", &debugDisplayTarget, { "Final composition", "Shadows", "Position", "Normals", "Albedo", "Specular" }); - bool shadows = (uniformDataComposition.useShadows == 1); - if (overlay->checkBox("Shadows", &shadows)) { - uniformDataComposition.useShadows = shadows; - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/descriptorbuffer/descriptorbuffer.cpp b/examples/descriptorbuffer/descriptorbuffer.cpp deleted file mode 100644 index 951e8027..00000000 --- a/examples/descriptorbuffer/descriptorbuffer.cpp +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Vulkan Example - Using descriptor buffers via VK_EXT_descriptor_buffer - * - * Copyright (C) 2022-2024 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" - - -class VulkanExample : public VulkanExampleBase -{ -public: - bool animate = true; - - struct Cube { - glm::mat4 matrix; - vks::Texture2D texture; - vks::Buffer uniformBuffer; - glm::vec3 rotation; - }; - std::array cubes; - - vks::Buffer uniformBufferCamera; - - vkglTF::Model model; - - VkPipeline pipeline; - VkPipelineLayout pipelineLayout; - - PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR; - - VkPhysicalDeviceDescriptorBufferFeaturesEXT enabledDeviceDescriptorBufferFeaturesEXT{}; - VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{}; - VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptorBufferProperties{}; - - PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT; - PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT; - PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT; - PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT; - PFN_vkGetDescriptorEXT vkGetDescriptorEXT; - PFN_vkCmdBindDescriptorBufferEmbeddedSamplersEXT vkCmdBindDescriptorBufferEmbeddedSamplersEXT; - - // Stores all values that are required to setup a descriptor buffer for a resource buffer - struct DescriptorInfo { - VkDeviceSize layoutOffset; - VkDeviceSize layoutSize; - VkDescriptorSetLayout setLayout; - VkDeviceOrHostAddressConstKHR bufferDeviceAddress; - vks::Buffer buffer; - }; - DescriptorInfo uniformDescriptor{}; - DescriptorInfo combinedImageDescriptor{}; - - uint64_t getBufferDeviceAddress(VkBuffer buffer) - { - VkBufferDeviceAddressInfoKHR bufferDeviceAI{}; - bufferDeviceAI.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; - bufferDeviceAI.buffer = buffer; - return vkGetBufferDeviceAddressKHR(vulkanDevice->logicalDevice, &bufferDeviceAI); - } - - VulkanExample() : VulkanExampleBase() - { - title = "Descriptor buffers (VK_EXT_descriptor_buffer)"; - 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)); - - apiVersion = VK_API_VERSION_1_1; - - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - - enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME); - - enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); - - enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; - enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE; - - enabledDeviceDescriptorBufferFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT; - enabledDeviceDescriptorBufferFeaturesEXT.descriptorBuffer = VK_TRUE; - enabledDeviceDescriptorBufferFeaturesEXT.pNext = &enabledBufferDeviceAddresFeatures; - - deviceCreatepNextChain = &enabledDeviceDescriptorBufferFeaturesEXT; - } - - ~VulkanExample() - { - vkDestroyDescriptorSetLayout(device, uniformDescriptor.setLayout, nullptr); - vkDestroyDescriptorSetLayout(device, combinedImageDescriptor.setLayout, nullptr); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - for (auto& cube : cubes) { - cube.uniformBuffer.destroy(); - cube.texture.destroy(); - } - uniformBufferCamera.destroy(); - uniformDescriptor.buffer.destroy(); - combinedImageDescriptor.buffer.destroy(); - } - - virtual void getEnabledFeatures() - { - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - }; - } - - void setupDescriptors() - { - VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; - descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorLayoutCI.bindingCount = 1; - descriptorLayoutCI.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; - - VkDescriptorSetLayoutBinding setLayoutBinding = {}; - - setLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - setLayoutBinding.binding = 0; - setLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - setLayoutBinding.descriptorCount = 1; - - descriptorLayoutCI.pBindings = &setLayoutBinding; - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &uniformDescriptor.setLayout)); - - setLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - setLayoutBinding.binding = 0; - setLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - setLayoutBinding.descriptorCount = 1; - - descriptorLayoutCI.pBindings = &setLayoutBinding; - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &combinedImageDescriptor.setLayout)); - } - - void preparePipelines() - { - // Set 0 = Camera UBO - // Set 1 = Model UBO - // Set 2 = Model image - const std::array setLayouts = { uniformDescriptor.setLayout, uniformDescriptor.setLayout, combinedImageDescriptor.setLayout }; - - 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 = static_cast(setLayouts.size()); - pipelineLayoutCI.pSetLayouts = setLayouts.data(); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - const std::vector 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_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); - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); - std::array shaderStages = { - loadShader(getShadersPath() + "descriptorbuffer/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "descriptorbuffer/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - - 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color }); - pipelineCI.flags = VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); - } - - void prepareDescriptorBuffer() - { - // We need to get sizes and offsets for the descriptor layouts - - // This is done using a new extension structures and features - PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR")); - assert(vkGetPhysicalDeviceProperties2KHR); - VkPhysicalDeviceProperties2KHR deviceProps2{}; - descriptorBufferProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT; - deviceProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; - deviceProps2.pNext = &descriptorBufferProperties; - vkGetPhysicalDeviceProperties2KHR(physicalDevice, &deviceProps2); - - // Some devices have very low limits for the no. of max descriptor buffer bindings, so we need to check - if (descriptorBufferProperties.maxResourceDescriptorBufferBindings < 2) { - vks::tools::exitFatal("This sample requires at least 2 descriptor bindings to run, the selected device only supports " + std::to_string(descriptorBufferProperties.maxResourceDescriptorBufferBindings), - 1); - } - - vkGetDescriptorSetLayoutSizeEXT(device, uniformDescriptor.setLayout, &uniformDescriptor.layoutSize); - vkGetDescriptorSetLayoutSizeEXT(device, combinedImageDescriptor.setLayout, &combinedImageDescriptor.layoutSize); - - vkGetDescriptorSetLayoutBindingOffsetEXT(device, uniformDescriptor.setLayout, 0, &uniformDescriptor.layoutOffset); - vkGetDescriptorSetLayoutBindingOffsetEXT(device, combinedImageDescriptor.setLayout, 0, &combinedImageDescriptor.layoutOffset); - - // In order to copy resource descriptors to the correct place, we need to calculate aligned sizes - uniformDescriptor.layoutSize = vks::tools::alignedVkSize(uniformDescriptor.layoutSize, descriptorBufferProperties.descriptorBufferOffsetAlignment); - combinedImageDescriptor.layoutSize = vks::tools::alignedVkSize(combinedImageDescriptor.layoutSize, descriptorBufferProperties.descriptorBufferOffsetAlignment); - - // This buffer will contain resource descriptors for all the uniform buffers (one per cube and one with global matrices) - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformDescriptor.buffer, - (static_cast(cubes.size()) + 1) * uniformDescriptor.layoutSize)); - uniformDescriptor.buffer.map(); - - // This buffer contains resource descriptors for the combined images (one per cube) - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, // Flags 1 & 2 are required for combined images - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &combinedImageDescriptor.buffer, - static_cast(cubes.size()) * combinedImageDescriptor.layoutSize)); - combinedImageDescriptor.buffer.map(); - - uniformDescriptor.bufferDeviceAddress.deviceAddress = getBufferDeviceAddress(uniformDescriptor.buffer.buffer); - combinedImageDescriptor.bufferDeviceAddress.deviceAddress = getBufferDeviceAddress(combinedImageDescriptor.buffer.buffer); - - VkDescriptorGetInfoEXT descriptorInfo{}; - descriptorInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT; - - // Put image descriptors into the corresponding resource buffer - char* imageDescriptorBufPtr = (char*)combinedImageDescriptor.buffer.mapped; - descriptorInfo.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - for (uint32_t i = 0; i < static_cast(cubes.size()); i++) { - descriptorInfo.data.pCombinedImageSampler = &cubes[i].texture.descriptor; - vkGetDescriptorEXT(device, &descriptorInfo, descriptorBufferProperties.combinedImageSamplerDescriptorSize, imageDescriptorBufPtr + i * combinedImageDescriptor.layoutSize + combinedImageDescriptor.layoutOffset); - } - - // For uniform buffers we only need buffer device addresses - // Global uniform buffer - char* uniformDescriptorBufPtr = (char*)uniformDescriptor.buffer.mapped; - - VkDescriptorAddressInfoEXT descriptorAddressInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT }; - descriptorAddressInfo.address = getBufferDeviceAddress(uniformBufferCamera.buffer); - descriptorAddressInfo.range = uniformBufferCamera.size; - descriptorAddressInfo.format = VK_FORMAT_UNDEFINED; - - descriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorInfo.data.pCombinedImageSampler = nullptr; - descriptorInfo.data.pUniformBuffer = &descriptorAddressInfo; - vkGetDescriptorEXT(device, &descriptorInfo, descriptorBufferProperties.uniformBufferDescriptorSize, uniformDescriptorBufPtr); - - // Per-model uniform buffers - for (uint32_t i = 0; i < static_cast(cubes.size()); i++) { - VkDescriptorAddressInfoEXT addr_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT }; - addr_info.address = getBufferDeviceAddress(cubes[i].uniformBuffer.buffer); - addr_info.range = cubes[i].uniformBuffer.size; - addr_info.format = VK_FORMAT_UNDEFINED; - - descriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorInfo.data.pCombinedImageSampler = nullptr; - descriptorInfo.data.pUniformBuffer = &addr_info; - vkGetDescriptorEXT(device, &descriptorInfo, descriptorBufferProperties.uniformBufferDescriptorSize, uniformDescriptorBufPtr + (i + 1) * uniformDescriptor.layoutSize + uniformDescriptor.layoutOffset); - } - } - - 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]); - - // Descriptor buffer bindings - // Set 0 = uniform buffer - VkDescriptorBufferBindingInfoEXT bindingInfos[2]{}; - bindingInfos[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT; - bindingInfos[0].address = uniformDescriptor.bufferDeviceAddress.deviceAddress; - bindingInfos[0].usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT;// | VK_BUFFER_USAGE_PUSH_DESCRIPTORS_DESCRIPTOR_BUFFER_BIT_EXT; - // Set 1 = Image - bindingInfos[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT; - bindingInfos[1].pNext = nullptr; - bindingInfos[1].address = combinedImageDescriptor.bufferDeviceAddress.deviceAddress; - bindingInfos[1].usage = VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT; - vkCmdBindDescriptorBuffersEXT(drawCmdBuffers[i], 2, bindingInfos); - - uint32_t bufferIndexUbo = 0; - VkDeviceSize bufferOffset = 0; - - // Global Matrices (set 0) - bufferOffset = 0; - vkCmdSetDescriptorBufferOffsetsEXT(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &bufferIndexUbo, &bufferOffset); - - // Set and offset into descriptor for each model - for (uint32_t j = 0; j < static_cast(cubes.size()); j++) { - // Uniform buffer (set 1) - // Model ubos start at offset * 1 (slot 0 is global matrices) - bufferOffset = (j + 1) * uniformDescriptor.layoutSize; - vkCmdSetDescriptorBufferOffsetsEXT(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &bufferIndexUbo, &bufferOffset); - // Image (set 2) - uint32_t bufferIndexImage = 1; - bufferOffset = j * combinedImageDescriptor.layoutSize; - vkCmdSetDescriptorBufferOffsetsEXT(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 2, 1, &bufferIndexImage, &bufferOffset); - 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 prepareUniformBuffers() - { - // UBO for camera matrices - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBufferCamera, - sizeof(glm::mat4) * 2)); - VK_CHECK_RESULT(uniformBufferCamera.map()); - - // UBOs for model matrices - for (uint32_t i = 0; i < static_cast(cubes.size()); i++) { - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &cubes[i].uniformBuffer, - sizeof(glm::mat4))); - VK_CHECK_RESULT(cubes[i].uniformBuffer.map()); - } - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - memcpy(uniformBufferCamera.mapped, &camera.matrices.perspective, sizeof(glm::mat4)); - memcpy((char*)uniformBufferCamera.mapped + sizeof(glm::mat4), &camera.matrices.view, sizeof(glm::mat4)); - - cubes[0].matrix = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f)); - cubes[1].matrix = glm::translate(glm::mat4(1.0f), glm::vec3( 1.5f, 0.5f, 0.0f)); - - for (auto& cube : cubes) { - cube.matrix = glm::rotate(cube.matrix, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); - cube.matrix = glm::rotate(cube.matrix, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - cube.matrix = glm::rotate(cube.matrix, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); - cube.matrix = glm::scale(cube.matrix, glm::vec3(0.25f)); - memcpy(cube.uniformBuffer.mapped, &cube.matrix, sizeof(glm::mat4)); - } - } - - 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(); - - vkGetBufferDeviceAddressKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR")); - - vkGetDescriptorSetLayoutSizeEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetDescriptorSetLayoutSizeEXT")); - vkGetDescriptorSetLayoutBindingOffsetEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetDescriptorSetLayoutBindingOffsetEXT")); - vkCmdBindDescriptorBuffersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindDescriptorBuffersEXT")); - vkGetDescriptorEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetDescriptorEXT")); - vkCmdBindDescriptorBufferEmbeddedSamplersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindDescriptorBufferEmbeddedSamplersEXT")); - vkCmdSetDescriptorBufferOffsetsEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDescriptorBufferOffsetsEXT")); - - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - prepareDescriptorBuffer(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - draw(); - if (animate && !paused) { - 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.y > 360.0f) - cubes[1].rotation.y -= 360.0f; - } - if ((camera.updated) || (animate && !paused)) { - updateUniformBuffers(); - } - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Animate", &animate); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/descriptorindexing/descriptorindexing.cpp b/examples/descriptorindexing/descriptorindexing.cpp deleted file mode 100644 index 79e6f794..00000000 --- a/examples/descriptorindexing/descriptorindexing.cpp +++ /dev/null @@ -1,461 +0,0 @@ -/* -* Vulkan Example - Descriptor indexing (VK_EXT_descriptor_indexing) -* -* Demonstrates use of descriptor indexing to dynamically index into a variable sized array of images -* -* The sample renders multiple objects with the index of the texture (descriptor) to use passed as a vertex attribute (aka "descriptor indexing") -* -* Relevant code parts are marked with [POI] -* -* Copyright (C) 2021-2025 Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - - -#include "vulkanexamplebase.h" - - -class VulkanExample : public VulkanExampleBase -{ -public: - // We will be dynamically indexing into an array of images - std::vector textures; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures{}; - - struct Vertex { - float pos[3]; - float uv[2]; - int32_t textureIndex; - }; - - VulkanExample() : VulkanExampleBase() - { - 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_MAINTENANCE1_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; - -#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // Use layer settings extension to configure MoltenVK - enabledInstanceExtensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); - - // Configure MoltenVK to use Metal argument buffers (needed for descriptor indexing) - VkLayerSettingEXT layerSetting; - layerSetting.pLayerName = "MoltenVK"; - layerSetting.pSettingName = "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS"; - layerSetting.type = VK_LAYER_SETTING_TYPE_BOOL32_EXT; - layerSetting.valueCount = 1; - - // Make this static so layer setting reference remains valid after leaving constructor scope - static const VkBool32 layerSettingOn = VK_TRUE; - layerSetting.pValues = &layerSettingOn; - enabledLayerSettings.push_back(layerSetting); -#endif - } - - ~VulkanExample() - { - if (device) { - for (auto& texture : textures) { - texture.destroy(); - } - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vertexBuffer.destroy(); - indexBuffer.destroy(); - uniformBuffer.destroy(); - } - } - - // 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(benchmark.active ? 0 : rndDevice()); - std::uniform_int_distribution<> rndDist(50, UCHAR_MAX); - const int32_t dim = 3; - const size_t bufferSize = dim * dim * 4; - std::vector texture(bufferSize); - for (size_t j = 0; j < dim * dim; j++) { - texture[j * 4] = rndDist(rndEngine); - texture[j * 4 + 1] = rndDist(rndEngine); - texture[j * 4 + 2] = rndDist(rndEngine); - texture[j * 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 and uploads them to the GPU - void generateCubes() - { - std::vector vertices; - std::vector indices; - - // Generate random per-face texture indices - std::random_device rndDevice; - std::default_random_engine rndEngine(benchmark.active ? 0 : rndDevice()); - std::uniform_int_distribution rndDist(0, static_cast(textures.size()) - 1); - - // Generate cubes with random per-face texture indices - const uint32_t count = 5; - for (uint32_t i = 0; i < count; i++) { - // Push indices to buffer - const std::vector 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(vertices.size())); - } - // 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) + 1.25f; - const std::vector 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); - } - } - - indexCount = static_cast(indices.size()); - - // Create buffers and upload data to the GPU - 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, vertices.size() * sizeof(Vertex), vertices.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, indices.size() * sizeof(uint32_t), indices.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, &vertexBuffer, vertices.size() * sizeof(Vertex))); - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t))); - - // Copy from host do device - vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue); - vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue); - - // Clean up - stagingBuffers.vertices.destroy(); - stagingBuffers.indices.destroy(); - } - - // [POI] Set up descriptor sets and set layout - void setupDescriptors() - { - // Descriptor pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(textures.size())) - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); -#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // Increase the per-stage descriptor samplers limit on macOS/iOS (maxPerStageDescriptorUpdateAfterBindSamplers > maxPerStageDescriptorSamplers) - descriptorPoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT; -#endif - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor set layout - std::vector 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(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 - VkDescriptorSetLayoutBindingFlagsCreateInfoEXT setLayoutBindingFlags{}; - setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; - setLayoutBindingFlags.bindingCount = 2; - // Binding 0 is the vertex shader uniform buffer, which does not use indexing - // Binding 1 are the fragment shader images, which use indexing - // In the fragment shader: - // layout (set = 0, binding = 1) uniform sampler2D textures[]; - -#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // Disable variable descriptor count feature on macOS/iOS until MoltenVK supports this feature when using combined image sampler textures - // Note we are using only 1 descriptor set with a fixed descriptor count/pool size, so we can simply turn off the capability for now - std::vector descriptorBindingFlags = { - 0, - 0 - }; - setLayoutBindingFlags.pBindingFlags = descriptorBindingFlags.data(); - - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - // Increase the per-stage descriptor samplers limit on macOS/iOS (maxPerStageDescriptorUpdateAfterBindSamplers > maxPerStageDescriptorSamplers) - descriptorSetLayoutCI.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; - descriptorSetLayoutCI.pNext = &setLayoutBindingFlags; - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout)); - - // [POI] Descriptor sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - allocInfo.pNext = nullptr; -#else - // Enable variable descriptor count feature on platforms other than macOS/iOS - std::vector 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)); - - // [POI] Descriptor sets - // We need to provide the descriptor counts for bindings with variable counts using a new structure - std::vector variableDesciptorCounts = { - static_cast(textures.size()) - }; - - VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo = {}; - variableDescriptorCountAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT; - variableDescriptorCountAllocInfo.descriptorSetCount = static_cast(variableDesciptorCounts.size()); - variableDescriptorCountAllocInfo.pDescriptorCounts = variableDesciptorCounts.data(); - - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - allocInfo.pNext = &variableDescriptorCountAllocInfo; -#endif - - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets(2); - - writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor); - - // Image descriptors for the texture array - std::vector 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(textures.size()); - writeDescriptorSets[1].pBufferInfo = 0; - writeDescriptorSets[1].dstSet = descriptorSet; - writeDescriptorSets[1].pImageInfo = textureDescriptors.data(); - - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // 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_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 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 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(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - // Instacing pipeline - std::array 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) in it (see descriptorindexing.frag) - 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(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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffersCamera(); - } - - void updateUniformBuffersCamera() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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 prepare() - { - VulkanExampleBase::prepare(); - generateTextures(); - generateCubes(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffersCamera(); - draw(); - } - -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/descriptorsets/descriptorsets.cpp b/examples/descriptorsets/descriptorsets.cpp deleted file mode 100644 index 0efbb4a7..00000000 --- a/examples/descriptorsets/descriptorsets.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* -* Vulkan Example - Using descriptor sets for passing data to shader stages -* -* Relevant code parts are marked with [POI] -* -* Copyright (C) 2018-2025 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" - - -class VulkanExample : public VulkanExampleBase -{ -public: - bool animate = true; - - struct Cube { - struct Matrices { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - } matrices; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - vks::Texture2D texture; - vks::Buffer uniformBuffer; - glm::vec3 rotation{ 0.0f }; - }; - std::array cubes; - - vkglTF::Model model; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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); - - 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 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(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 descriptorPoolSizes{}; - - // Uniform buffers : 1 per object - descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptorPoolSizes[0].descriptorCount = static_cast(cubes.size()); - - // Combined image samples : 1 per object texture - descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptorPoolSizes[1].descriptorCount = static_cast(cubes.size()); - - // Create the global descriptor pool - VkDescriptorPoolCreateInfo descriptorPoolCI = {}; - descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - descriptorPoolCI.poolSizeCount = static_cast(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(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 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(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 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(dynamicStateEnables.size()),0); - std::array 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(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 && !paused) { - 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.y > 360.0f) - cubes[1].rotation.y -= 360.0f; - } - if ((camera.updated) || (animate && !paused)) { - updateUniformBuffers(); - } - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Animate", &animate); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/displacement/displacement.cpp b/examples/displacement/displacement.cpp deleted file mode 100644 index 6a9e2ac4..00000000 --- a/examples/displacement/displacement.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* -* Vulkan Example - Displacement mapping with tessellation shaders -* -* This samples uses tessellation shaders to displace geometry based on a height map -* -* 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" - - -class VulkanExample : public VulkanExampleBase -{ -public: - bool splitScreen = true; - bool displacement = true; - - vkglTF::Model plane; - vks::Texture2D colorHeightMap; - - // Uniform data/buffer used by both tessellation shader stages - struct UniformData { - 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; - float tessLevel = 64.0f; - } uniformData; - vks::Buffer uniformBuffer; - - struct Pipelines { - VkPipeline solid{ VK_NULL_HANDLE }; - VkPipeline wireframe{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipelines.solid, nullptr); - if (pipelines.wireframe != VK_NULL_HANDLE) { - vkDestroyPipeline(device, pipelines.wireframe, nullptr); - }; - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - 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); - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : 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 : 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, 1), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Tessellation shader ubo - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Color and displacement map (alpha channel) - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colorHeightMap.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - VkPipelineTessellationStateCreateInfo tessellationState = vks::initializers::pipelineTessellationStateCreateInfo(3); - - // Load shaders - std::array 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV }); - - // Tessellation pipelines - // 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, &uniformBuffer, sizeof(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - uniformData.lightPos.y = -0.5f - uniformData.tessStrength; - // Tessellation control - float savedLevel = uniformData.tessLevel; - // If displacement is unchecked, we simply set the tessellation level to 1.0f, which disables tessellation - if (!displacement) { - uniformData.tessLevel = 1.0f; - } - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - if (!displacement) { - uniformData.tessLevel = savedLevel; - } - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->checkBox("Tessellation displacement", &displacement)) { - updateUniformBuffers(); - } - if (overlay->inputFloat("Strength", &uniformData.tessStrength, 0.025f, 3)) { - updateUniformBuffers(); - } - if (overlay->inputFloat("Level", &uniformData.tessLevel, 0.5f, 2)) { - updateUniformBuffers(); - } - if (deviceFeatures.fillModeNonSolid) { - if (overlay->checkBox("Splitscreen", &splitScreen)) { - buildCommandBuffers(); - updateUniformBuffers(); - } - } - - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/distancefieldfonts/distancefieldfonts.cpp b/examples/distancefieldfonts/distancefieldfonts.cpp deleted file mode 100644 index d484fcca..00000000 --- a/examples/distancefieldfonts/distancefieldfonts.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* -* Vulkan Example - Font rendering using signed distance fields -* -* This sample compares rendering resolution independent fonts using signed distance fields to traditional bitmap fonts -* -* Font generated using https://github.com/libgdx/libgdx/wiki/Hiero -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -// 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 : We store complete ASCII table -// Only chars present in the .fnt are filled with data, so not a complete, production ready solution! -std::array fontChars; - -int32_t nextValuePair(std::stringstream *stream) -{ - std::string pair; - *stream >> pair; - size_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 Textures { - vks::Texture2D fontSDF; - vks::Texture2D fontBitmap; - } textures; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - struct UniformData { - // Scene matrices - glm::mat4 projection; - glm::mat4 modelView; - // Font display options - glm::vec4 outlineColor{ 1.0f, 0.0f, 0.0f, 0.0f }; - float outlineWidth{ 0.6f }; - float outline{ true }; - } uniformData; - vks::Buffer uniformBuffer; - - struct Pipelines { - VkPipeline sdf{ VK_NULL_HANDLE }; - VkPipeline bitmap{ VK_NULL_HANDLE }; - } pipelines; - - struct DescriptorSets { - VkDescriptorSet sdf{ VK_NULL_HANDLE }; - VkDescriptorSet bitmap{ VK_NULL_HANDLE }; - } descriptorSets; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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(); - uniformBuffer.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], 0, 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 and index buffer with triangle data containing the chars of the given text - void generateText(std:: string text) - { - std::vector vertices; - std::vector indices; - uint32_t indexOffset = 0; - - float w = static_cast(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 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 = static_cast(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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - - // The pipeline uses one set and three bindings - // Binding 0 : Shader uniform buffer - // Binding 1 : Fragment shader image sampler - - std::vector 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)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - - // Signed distance font descriptor set - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.sdf)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.sdf, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.sdf, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.fontSDF.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Bitmap font descriptor set - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.bitmap)); - writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.bitmap, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.bitmap, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.fontBitmap.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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); - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Enabled blending (With the background) - 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; - - // Vertex input state - std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - // Signed distance font rendering pipeline - 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); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.sdf)); - - // 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, &uniformBuffer, sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - // Adjust camera perspective as we render two viewports - camera.setPerspective(splitScreen ? 30.0f : 45.0f, (float)width / (float)(height * ((splitScreen) ? 0.5f : 1.0f)), 1.0f, 256.0f); - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - parsebmFont(); - loadAssets(); - generateText("Vulkan"); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - bool outline = (uniformData.outline == 1.0f); - if (overlay->checkBox("Outline", &outline)) { - uniformData.outline = outline ? 1.0f : 0.0f; - } - 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(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/dynamicrendering/dynamicrendering.cpp b/examples/dynamicrendering/dynamicrendering.cpp deleted file mode 100644 index b3ac9ebc..00000000 --- a/examples/dynamicrendering/dynamicrendering.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Vulkan Example - Using VK_KHR_dynamic_rendering for rendering without framebuffers and render passes - * - * Copyright (C) 2022-2023 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" - - -class VulkanExample : public VulkanExampleBase -{ -public: - PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; - PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; - - VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; - - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 viewPos; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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); - - // The sample uses the extension (instead of Vulkan 1.2, where dynamic rendering is core) - enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - - // in addition to the extension, the feature needs to be explicitly enabled too by chaining the extension structure into device creation - enabledDynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; - enabledDynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE; - - deviceCreatepNextChain = &enabledDynamicRenderingFeaturesKHR; - } - - ~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; - }; - } - - 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)); - - // With dynamic rendering there are no subpass dependencies, so we need to take care of proper layout transitions by using barriers - // This set of barriers prepares the color and depth images for output - vks::tools::insertImageMemoryBarrier( - drawCmdBuffers[i], - swapChain.images[i], - 0, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_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_STENCIL_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.imageViews[i]; - colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - 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_STENCIL_ATTACHMENT_OPTIMAL; - 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); - - drawUI(drawCmdBuffers[i]); - - // End dynamic rendering - vkCmdEndRenderingKHR(drawCmdBuffers[i]); - - // This set of barriers prepares the color image for presentation, we don't need to care for the depth image - vks::tools::insertImageMemoryBarrier( - drawCmdBuffers[i], - swapChain.images[i], - 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 setupDescriptors() - { - // Pool - std::vector 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)); - // Layout - const std::vector 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)); - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - // Uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model) - const std::vector setLayouts = { - descriptorSetLayout, - vkglTF::descriptorSetLayoutImage, - }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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(); - - // Since we use an extension, we need to expliclity load the function pointers for extension related Vulkan commands - vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); - vkCmdEndRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR")); - - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp b/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp deleted file mode 100644 index 06971270..00000000 --- a/examples/dynamicrenderingmultisampling/dynamicrenderingmultisampling.cpp +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Vulkan Example - Using Multi sampling with VK_KHR_dynamic_rendering - * - * Copyright (C) 2025 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; - PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; - - VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; - - vkglTF::Model model; - - const VkSampleCountFlagBits multiSampleCount = VK_SAMPLE_COUNT_4_BIT; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 viewPos; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // Intermediate images used for multi sampling - struct Image { - VkImage image{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - }; - Image renderImage; - - VulkanExample() : VulkanExampleBase() - { - title = "Multi sampling with 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); - settings.overlay = false; - - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - - // The sample uses the extension (instead of Vulkan 1.2, where dynamic rendering is core) - enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - - // in addition to the extension, the feature needs to be explicitly enabled too by chaining the extension structure into device creation - enabledDynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; - enabledDynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE; - - deviceCreatepNextChain = &enabledDynamicRenderingFeaturesKHR; - } - - ~VulkanExample() override - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - vkDestroyImage(device, renderImage.image, nullptr); - vkDestroyImageView(device, renderImage.view, nullptr); - vkFreeMemory(device, renderImage.memory, nullptr); - } - } - - void setupRenderPass() override - { - // With VK_KHR_dynamic_rendering we no longer need a render pass, so we can skip the sample base render pass setup - renderPass = VK_NULL_HANDLE; - } - - void setupFrameBuffer() override - { - // With VK_KHR_dynamic_rendering we no longer need a frame buffer, so we can so skip the sample base framebuffer setup - // For multi sampling we need intermediate images that are then resolved to the final presentation image - vkDestroyImage(device, renderImage.image, nullptr); - vkDestroyImageView(device, renderImage.view, nullptr); - vkFreeMemory(device, renderImage.memory, nullptr); - VkImageCreateInfo renderImageCI = vks::initializers::imageCreateInfo(); - renderImageCI.imageType = VK_IMAGE_TYPE_2D; - renderImageCI.format = swapChain.colorFormat; - renderImageCI.extent = { width, height, 1 }; - renderImageCI.mipLevels = 1; - renderImageCI.arrayLayers = 1; - renderImageCI.samples = multiSampleCount; - renderImageCI.tiling = VK_IMAGE_TILING_OPTIMAL; - renderImageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - renderImageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VK_CHECK_RESULT(vkCreateImage(device, &renderImageCI, nullptr, &renderImage.image)); - VkMemoryRequirements memReqs{}; - vkGetImageMemoryRequirements(device, renderImage.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, &renderImage.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, renderImage.image, renderImage.memory, 0)); - VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo(); - imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - imageViewCI.image = renderImage.image; - imageViewCI.format = swapChain.colorFormat; - imageViewCI.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &renderImage.view)); - } - - // We need to override the default depth/stencil setup to create a depth image that supports multi sampling - void setupDepthStencil() override - { - 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 = multiSampleCount; - 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.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.memory, 0)); - VkImageViewCreateInfo depthImageViewCI{}; - depthImageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - depthImageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthImageViewCI.image = depthStencil.image; - depthImageViewCI.format = depthFormat; - depthImageViewCI.subresourceRange.baseMipLevel = 0; - depthImageViewCI.subresourceRange.levelCount = 1; - depthImageViewCI.subresourceRange.baseArrayLayer = 0; - depthImageViewCI.subresourceRange.layerCount = 1; - depthImageViewCI.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) { - depthImageViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - VK_CHECK_RESULT(vkCreateImageView(device, &depthImageViewCI, nullptr, &depthStencil.view)); - } - - // Enable physical device features required for this example - void getEnabledFeatures() override - { - // Enable anisotropic filtering if supported - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - }; - } - - 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() override - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) - { - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - - // With dynamic rendering there are no subpass dependencies, so we need to take care of proper layout transitions by using barriers - // This set of barriers prepares the color and depth images for output - vks::tools::insertImageMemoryBarrier( - drawCmdBuffers[i], - renderImage.image, - 0, - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_GENERAL, - 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_STENCIL_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.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - 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 }; - // When multi sampling is used, we use intermediate images to render and resolve to the swap chain images - colorAttachment.imageView = renderImage.view; - colorAttachment.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; - colorAttachment.resolveImageView = swapChain.imageViews[i]; - colorAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_GENERAL; - - // 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_STENCIL_ATTACHMENT_OPTIMAL; - 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); - - drawUI(drawCmdBuffers[i]); - - // End dynamic rendering - vkCmdEndRenderingKHR(drawCmdBuffers[i]); - - // This set of barriers prepares the color image for presentation, we don't need to care for the depth image - vks::tools::insertImageMemoryBarrier( - drawCmdBuffers[i], - swapChain.images[i], - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - 0, - VK_IMAGE_LAYOUT_GENERAL, - 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 setupDescriptors() - { - // Pool - std::vector 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)); - // Layout - const std::vector 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)); - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - // Uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model) - const std::vector setLayouts = { - descriptorSetLayout, - vkglTF::descriptorSetLayoutImage, - }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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(multiSampleCount, 0); - std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() override - { - VulkanExampleBase::prepare(); - - // Since we use an extension, we need to expliclity load the function pointers for extension related Vulkan commands - vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); - vkCmdEndRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR")); - - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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(); - } - - void render() override - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/dynamicstate/dynamicstate.cpp b/examples/dynamicstate/dynamicstate.cpp deleted file mode 100644 index a8c200c3..00000000 --- a/examples/dynamicstate/dynamicstate.cpp +++ /dev/null @@ -1,415 +0,0 @@ -/* -* Vulkan Example - Using dynamic state -* -* This sample demonstrates the use of some of the VK_EXT_dynamic_state extensions -* These allow an application to set some pipeline related state dynamically at drawtime -* instead of having to pre-bake the state into a pipeline -* This can help reduce the number of pipelines required -* -* Copyright (C) 2022-2023 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" - -class VulkanExample: public VulkanExampleBase -{ -public: - vkglTF::Model scene; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos{ 0.0f, 2.0f, 1.0f, 0.0f }; - } uniformData; - vks::Buffer uniformBuffer; - - float clearColor[4] = { 0.0f, 0.0f, 0.2f, 1.0f }; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // This sample demonstrates different dynamic states, so we check and store what extension is available - bool hasDynamicState{ false }; - bool hasDynamicState2{ false }; - bool hasDynamicState3{ false }; - bool hasDynamicVertexState{ false }; - - VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeaturesEXT{}; - VkPhysicalDeviceExtendedDynamicState2FeaturesEXT extendedDynamicState2FeaturesEXT{}; - VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamicState3FeaturesEXT{}; - - // Function pointers for dynamic states used in this sample - // VK_EXT_dynamic_stte - PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{ nullptr }; - PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT{ nullptr }; - PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT{ nullptr }; - PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{ nullptr }; - // VK_EXT_dynamic_state_2 - PFN_vkCmdSetRasterizerDiscardEnable vkCmdSetRasterizerDiscardEnableEXT{ nullptr }; - // VK_EXT_dynamic_state_3 - PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{ nullptr }; - PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT{ nullptr }; - - // Dynamic state UI toggles - struct DynamicState { - int32_t cullMode = VK_CULL_MODE_BACK_BIT; - int32_t frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - bool depthTest = true; - bool depthWrite = true; - } dynamicState; - struct DynamicState2 { - bool rasterizerDiscardEnable = false; - } dynamicState2; - struct DynamicState3 { - bool colorBlendEnable = false; - } dynamicState3; - - VulkanExample() : VulkanExampleBase() - { - title = "Dynamic state"; - 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 / (float)height, 0.1f, 256.0f); - - // Note: We enable the dynamic state extensions dynamically, based on which ones the device supports see getEnabledExtensions - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - } - } - - void getEnabledExtensions() - { - // Get the full list of extended dynamic state features supported by the device - extendedDynamicStateFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; - extendedDynamicStateFeaturesEXT.pNext = &extendedDynamicState2FeaturesEXT; - extendedDynamicState2FeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT; - extendedDynamicState2FeaturesEXT.pNext = &extendedDynamicState3FeaturesEXT; - extendedDynamicState3FeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT; - extendedDynamicState3FeaturesEXT.pNext = nullptr; - - VkPhysicalDeviceFeatures2 physicalDeviceFeatures2; - physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - physicalDeviceFeatures2.pNext = &extendedDynamicStateFeaturesEXT; - vkGetPhysicalDeviceFeatures2(physicalDevice, &physicalDeviceFeatures2); - - // Check what dynamic states are supported by the current implementation - // Checking for available features is probably sufficient, but retained redundant extension checks for clarity and consistency - hasDynamicState = vulkanDevice->extensionSupported(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) && extendedDynamicStateFeaturesEXT.extendedDynamicState; - hasDynamicState2 = vulkanDevice->extensionSupported(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) && extendedDynamicState2FeaturesEXT.extendedDynamicState2; - hasDynamicState3 = vulkanDevice->extensionSupported(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) && extendedDynamicState3FeaturesEXT.extendedDynamicState3ColorBlendEnable && extendedDynamicState3FeaturesEXT.extendedDynamicState3ColorBlendEquation; - hasDynamicVertexState = vulkanDevice->extensionSupported(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); - - // Enable dynamic state extensions if present. This function is called after physical and before logical device creation, so we can enabled extensions based on a list of supported extensions - if (hasDynamicState) { - enabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); - extendedDynamicStateFeaturesEXT.pNext = nullptr; - deviceCreatepNextChain = &extendedDynamicStateFeaturesEXT; - } - if (hasDynamicState2) { - enabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); - extendedDynamicState2FeaturesEXT.pNext = nullptr; - if (hasDynamicState) { - extendedDynamicStateFeaturesEXT.pNext = &extendedDynamicState2FeaturesEXT; - } - else { - deviceCreatepNextChain = &extendedDynamicState2FeaturesEXT; - } - } - if (hasDynamicState3) { - enabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); - if (hasDynamicState2) { - extendedDynamicState2FeaturesEXT.pNext = &extendedDynamicState3FeaturesEXT; - } - else { - deviceCreatepNextChain = &extendedDynamicState3FeaturesEXT; - } - - } - if (hasDynamicVertexState) { - enabledDeviceExtensions.push_back(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); - } - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - VkClearValue clearValues[2]; - clearValues[0].color = { { clearColor[0], clearColor[1], clearColor[2], clearColor[3] } }; - 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); - - // Apply dynamic states - - if (vkCmdSetCullModeEXT) { - vkCmdSetCullModeEXT(drawCmdBuffers[i], VkCullModeFlagBits(dynamicState.cullMode)); - } - if (vkCmdSetFrontFaceEXT) { - vkCmdSetFrontFaceEXT(drawCmdBuffers[i], VkFrontFace(dynamicState.frontFace)); - } - if (vkCmdSetDepthTestEnableEXT) { - vkCmdSetDepthTestEnableEXT(drawCmdBuffers[i], VkFrontFace(dynamicState.depthTest)); - } - if (vkCmdSetDepthWriteEnableEXT) { - vkCmdSetDepthWriteEnableEXT(drawCmdBuffers[i], VkFrontFace(dynamicState.depthWrite)); - } - - if (vkCmdSetRasterizerDiscardEnableEXT) { - vkCmdSetRasterizerDiscardEnableEXT(drawCmdBuffers[i], VkBool32(dynamicState2.rasterizerDiscardEnable)); - } - - if (vkCmdSetColorBlendEnableEXT) { - const std::vector blendEnables = { dynamicState3.colorBlendEnable }; - vkCmdSetColorBlendEnableEXT(drawCmdBuffers[i], 0, 1, blendEnables.data()); - - VkColorBlendEquationEXT colorBlendEquation{}; - - if (dynamicState3.colorBlendEnable) { - colorBlendEquation.colorBlendOp = VK_BLEND_OP_ADD; - colorBlendEquation.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR; - colorBlendEquation.dstColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR; - colorBlendEquation.alphaBlendOp = VK_BLEND_OP_ADD; - colorBlendEquation.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - colorBlendEquation.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; - } - - vkCmdSetColorBlendEquationEXT(drawCmdBuffers[i], 0, 1, &colorBlendEquation); - } - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); - scene.bindBuffers(drawCmdBuffers[i]); - - 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() - { - const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; - scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipeline - // Instead of having to create a pipeline for each state combination, we only create one pipeline and toggle the new dynamic states during command buffer creation - 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::array shaderStages; - - // All dynamic states we want to use need to be enabled at pipeline creation - std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH, }; - if (hasDynamicState) { - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_CULL_MODE_EXT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_FRONT_FACE_EXT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT); - } - if (hasDynamicState2) { - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT); - } - if (hasDynamicState3) { - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT); - } - - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - 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(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 - - 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, &pipeline)); - } - - // 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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - - // Dynamic states are set with vkCmd* calls in the command buffer, so we need to load the function pointers depending on extension supports - if (hasDynamicState) { - vkCmdSetCullModeEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetCullModeEXT")); - vkCmdSetFrontFaceEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetFrontFaceEXT")); - vkCmdSetDepthWriteEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthWriteEnableEXT")); - vkCmdSetDepthTestEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthTestEnableEXT")); - } - - if (hasDynamicState2) { - vkCmdSetRasterizerDiscardEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetRasterizerDiscardEnableEXT")); - } - - if (hasDynamicState3) { - vkCmdSetColorBlendEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetColorBlendEnableEXT")); - vkCmdSetColorBlendEquationEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetColorBlendEquationEXT")); - } - - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - bool rebuildCB = false; - if (overlay->header("Dynamic state")) { - if (hasDynamicState) { - rebuildCB = overlay->comboBox("Cull mode", &dynamicState.cullMode, { "none", "front", "back" }); - rebuildCB |= overlay->comboBox("Front face", &dynamicState.frontFace, { "Counter clockwise", "Clockwise" }); - rebuildCB |= overlay->checkBox("Depth test", &dynamicState.depthTest); - rebuildCB |= overlay->checkBox("Depth write", &dynamicState.depthWrite); - } else { - overlay->text("Extension or features not supported"); - } - } - if (overlay->header("Dynamic state 2")) { - if (hasDynamicState2) { - rebuildCB |= overlay->checkBox("Rasterizer discard", &dynamicState2.rasterizerDiscardEnable); - } - else { - overlay->text("Extension or features not supported"); - } - } - if (overlay->header("Dynamic state 3")) { - if (hasDynamicState3) { - rebuildCB |= overlay->checkBox("Color blend", &dynamicState3.colorBlendEnable); - rebuildCB |= overlay->colorPicker("Clear color", clearColor); - } - else { - overlay->text("Extension or features not supported"); - } - } - if (rebuildCB) { - buildCommandBuffers(); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/dynamicuniformbuffer/README.md b/examples/dynamicuniformbuffer/README.md deleted file mode 100644 index f70d39bd..00000000 --- a/examples/dynamicuniformbuffer/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# Dynamic uniform buffers - - - -## 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 setupDescriptors() -{ - ... - std::vector 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 setupDescriptors() -{ - ... - std::vector 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 setupDescriptors() -{ - ... - std::vector 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(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/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp b/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp deleted file mode 100644 index 893e0e22..00000000 --- a/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* -* Vulkan Example - Dynamic uniform buffers -* -* Copyright (C) 2016-2023 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 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: - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - 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{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - float animationTimer{ 0.0f }; - - size_t dynamicAlignment{ 0 }; - - VulkanExample() : VulkanExampleBase() - { - 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 (device) { - if (uboDataDynamic.model) { - alignedFree(uboDataDynamic.model); - } - 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], 0, 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(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 generateCube() - { - // Setup vertices indices for a colored cube - std::vector 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 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(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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - // Dynamic uniform buffer - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1) - }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), - // Dynamic uniform buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, VK_SHADER_STAGE_VERTEX_BIT, 1) - }; - - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - // Binding 0 : Projection/View matrix as 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Vertex bindings and attributes - VkVertexInputBindingDescription vertexInputBinding = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), // Location 0 : Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)), // Location 1 : Color - }; - VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputStateCI.vertexBindingDescriptionCount = 1; - vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding; - vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - 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 = &vertexInputStateCI; - 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(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 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(); - } - - 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() - { - // Update at max. 60 fps - animationTimer += frameTimer; - if (animationTimer <= 1.0f / 60.0f) { - return; - } - - // Dynamic ubo with per-object model matrices indexed by offsets in the command buffer - uint32_t dim = static_cast(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(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - updateDynamicUniformBuffer(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/gears/gears.cpp b/examples/gears/gears.cpp deleted file mode 100644 index f1cd272e..00000000 --- a/examples/gears/gears.cpp +++ /dev/null @@ -1,525 +0,0 @@ -/* -* Vulkan Example - Drawing multiple animated gears (emulating the look of glxgears) -* -* All gears are using single index, vertex and uniform buffers to show the Vulkan best practices of keeping the no. of buffer/memory allocations to a mimimum -* We use index offsets and instance indices to offset into the buffers at draw time for each gear -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -const uint32_t numGears = 3; - -// Used for passing the definition of a gear during construction -struct GearDefinition { - float innerRadius; - float outerRadius; - float width; - int numTeeth; - float toothDepth; - glm::vec3 color; - glm::vec3 pos; - float rotSpeed; - float rotOffset; -}; - -/* - * Gear - * This class contains the properties of a single gear and a function to generate vertices and indices - */ -class Gear -{ -public: - // Definition for the vertex data used to render the gears - struct Vertex { - glm::vec3 position; - glm::vec3 normal; - glm::vec3 color; - }; - - glm::vec3 color; - glm::vec3 pos; - float rotSpeed{ 0.0f }; - float rotOffset{ 0.0f }; - // These are used at draw time to offset into the single buffers - uint32_t indexCount{ 0 }; - uint32_t indexStart{ 0 }; - - // Generates the indices and vertices for this gear - // They are added to the vertex and index buffers passed into the function - // This way we can put all gears into single vertex and index buffers instead of having to allocate single buffers for each gear (which would be bad practice) - void generate(GearDefinition& gearDefinition, std::vector& vertexBuffer, std::vector& indexBuffer) { - this->color = gearDefinition.color; - this->pos = gearDefinition.pos; - this->rotOffset = gearDefinition.rotOffset; - this->rotSpeed = gearDefinition.rotSpeed; - - int i; - 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; - - // We need to know where this triangle's indices start within the single index buffer - indexStart = static_cast(indexBuffer.size()); - - r0 = gearDefinition.innerRadius; - r1 = gearDefinition.outerRadius - gearDefinition.toothDepth / 2.0f; - r2 = gearDefinition.outerRadius + gearDefinition.toothDepth / 2.0f; - da = static_cast (2.0 * M_PI / gearDefinition.numTeeth / 4.0); - - glm::vec3 normal; - - // Use lambda functions to simplify vertex and face creation - auto addFace = [&indexBuffer](int a, int b, int c) { - indexBuffer.push_back(a); - indexBuffer.push_back(b); - indexBuffer.push_back(c); - }; - - auto addVertex = [this, &vertexBuffer](float x, float y, float z, glm::vec3 normal) { - Vertex v{}; - v.position = { x, y, z }; - v.normal = normal; - v.color = this->color; - vertexBuffer.push_back(v); - return static_cast(vertexBuffer.size()) - 1; - }; - - for (i = 0; i < gearDefinition.numTeeth; i++) { - ta = i * static_cast (2.0 * M_PI / gearDefinition.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 = addVertex(r0 * cos_ta, r0 * sin_ta, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r1 * cos_ta, r1 * sin_ta, gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r0 * cos_ta, r0 * sin_ta, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, gearDefinition.width * 0.5f, normal); - ix4 = addVertex(r0 * cos_ta_4da, r0 * sin_ta_4da, gearDefinition.width * 0.5f, normal); - ix5 = addVertex(r1 * cos_ta_4da, r1 * sin_ta_4da, gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - addFace(ix2, ix3, ix4); - addFace(ix3, ix5, ix4); - - // Teeth front face - normal = glm::vec3(0.0f, 0.0f, 1.0f); - ix0 = addVertex(r1 * cos_ta, r1 * sin_ta, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - // Back face - normal = glm::vec3(0.0f, 0.0f, -1.0f); - ix0 = addVertex(r1 * cos_ta, r1 * sin_ta, -gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r0 * cos_ta, r0 * sin_ta, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, -gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r0 * cos_ta, r0 * sin_ta, -gearDefinition.width * 0.5f, normal); - ix4 = addVertex(r1 * cos_ta_4da, r1 * sin_ta_4da, -gearDefinition.width * 0.5f, normal); - ix5 = addVertex(r0 * cos_ta_4da, r0 * sin_ta_4da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - addFace(ix2, ix3, ix4); - addFace(ix3, ix5, ix4); - - // Teeth back face - normal = glm::vec3(0.0f, 0.0f, -1.0f); - ix0 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, -gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r1 * cos_ta, r1 * sin_ta, -gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - // Outard teeth faces - normal = glm::vec3(v1, -u1, 0.0f); - ix0 = addVertex(r1 * cos_ta, r1 * sin_ta, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r1 * cos_ta, r1 * sin_ta, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - normal = glm::vec3(cos_ta, sin_ta, 0.0f); - ix0 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r2 * cos_ta_1da, r2 * sin_ta_1da, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - normal = glm::vec3(v2, -u2, 0.0f); - ix0 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r2 * cos_ta_2da, r2 * sin_ta_2da, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - normal = glm::vec3(cos_ta, sin_ta, 0.0f); - ix0 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, gearDefinition.width * 0.5f, normal); - ix1 = addVertex(r1 * cos_ta_3da, r1 * sin_ta_3da, -gearDefinition.width * 0.5f, normal); - ix2 = addVertex(r1 * cos_ta_4da, r1 * sin_ta_4da, gearDefinition.width * 0.5f, normal); - ix3 = addVertex(r1 * cos_ta_4da, r1 * sin_ta_4da, -gearDefinition.width * 0.5f, normal); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - - // Inside cylinder faces - ix0 = addVertex(r0 * cos_ta, r0 * sin_ta, -gearDefinition.width * 0.5f, glm::vec3(-cos_ta, -sin_ta, 0.0f)); - ix1 = addVertex(r0 * cos_ta, r0 * sin_ta, gearDefinition.width * 0.5f, glm::vec3(-cos_ta, -sin_ta, 0.0f)); - ix2 = addVertex(r0 * cos_ta_4da, r0 * sin_ta_4da, -gearDefinition.width * 0.5f, glm::vec3(-cos_ta_4da, -sin_ta_4da, 0.0f)); - ix3 = addVertex(r0 * cos_ta_4da, r0 * sin_ta_4da, gearDefinition.width * 0.5f, glm::vec3(-cos_ta_4da, -sin_ta_4da, 0.0f)); - addFace(ix0, ix1, ix2); - addFace(ix1, ix3, ix2); - } - - // We need to know how many indices this triangle has at draw time - indexCount = static_cast(indexBuffer.size()) - indexStart; - } -}; - -/* - * VulkanExample - */ -class VulkanExample : public VulkanExampleBase -{ -public: - std::vector gears{}; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // Even though this sample renders multiple objects (gears), we only use single buffers - // This is a best practices and Vulkan applications should keep the number of memory allocations as small as possible - // Having as little buffers as possible also reduces the number of buffer binds - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - struct UniformData - { - glm::mat4 projection; - glm::mat4 view; - glm::vec4 lightPos; - // The model matrix is used to rotate a given gear, so we have one mat4 per gear - glm::mat4 model[numGears]; - } uniformData; - vks::Buffer uniformBuffer; - - VulkanExample() : VulkanExampleBase() - { - title = "Vulkan gears"; - camera.type = Camera::CameraType::lookat; - camera.setPosition(glm::vec3(0.0f, 2.5f, -16.0f)); - camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f)); - camera.setPerspective(60.0f, (float)width / (float)height, 0.001f, 256.0f); - timerSpeed *= 0.25f; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - indexBuffer.destroy(); - vertexBuffer.destroy(); - uniformBuffer.destroy(); - } - } - - void prepareGears() - { - // Set up three differntly shaped and colored gears - std::vector gearDefinitions(3); - - // Large red gear - gearDefinitions[0].innerRadius = 1.0f; - gearDefinitions[0].outerRadius = 4.0f; - gearDefinitions[0].width = 1.0f; - gearDefinitions[0].numTeeth = 20; - gearDefinitions[0].toothDepth = 0.7f; - gearDefinitions[0].color = { 1.0f, 0.0f, 0.0f }; - gearDefinitions[0].pos = { -3.0f, 0.0f, 0.0f }; - gearDefinitions[0].rotSpeed = 1.0f; - gearDefinitions[0].rotOffset = 0.0f; - - // Medium sized green gear - gearDefinitions[1].innerRadius = 0.5f; - gearDefinitions[1].outerRadius = 2.0f; - gearDefinitions[1].width = 2.0f; - gearDefinitions[1].numTeeth = 10; - gearDefinitions[1].toothDepth = 0.7f; - gearDefinitions[1].color = { 0.0f, 1.0f, 0.2f }; - gearDefinitions[1].pos = { 3.1f, 0.0f, 0.0f }; - gearDefinitions[1].rotSpeed = -2.0f; - gearDefinitions[1].rotOffset = -9.0f; - - // Small blue gear - gearDefinitions[2].innerRadius = 1.3f; - gearDefinitions[2].outerRadius = 2.0f; - gearDefinitions[2].width = 0.5f; - gearDefinitions[2].numTeeth = 10; - gearDefinitions[2].toothDepth = 0.7f; - gearDefinitions[2].color = { 0.0f, 0.0f, 1.0f }; - gearDefinitions[2].pos = { -3.1f, -6.2f, 0.0f }; - gearDefinitions[2].rotSpeed = -2.0f; - gearDefinitions[2].rotOffset = -30.0f; - - // We'll be using a single vertex and a single index buffer for all the gears, no matter their number - // This is a Vulkan best practice as it keeps the no. of memory/buffer allocations low - // Vulkan offers all the tools to easily index into those buffers at a later point (see the buildCommandBuffers function) - std::vector vertices{}; - std::vector indices{}; - - // Fills the vertex and index buffers for each of the gear - gears.resize(gearDefinitions.size()); - for (int32_t i = 0; i < gears.size(); i++) { - gears[i].generate(gearDefinitions[i], vertices, indices); - } - - // Create buffers and stage to device for performances - size_t vertexBufferSize = vertices.size() * sizeof(Gear::Vertex); - size_t indexBufferSize = indices.size() * sizeof(uint32_t); - - vks::Buffer vertexStaging, indexStaging; - - // Temorary Staging buffers from vertex and index data - vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexStaging, vertexBufferSize, vertices.data()); - vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexStaging, indexBufferSize, indices.data()); - // Device local buffers to where our staging buffers will be copied to - vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertexBuffer, vertexBufferSize); - vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indexBufferSize); - - // Copy host (staging) to device - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = {}; - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertexBuffer.buffer, 1, ©Region); - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indexBuffer.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - vertexStaging.destroy(); - indexStaging.destroy(); - } - - void setupDescriptors() - { - // We use a single descriptor set for the uniform data that contains both global matrices as well as per-gear model matrices - - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, static_cast(gears.size())); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor); - vkUpdateDescriptorSets(vulkanDevice->logicalDevice, 1, &writeDescriptorSet, 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - // Solid rendering pipeline - // Load shaders - std::array 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); - - // Vertex bindings and attributes to match the vertex buffers storing the vertices for the gears - VkVertexInputBindingDescription vertexInputBinding = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Gear::Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Gear::Vertex, position)), // Location 0 : Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Gear::Vertex, normal)), // Location 1 : Normal - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Gear::Vertex, color)), // Location 2 : Color - }; - VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputStateCI.vertexBindingDescriptionCount = 1; - vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding; - vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); - pipelineCreateInfo.pVertexInputState = &vertexInputStateCI; - 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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); - } - - 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); - - // Vertices, indices and uniform data for all gears are stored in single buffers, so we only need to bind one buffer of each type and then index/offset into that for each separate gear - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets); - vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); - for (auto j = 0; j < numGears; j++) { - // We use the instance index (last argument) to pass the index of the triangle to the shader - // With this we can index into the model matrices array of the uniform buffer like this (see gears.vert): - // ubo.model[gl_InstanceIndex]; - vkCmdDrawIndexed(drawCmdBuffers[i], gears[j].indexCount, 1, gears[j].indexStart, 0, j); - } - - drawUI(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - 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(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - float degree = timer * 360.0f; - - // Camera specific global matrices - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.lightPos = glm::vec4(0.0f, 0.0f, 2.5f, 1.0f); - - // Update the model matrix for each gear that contains it's position and rotation - for (auto i = 0; i < numGears; i++) { - Gear gear = gears[i]; - uniformData.model[i] = glm::mat4(1.0f); - uniformData.model[i] = glm::translate(uniformData.model[i], gear.pos); - uniformData.model[i] = glm::rotate(uniformData.model[i], glm::radians((gear.rotSpeed * degree) + gear.rotOffset), glm::vec3(0.0f, 0.0f, 1.0f)); - } - - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - prepareGears(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - -}; - -VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/examples/geometryshader/geometryshader.cpp b/examples/geometryshader/geometryshader.cpp deleted file mode 100644 index 8174a8d8..00000000 --- a/examples/geometryshader/geometryshader.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/* -* Vulkan Example - Geometry shader (vertex normal debugging) -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool displayNormals = true; - - vkglTF::Model scene; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline solid{ VK_NULL_HANDLE }; - VkPipeline normals{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipelines.solid, nullptr); - vkDestroyPipeline(device, pipelines.normals, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.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); - - // 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : Shader uniform ubo - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - //Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 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 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 = static_cast(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, &uniformBuffer, sizeof(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - // Vertex shader - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/gltfloading/gltfloading.cpp b/examples/gltfloading/gltfloading.cpp deleted file mode 100644 index 827f7dbb..00000000 --- a/examples/gltfloading/gltfloading.cpp +++ /dev/null @@ -1,744 +0,0 @@ -/* -* Vulkan Example - glTF scene loading and rendering -* -* Copyright (C) 2020-2023 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" - - -// 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 primitives; - }; - - // A node represents an object in the glTF scene graph - struct Node { - Node* parent; - std::vector children; - Mesh mesh; - glm::mat4 matrix; - ~Node() { - for (auto& child : children) { - delete child; - } - } - }; - - // 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 images; - std::vector textures; - std::vector materials; - std::vector nodes; - - ~VulkanglTFModel() - { - for (auto node : nodes) { - delete node; - } - // 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& indexBuffer, std::vector& vertexBuffer) - { - VulkanglTFModel::Node* node = new VulkanglTFModel::Node{}; - node->matrix = glm::mat4(1.0f); - node->parent = parent; - - // 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(indexBuffer.size()); - uint32_t vertexStart = static_cast(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 positions - 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(&(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(&(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(&(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(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&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(&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(&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); - glm::vec4 viewPos; - } values; - } shaderData; - - struct Pipelines { - VkPipeline solid{ VK_NULL_HANDLE }; - VkPipeline wireframe{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - struct DescriptorSetLayouts { - VkDescriptorSetLayout matrices{ VK_NULL_HANDLE }; - VkDescriptorSetLayout textures{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - - VulkanExample() : VulkanExampleBase() - { - 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, 45.0f, 0.0f)); - camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); - } - - ~VulkanExample() - { - if (device) { - 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 = { { 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 indexBuffer; - std::vector 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\nMake sure the assets submodule has been checked out and is up-to-date.", -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(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, - ©Region); - - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - glTFModel.indices.buffer, - 1, - ©Region); - - 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 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(glTFModel.images.size())), - }; - // One set for matrices and one per model image/texture - const uint32_t maxSetCount = static_cast(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)); - - // 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() - { - // Layout - // The pipeline layout uses both descriptor sets (set 0 = matrices, set 1 = material) - std::array setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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)); - - // 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - // Vertex input bindings and attributes - const std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - }; - const std::vector 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(vertexInputBindings.size()); - vertexInputStateCI.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - const std::array 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(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()); - } - - void updateUniformBuffers() - { - shaderData.values.projection = camera.matrices.perspective; - shaderData.values.model = camera.matrices.view; - shaderData.values.viewPos = camera.viewPos; - memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - updateUniformBuffers(); - renderFrame(); - - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->checkBox("Wireframe", &wireframe)) { - buildCommandBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/gltfscenerendering/README.md b/examples/gltfscenerendering/README.md deleted file mode 100644 index 6c661e82..00000000 --- a/examples/gltfscenerendering/README.md +++ /dev/null @@ -1,323 +0,0 @@ -# glTF scene rendering - - - -## 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```
-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```
-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```
-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 { - VkBool32 alphaMask; - float alphaMaskCutoff; - } materialSpecializationData; - - materialSpecializationData.alphaMask = material.alphaMode == "MASK"; - materialSpecializationData.alphaMaskCutoff = material.alphaCutOff; - - std::vector 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 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(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& indexBuffer, std::vector& 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(&(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/examples/gltfscenerendering/gltfscenerendering.cpp b/examples/gltfscenerendering/gltfscenerendering.cpp deleted file mode 100644 index 7faef44c..00000000 --- a/examples/gltfscenerendering/gltfscenerendering.cpp +++ /dev/null @@ -1,663 +0,0 @@ -/* -* Vulkan Example - Scene rendering -* -* Copyright (C) 2020-2023 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() -{ - for (auto node : nodes) { - delete node; - } - // 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& indexBuffer, std::vector& vertexBuffer) -{ - VulkanglTFScene::Node* node = new VulkanglTFScene::Node{}; - node->name = inputNode.name; - node->parent = parent; - - // 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(indexBuffer.size()); - uint32_t vertexStart = static_cast(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(&(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(&(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(&(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(&(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(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&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(&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(&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() -{ - 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() -{ - if (device) { - 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 indexBuffer; - std::vector 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\nMake sure the assets submodule has been checked out and is up-to-date.", -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(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, - ©Region); - - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - glTFScene.indices.buffer, - 1, - ©Region); - - 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 poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(glTFScene.materials.size()) * 2), - }; - // One set for matrices and one per model image/texture - const uint32_t maxSetCount = static_cast(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 setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0) - }; - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(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)); - - // 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 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } -} - -void VulkanExample::preparePipelines() -{ - // Layout - // Pipeline layout uses both descriptor sets (set 0 = matrices, set 1 = material) - std::array setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); - std::array shaderStages; - - const std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFScene::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - }; - const std::vector 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(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 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()); -} - -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() -{ - updateUniformBuffers(); - renderFrame(); -} - -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 * overlay->scale, 340.0f * overlay->scale), false); - for (auto& node : glTFScene.nodes) - { - if (overlay->checkBox(node->name.c_str(), &node->visible)) - { - buildCommandBuffers(); - } - } - ImGui::EndChild(); - } -} - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/gltfscenerendering/gltfscenerendering.h b/examples/gltfscenerendering/gltfscenerendering.h deleted file mode 100644 index 7e71bbfe..00000000 --- a/examples/gltfscenerendering/gltfscenerendering.h +++ /dev/null @@ -1,170 +0,0 @@ -/* -* Vulkan Example - Scene rendering -* -* Copyright (C) 2020-2023 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" - - - // 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 primitives; - }; - - // A node represents an object in the glTF scene graph - struct Node { - Node* parent; - std::vector children; - Mesh mesh; - glm::mat4 matrix; - std::string name; - bool visible = true; - ~Node() { - for (auto& child : children) { - delete child; - } - } - }; - - // 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 images; - std::vector textures; - std::vector materials; - std::vector 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& indexBuffer, std::vector& 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{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - struct DescriptorSetLayouts { - VkDescriptorSetLayout matrices{ VK_NULL_HANDLE }; - VkDescriptorSetLayout textures{ VK_NULL_HANDLE }; - } 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); -}; diff --git a/examples/gltfskinning/README.md b/examples/gltfskinning/README.md deleted file mode 100644 index 88e199b3..00000000 --- a/examples/gltfskinning/README.md +++ /dev/null @@ -1,550 +0,0 @@ -# glTF vertex skinning - - - -## 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 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 inverseBindMatrices; - std::vector 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 inputs; - std::vector 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 samplers; - std::vector channels; - float start = std::numeric_limits::max(); - float end = std::numeric_limits::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(&(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(&(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 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(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(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(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. - - -####
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 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/examples/gltfskinning/gltfskinning.cpp b/examples/gltfskinning/gltfskinning.cpp deleted file mode 100644 index 6bade0c6..00000000 --- a/examples/gltfskinning/gltfskinning.cpp +++ /dev/null @@ -1,1006 +0,0 @@ -/* -* Vulkan Example - glTF skinned animation -* -* Copyright (C) 2020-2023 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(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(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(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 &indexBuffer, std::vector &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(indexBuffer.size()); - uint32_t vertexStart = static_cast(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(&(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(&(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(&(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(&(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(&(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(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) - { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&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(&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(&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 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(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() -{ - 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 indexBuffer; - std::vector 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\nMake sure the assets submodule has been checked out and is up-to-date.", -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(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, ©Region); - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, indexStaging.buffer, glTFModel.indices.buffer, 1, ©Region); - 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 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(glTFModel.images.size())), - // One ssbo per skin - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(glTFModel.skins.size())), - }; - // Number of descriptor sets = One for the scene ubo + one per image + one per skin - const uint32_t maxSetCount = static_cast(glTFModel.images.size()) + static_cast(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)); - - // 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() -{ - // Layout - // The pipeline layout uses three sets: - // Set 0 = Scene matrices (VS) - // Set 1 = Joint matrices (VS) - // Set 2 = Material texture (FS) - std::array setLayouts = { - descriptorSetLayouts.matrices, - descriptorSetLayouts.jointMatrices, - descriptorSetLayouts.textures }; - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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)); - - // 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, 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); - // Vertex input bindings and attributes - const std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - }; - const std::vector 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(vertexInputBindings.size()); - vertexInputStateCI.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - const std::array 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(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() -{ - updateUniformBuffers(); - // POI: Advance animation - if (!paused) { - glTFModel.updateAnimation(frameTimer); - } - renderFrame(); -} - -void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay *overlay) -{ - if (overlay->header("Settings")) - { - if (overlay->checkBox("Wireframe", &wireframe)) - { - buildCommandBuffers(); - } - } -} - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/gltfskinning/gltfskinning.h b/examples/gltfskinning/gltfskinning.h deleted file mode 100644 index 367394c6..00000000 --- a/examples/gltfskinning/gltfskinning.h +++ /dev/null @@ -1,235 +0,0 @@ -/* -* Vulkan Example - glTF skinned animation -* -* Copyright (C) 2020-2024 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 -#include -#include -#include -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include -#include - -#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 - - -// 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 primitives; - }; - - struct Node - { - Node * parent; - uint32_t index; - std::vector 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 inverseBindMatrices; - std::vector joints; - vks::Buffer ssbo; - VkDescriptorSet descriptorSet; - }; - - /* - Animation related structures - */ - - struct AnimationSampler - { - std::string interpolation; - std::vector inputs; - std::vector outputsVec4; - }; - - struct AnimationChannel - { - std::string path; - Node * node; - uint32_t samplerIndex; - }; - - struct Animation - { - std::string name; - std::vector samplers; - std::vector channels; - float start = std::numeric_limits::max(); - float end = std::numeric_limits::min(); - float currentTime = 0.0f; - }; - - std::vector images; - std::vector textures; - std::vector materials; - std::vector nodes; - std::vector skins; - std::vector 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 &indexBuffer, std::vector &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{ VK_NULL_HANDLE }; - struct Pipelines - { - VkPipeline solid{ VK_NULL_HANDLE }; - VkPipeline wireframe{ VK_NULL_HANDLE }; - } pipelines; - - struct DescriptorSetLayouts - { - VkDescriptorSetLayout matrices{ VK_NULL_HANDLE }; - VkDescriptorSetLayout textures{ VK_NULL_HANDLE }; - VkDescriptorSetLayout jointMatrices{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - 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/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp b/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp deleted file mode 100644 index cc0d711a..00000000 --- a/examples/graphicspipelinelibrary/graphicspipelinelibrary.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/* -* Vulkan Example - Using VK_EXT_graphics_pipeline_library -* -* Copyright (C) 2022-2023 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 -#include - -class VulkanExample: public VulkanExampleBase -{ -public: - bool linkTimeOptimization = true; - - vkglTF::Model scene; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos = glm::vec4(0.0f, -2.0f, 1.0f, 0.0f); - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT graphicsPipelineLibraryFeatures{}; - - struct PipelineLibrary { - VkPipeline vertexInputInterface; - VkPipeline preRasterizationShaders; - VkPipeline fragmentOutputInterface; - std::vector fragmentShaders; - } pipelineLibrary; - - std::vector pipelines{}; - - struct ShaderInfo { - uint32_t* code; - size_t size; - }; - - std::mutex mutex; - VkPipelineCache threadPipelineCache{ VK_NULL_HANDLE }; - - bool newPipelineCreated = false; - - uint32_t splitX{ 2 }; - uint32_t splitY{ 2 }; - - std::vector colors{}; - float rotation{ 0.0f }; - - VulkanExample() : VulkanExampleBase() - { - title = "Graphics pipeline library"; - camera.type = Camera::CameraType::lookat; - camera.setPosition(glm::vec3(0.0f, 0.0f, -2.0f)); - camera.setRotation(glm::vec3(-25.0f, 15.0f, 0.0f)); - camera.setRotationSpeed(0.5f); - - // Enable required extensions - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME); - - // Enable required extension features - graphicsPipelineLibraryFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT; - graphicsPipelineLibraryFeatures.graphicsPipelineLibrary = VK_TRUE; - deviceCreatepNextChain = &graphicsPipelineLibraryFeatures; - } - - ~VulkanExample() - { - if (device) { - for (auto pipeline : pipelines) { - vkDestroyPipeline(device, pipeline, nullptr); - } - for (auto pipeline : pipelineLibrary.fragmentShaders) { - vkDestroyPipeline(device, pipeline, nullptr); - } - vkDestroyPipeline(device, pipelineLibrary.fragmentOutputInterface, nullptr); - vkDestroyPipeline(device, pipelineLibrary.preRasterizationShaders, nullptr); - vkDestroyPipeline(device, pipelineLibrary.vertexInputInterface, nullptr); - vkDestroyPipelineCache(device, threadPipelineCache, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - 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) - { - renderPassBeginInfo.framebuffer = frameBuffers[i]; - - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - - vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); - scene.bindBuffers(drawCmdBuffers[i]); - - // Render a viewport for each pipeline - float w = (float)width / (float)splitX; - float h = (float)height / (float)splitY; - uint32_t idx = 0; - for (uint32_t y = 0; y < splitX; y++) { - for (uint32_t x = 0; x < splitY; x++) { - VkViewport viewport{}; - viewport.x = w * (float)x; - viewport.y = h * (float)y; - viewport.width = w; - viewport.height = h; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - - VkRect2D scissor{}; - scissor.extent.width = (uint32_t)w; - scissor.extent.height = (uint32_t)h; - scissor.offset.x = (uint32_t)w * x; - scissor.offset.y = (uint32_t)h * y; - vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - - if (pipelines.size() > idx) { - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[idx]); - scene.draw(drawCmdBuffers[i]); - } - - idx++; - } - } - - 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/color_teapot_spheres.gltf", vulkanDevice, queue, glTFLoadingFlags); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - // With VK_EXT_graphics_pipeline_library we don't need to create the shader module when loading it, but instead have the driver create it at linking time - // So we use a custom function that only loads the required shader information without actually creating the shader module - bool loadShaderFile(std::string fileName, ShaderInfo &shaderInfo) - { -#if defined(__ANDROID__) - // Load shader from compressed asset - AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, fileName.c_str(), AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - - shaderInfo.size = size; - shaderInfo.code = new uint32_t[size / 4]; - AAsset_read(asset, reinterpret_cast(shaderInfo.code), size); - AAsset_close(asset); - return true; -#else - std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate); - - if (is.is_open()) - { - shaderInfo.size = is.tellg(); - is.seekg(0, std::ios::beg); - shaderInfo.code = new uint32_t[shaderInfo.size]; - is.read(reinterpret_cast(shaderInfo.code), shaderInfo.size); - is.close(); - return true; - } else { - std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << "\n"; - throw std::runtime_error("Could open shader file"); - return false; - } -#endif - } - - // Create the shared pipeline parts up-front - void preparePipelineLibrary() - { - // Shared layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Create a pipeline library for the vertex input interface - { - VkGraphicsPipelineLibraryCreateInfoEXT libraryInfo{}; - libraryInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT; - libraryInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT; - - VkPipelineVertexInputStateCreateInfo vertexInputState = *vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color }); - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); - - VkGraphicsPipelineCreateInfo pipelineLibraryCI{}; - pipelineLibraryCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineLibraryCI.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; - pipelineLibraryCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineLibraryCI.pNext = &libraryInfo; - pipelineLibraryCI.pInputAssemblyState = &inputAssemblyState; - pipelineLibraryCI.pVertexInputState = &vertexInputState; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineLibraryCI, nullptr, &pipelineLibrary.vertexInputInterface)); - } - - // Creata a pipeline library for the vertex shader stage - { - VkGraphicsPipelineLibraryCreateInfoEXT libraryInfo{}; - libraryInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT; - libraryInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT; - - VkDynamicState vertexDynamicStates[2] = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR }; - - VkPipelineDynamicStateCreateInfo dynamicInfo{}; - dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicInfo.dynamicStateCount = 2; - dynamicInfo.pDynamicStates = vertexDynamicStates; - - VkPipelineViewportStateCreateInfo viewportState = {}; - viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); - - ShaderInfo shaderInfo{}; - loadShaderFile(getShadersPath() + "graphicspipelinelibrary/shared.vert.spv", shaderInfo); - - VkShaderModuleCreateInfo shaderModuleCI{}; - shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - shaderModuleCI.codeSize = shaderInfo.size; - shaderModuleCI.pCode = shaderInfo.code; - - VkPipelineShaderStageCreateInfo shaderStageCI{}; - shaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStageCI.pNext = &shaderModuleCI; - shaderStageCI.stage = VK_SHADER_STAGE_VERTEX_BIT; - shaderStageCI.pName = "main"; - - VkGraphicsPipelineCreateInfo pipelineLibraryCI{}; - pipelineLibraryCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineLibraryCI.pNext = &libraryInfo; - pipelineLibraryCI.renderPass = renderPass; - pipelineLibraryCI.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; - pipelineLibraryCI.stageCount = 1; - pipelineLibraryCI.pStages = &shaderStageCI; - pipelineLibraryCI.layout = pipelineLayout; - pipelineLibraryCI.pDynamicState = &dynamicInfo; - pipelineLibraryCI.pViewportState = &viewportState; - pipelineLibraryCI.pRasterizationState = &rasterizationState; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineLibraryCI, nullptr, &pipelineLibrary.preRasterizationShaders)); - - delete[] shaderInfo.code; - } - - // Create a pipeline library for the fragment output interface - { - VkGraphicsPipelineLibraryCreateInfoEXT libraryInfo{}; - libraryInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT; - libraryInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT; - - VkPipelineColorBlendAttachmentState blendAttachmentSstate = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); - VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentSstate); - VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); - - VkGraphicsPipelineCreateInfo pipelineLibraryCI{}; - pipelineLibraryCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineLibraryCI.pNext = &libraryInfo; - pipelineLibraryCI.layout = pipelineLayout; - pipelineLibraryCI.renderPass = renderPass; - pipelineLibraryCI.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; - pipelineLibraryCI.pColorBlendState = &colorBlendState; - pipelineLibraryCI.pMultisampleState = &multisampleState; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineLibraryCI, nullptr, &pipelineLibrary.fragmentOutputInterface)); - } - } - - void threadFn() - { - const std::lock_guard lock(mutex); - - auto start = std::chrono::steady_clock::now(); - - prepareNewPipeline(); - newPipelineCreated = true; - - // Change viewport/draw count - if (pipelines.size() > splitX * splitY) { - splitX++; - splitY++; - } - - auto delta = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); - std::cout << "Pipeline created in " << delta.count() << " microseconds\n"; - } - - // Create a new pipeline using the pipeline library and a customized fragment shader - // Used from a thread - void prepareNewPipeline() - { - // Create the fragment shader part of the pipeline library with some random options - VkGraphicsPipelineLibraryCreateInfoEXT libraryInfo{}; - libraryInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT; - libraryInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT; - - VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); - VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); - - // Using the pipeline library extension, we can skip the pipeline shader module creation and directly pass the shader code to the pipeline - ShaderInfo shaderInfo{}; - loadShaderFile(getShadersPath() + "graphicspipelinelibrary/uber.frag.spv", shaderInfo); - - VkShaderModuleCreateInfo shaderModuleCI{}; - shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - shaderModuleCI.codeSize = shaderInfo.size; - shaderModuleCI.pCode = shaderInfo.code; - - VkPipelineShaderStageCreateInfo shaderStageCI{}; - shaderStageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStageCI.pNext = &shaderModuleCI; - shaderStageCI.stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderStageCI.pName = "main"; - - // Select lighting model using a specialization constant - srand(benchmark.active ? 0 : ((unsigned int)time(NULL))); - uint32_t lighting_model = (int)(rand() % 4); - - // Each shader constant of a shader stage corresponds to one map entry - VkSpecializationMapEntry specializationMapEntry{}; - specializationMapEntry.constantID = 0; - specializationMapEntry.size = sizeof(uint32_t); - - VkSpecializationInfo specializationInfo{}; - specializationInfo.mapEntryCount = 1; - specializationInfo.pMapEntries = &specializationMapEntry; - specializationInfo.dataSize = sizeof(uint32_t); - specializationInfo.pData = &lighting_model; - - shaderStageCI.pSpecializationInfo = &specializationInfo; - - VkGraphicsPipelineCreateInfo pipelineCI{}; - pipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipelineCI.pNext = &libraryInfo; - pipelineCI.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; - pipelineCI.stageCount = 1; - pipelineCI.pStages = &shaderStageCI; - pipelineCI.layout = pipelineLayout; - pipelineCI.renderPass = renderPass; - pipelineCI.pDepthStencilState = &depthStencilState; - pipelineCI.pMultisampleState = &multisampleState; - VkPipeline fragmentShader = VK_NULL_HANDLE; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, threadPipelineCache, 1, &pipelineCI, nullptr, &fragmentShader)); - - // Create the pipeline using the pre-built pipeline library parts - // Except for above fragment shader part all parts have been pre-built and will be re-used - std::vector libraries = { - pipelineLibrary.vertexInputInterface, - pipelineLibrary.preRasterizationShaders, - fragmentShader, - pipelineLibrary.fragmentOutputInterface }; - - // Link the library parts into a graphics pipeline - VkPipelineLibraryCreateInfoKHR pipelineLibraryCI{}; - pipelineLibraryCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR; - pipelineLibraryCI.libraryCount = static_cast(libraries.size()); - pipelineLibraryCI.pLibraries = libraries.data(); - - // If set to true, we pass VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT which will let the implementation do additional optimizations at link time - // This trades in pipeline creation time for run-time performance - bool optimized = true; - - VkGraphicsPipelineCreateInfo executablePipelineCI{}; - executablePipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - executablePipelineCI.pNext = &pipelineLibraryCI; - executablePipelineCI.layout = pipelineLayout; - if (linkTimeOptimization) - { - // If link time optimization is activated in the UI, we set the VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT flag which will let the implementation do additional optimizations at link time - // This trades in pipeline creation time for run-time performance - executablePipelineCI.flags = VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT; - } - - VkPipeline executable = VK_NULL_HANDLE; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, threadPipelineCache, 1, &executablePipelineCI, nullptr, &executable)); - - pipelines.push_back(executable); - // Push fragment shader to list for deletion in the sample's destructor - pipelineLibrary.fragmentShaders.push_back(fragmentShader); - - delete[] shaderInfo.code; - } - - // 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(UniformData))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - if (!paused) { - rotation += frameTimer * 0.1f; - } - camera.setPerspective(45.0f, ((float)width / (float)splitX) / ((float)height / (float)splitY), 0.1f, 256.0f); - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view * glm::rotate(glm::mat4(1.0f), glm::radians(rotation * 360.0f), glm::vec3(0.0f, 1.0f, 0.0f)); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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(); - preparePipelineLibrary(); - buildCommandBuffers(); - - // Create a separate pipeline cache for the pipeline creation thread - VkPipelineCacheCreateInfo pipelineCachCI = {}; - pipelineCachCI.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - vkCreatePipelineCache(device, &pipelineCachCI, nullptr, &threadPipelineCache); - - // Create first pipeline using a background thread - std::thread pipelineGenerationThread(&VulkanExample::threadFn, this); - pipelineGenerationThread.detach(); - - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - if (newPipelineCreated) - { - newPipelineCreated = false; - vkQueueWaitIdle(queue); - buildCommandBuffers(); - } - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - overlay->checkBox("Link time optimization", &linkTimeOptimization); - if (overlay->button("New pipeline")) { - // Spwan a thread to create a new pipeline in the background - std::thread pipelineGenerationThread(&VulkanExample::threadFn, this); - pipelineGenerationThread.detach(); - } - } -}; - -VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/examples/hdr/hdr.cpp b/examples/hdr/hdr.cpp deleted file mode 100644 index 4687e11e..00000000 --- a/examples/hdr/hdr.cpp +++ /dev/null @@ -1,830 +0,0 @@ -/* -* Vulkan Example - High dynamic range rendering pipeline -* -* This sample implements a HDR rendering pipeline that uses a wider range of possible colors via float component image formats -* It also does a bloom filter on the HDR image -* The final output is standard definition range (SDR) -* Note: Does not make use of HDR display capability. HDR is only internally used for offscreen rendering. -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool bloom = true; - bool displaySkybox = true; - - struct { - vks::TextureCubeMap envmap; - } textures; - - struct Models { - vkglTF::Model skybox; - std::vector objects; - int32_t index{ 1 }; - } models; - std::vector modelNames{}; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelview; - glm::mat4 inverseModelview; - float exposure{ 1.0f }; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline skybox{ VK_NULL_HANDLE }; - VkPipeline reflect{ VK_NULL_HANDLE }; - VkPipeline composition{ VK_NULL_HANDLE }; - // Bloom is a two pass filter (one pass for vertical and horizontal blur) - VkPipeline bloom[2]{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout models{ VK_NULL_HANDLE }; - VkPipelineLayout composition{ VK_NULL_HANDLE }; - VkPipelineLayout bloomFilter{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet object{ VK_NULL_HANDLE }; - VkDescriptorSet skybox{ VK_NULL_HANDLE }; - VkDescriptorSet composition{ VK_NULL_HANDLE }; - VkDescriptorSet bloomFilter{ VK_NULL_HANDLE }; - } descriptorSets; - - struct { - VkDescriptorSetLayout models{ VK_NULL_HANDLE }; - VkDescriptorSetLayout composition{ VK_NULL_HANDLE }; - VkDescriptorSetLayout bloomFilter{ VK_NULL_HANDLE }; - } 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; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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); - uniformBuffer.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; - - 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 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.index].vertices.buffer, offsets); - vkCmdBindIndexBuffer(drawCmdBuffers[i], models.objects[models.index].indices.buffer, 0, VK_INDEX_TYPE_UINT32); - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect); - models.objects[models.index].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; - if (format >= VK_FORMAT_D16_UNORM_S8_UINT) - aspectMask |=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 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 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 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(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 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(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 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 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 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(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 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(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 filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" }; - modelNames = { "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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6) - }; - const uint32_t numDescriptorSets = 4; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), numDescriptorSets); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layouts - std::vector 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 descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.composition)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.models, 1); - - // 3D object descriptor set - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Sky box descriptor set - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox)); - writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0,&uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Bloom filter - allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.bloomFilter, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.bloomFilter)); - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.models, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.models)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.bloomFilter, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.bloomFilter)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.composition, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - - VkSpecializationInfo specializationInfo; - std::array 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 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() - { - 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))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelview = camera.matrices.view; - uniformData.inverseModelview = glm::inverse(camera.matrices.view); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - prepareoffscreenfer(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->comboBox("Object type", &models.index, modelNames)) { - buildCommandBuffers(); - } - overlay->inputFloat("Exposure", &uniformData.exposure, 0.025f, 3); - if (overlay->checkBox("Bloom", &bloom)) { - buildCommandBuffers(); - } - if (overlay->checkBox("Skybox", &displaySkybox)) { - buildCommandBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/hostimagecopy/hostimagecopy.cpp b/examples/hostimagecopy/hostimagecopy.cpp deleted file mode 100644 index cad565c5..00000000 --- a/examples/hostimagecopy/hostimagecopy.cpp +++ /dev/null @@ -1,460 +0,0 @@ -/* -* Vulkan Example - Host image copy using VK_EXT_host_image_copy -* -* This sample shows how to use host image copies to directly upload an image to the devic without having to use staging - -* Copyright (C) 2024 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 -#include - -class VulkanExample : public VulkanExampleBase -{ -public: - // Pointers for functions added by the host image copy extension; - PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImageEXT{ nullptr }; - PFN_vkTransitionImageLayoutEXT vkTransitionImageLayoutEXT{ nullptr }; - // Used to check feature image format support for host image copies - PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2{ nullptr }; - - VkPhysicalDeviceHostImageCopyFeaturesEXT enabledPhysicalDeviceHostImageCopyFeaturesEXT{}; - - // Contains all Vulkan objects that are required to store and use a texture - struct Texture { - VkSampler sampler{ VK_NULL_HANDLE }; - VkImage image{ VK_NULL_HANDLE }; - VkDeviceMemory deviceMemory{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - uint32_t width{ 0 }; - uint32_t height{ 0 }; - uint32_t mipLevels{ 0 }; - } texture; - - vkglTF::Model plane; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 viewPos; - float lodBias = 0.0f; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - title = "Host image copy"; - camera.type = Camera::CameraType::lookat; - camera.setPosition(glm::vec3(0.0f, 0.0f, -1.5f)); - camera.setRotation(glm::vec3(0.0f, 15.0f, 0.0f)); - camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); - - // Enable required extensions - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME); - - // Enable host image copy feature - enabledPhysicalDeviceHostImageCopyFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT; - enabledPhysicalDeviceHostImageCopyFeaturesEXT.hostImageCopy = VK_TRUE; - deviceCreatepNextChain = &enabledPhysicalDeviceHostImageCopyFeaturesEXT; - } - - ~VulkanExample() override - { - if (device) { - destroyTextureImage(texture); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - } - } - - // Enable physical device features required for this example - void getEnabledFeatures() override - { - // Enable anisotropic filtering if supported - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - }; - } - - /* - Upload texture image data to the GPU - - Unlike the texture(3d/array/etc) samples, this one uses the VK_EXT_host_image_copy to drasticly simplify the process - of uploading an image from the host to the GPU. This new extension adds a way of directly uploading image data from - host memory to an optimal tiled image on the device (GPU). This no longer requires a staging buffer in between, as we can - now directly copy data stored in host memory to the image. The extension also adds new functionality to simplfy image barriers - */ - 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"; - - 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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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); - - const VkFormat imageFormat = VK_FORMAT_R8G8B8A8_UNORM; - - // Check if the image format supports the host image copy flag - // Note: All formats that support sampling are required to support this flag - // So for the format used here (R8G8B8A8_UNORM) we could skip this check - // The flag we need to check is an extension flag, so we need to go through VkFormatProperties3 - VkFormatProperties3 formatProperties3{}; - formatProperties3.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR; - // Properties3 need to be chained into Properties2 - VkFormatProperties2 formatProperties2{}; - formatProperties2.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; - formatProperties2.pNext = &formatProperties3; - vkGetPhysicalDeviceFormatProperties2(physicalDevice, imageFormat, &formatProperties2); - - if ((formatProperties3.optimalTilingFeatures & VK_FORMAT_FEATURE_2_HOST_IMAGE_TRANSFER_BIT_EXT) == 0) { - vks::tools::exitFatal("The selected image format does not support the required host transfer bit.", -1); - } - - // Create optimal tiled target image on the device - VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = imageFormat; - 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 }; - // For images that use host image copy we need to specify the VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT usage flag - imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT; - VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.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)); - - // With host image copy we can directly copy from the KTX image in host memory to the device - // This is pretty straight forward, as the KTX image is already tightly packed, doesn't need and swizzle and as such matches - // what the device expects - - // Set up copy information for all mip levels stored in the image - std::vector memoryToImageCopies{}; - for (uint32_t i = 0; i < texture.mipLevels; i++) { - // Setup a buffer image copy structure for the current mip level - VkMemoryToImageCopyEXT memoryToImageCopy = {}; - memoryToImageCopy.sType = VK_STRUCTURE_TYPE_MEMORY_TO_IMAGE_COPY_EXT; - memoryToImageCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - memoryToImageCopy.imageSubresource.mipLevel = i; - memoryToImageCopy.imageSubresource.baseArrayLayer = 0; - memoryToImageCopy.imageSubresource.layerCount = 1; - memoryToImageCopy.imageExtent.width = ktxTexture->baseWidth >> i; - memoryToImageCopy.imageExtent.height = ktxTexture->baseHeight >> i; - memoryToImageCopy.imageExtent.depth = 1; - - // This tells the implementation where to read the data from - // As the KTX file is tightly packed, we can simply offset into that 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); - memoryToImageCopy.pHostPointer = ktxTextureData + offset; - - memoryToImageCopies.push_back(memoryToImageCopy); - } - - VkImageSubresourceRange subresourceRange{}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.baseMipLevel = 0; - subresourceRange.levelCount = texture.mipLevels; - subresourceRange.layerCount = 1; - - // VK_EXT_host_image_copy als introduces a simplified way of doing the required image transition on the host - // This no longer requires a dedicated command buffer to submit the barrier - // We also no longer need multiple transitions, and only have to do one for the final layout - VkHostImageLayoutTransitionInfoEXT hostImageLayoutTransitionInfo{}; - hostImageLayoutTransitionInfo.sType = VK_STRUCTURE_TYPE_HOST_IMAGE_LAYOUT_TRANSITION_INFO_EXT; - hostImageLayoutTransitionInfo.image = texture.image; - hostImageLayoutTransitionInfo.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - hostImageLayoutTransitionInfo.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - hostImageLayoutTransitionInfo.subresourceRange = subresourceRange; - - vkTransitionImageLayoutEXT(device, 1, &hostImageLayoutTransitionInfo); - - // With the image in the correct layout and copy information for all mip levels setup, we can now issue the copy to our taget image from the host - // The implementation will then convert this to an implementation specific optimal tiling layout - VkCopyMemoryToImageInfoEXT copyMemoryInfo{}; - copyMemoryInfo.sType = VK_STRUCTURE_TYPE_COPY_MEMORY_TO_IMAGE_INFO_EXT; - copyMemoryInfo.dstImage = texture.image; - copyMemoryInfo.dstImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - copyMemoryInfo.regionCount = static_cast(memoryToImageCopies.size()); - copyMemoryInfo.pRegions = memoryToImageCopies.data(); - - vkCopyMemoryToImageEXT(device, ©MemoryInfo); - - ktxTexture_Destroy(ktxTexture); - - // Create a texture 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_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; - sampler.maxLod = (float)texture.mipLevels; - sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy; - sampler.anisotropyEnable = VK_TRUE; - 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.viewType = VK_IMAGE_VIEW_TYPE_2D; - view.format = imageFormat; - view.subresourceRange = subresourceRange; - 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() override - { - 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, nullptr); - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - 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; - textureDescriptor.sampler = texture.sampler; - textureDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - // Shaders - std::array shaderStages = { - loadShader(getShadersPath() + "texture/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getShadersPath() + "texture/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) - }; - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, 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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - pipelineCreateInfo.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Normal }); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, 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(uniformData), &uniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - uniformData.viewPos = camera.viewPos; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void loadAssets() - { - const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; - plane.loadFromFile(getAssetPath() + "models/plane_z.gltf", vulkanDevice, queue, glTFLoadingFlags); - } - - void prepare() override - { - VulkanExampleBase::prepare(); - - // Get the function pointers required host image copies - vkCopyMemoryToImageEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCopyMemoryToImageEXT")); - vkTransitionImageLayoutEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkTransitionImageLayoutEXT")); - vkGetPhysicalDeviceFormatProperties2 = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties2KHR")); - - loadAssets(); - loadTexture(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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(); - } - - void render() override - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - void OnUpdateUIOverlay(vks::UIOverlay *overlay) override - { - if (overlay->header("Settings")) { - if (overlay->sliderFloat("LOD bias", &uniformData.lodBias, 0.0f, (float)texture.mipLevels)) { - updateUniformBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/imgui/main.cpp b/examples/imgui/main.cpp index 3b612ad3..4a1a6e40 100644 --- a/examples/imgui/main.cpp +++ b/examples/imgui/main.cpp @@ -1,25 +1,1002 @@ /* -* Vulkan Example - imGui (https://github.com/ocornut/imgui) -* -* Copyright (C) 2017-2025 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ + * Procedural 3D Engine + * Copyright (c) 2025 Your Project + * + * This software is licensed under the MIT License. + * See LICENSE.md for full license information. + * + * Based on Vulkan examples by Sascha Willems (MIT License) + */ #include #include "vulkanexamplebase.h" #include "VulkanglTFModel.h" +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + + +// Enhanced Camera System with Maya-style controls +class OrbitCamera { +public: + // Constructor + OrbitCamera(glm::vec3 initialPosition = glm::vec3(0.0f, 0.0f, 8.0f), + glm::vec3 focusPoint = glm::vec3(0.0f, 0.0f, 0.0f)) { + m_currentFocusPoint = focusPoint; + m_targetFocusPoint = focusPoint; + + // Calculate initial spherical coordinates from position + glm::vec3 offset = initialPosition - focusPoint; + m_currentDistance = glm::length(offset); + m_targetDistance = m_currentDistance; + + // Calculate initial angles + CartesianToSpherical(initialPosition, focusPoint, m_currentDistance, m_currentAzimuth, m_currentElevation); + m_targetAzimuth = m_currentAzimuth; + m_targetElevation = m_currentElevation; + + m_currentPosition = initialPosition; + + // Set default parameters + m_fov = 45.0f; + m_smoothingFactor = 0.1f; + m_orbitSensitivity = 0.5f; + m_panSensitivity = 0.00003f; + m_zoomSensitivity = 0.1f; + + // Set constraints + m_minDistance = 0.5f; + m_maxDistance = 100.0f; + m_minElevation = -89.0f; + m_maxElevation = 89.0f; + + m_viewMatrixDirty = true; + } + + // Core camera operations + void Orbit(float deltaAzimuth, float deltaElevation, float deltaTime) { + // Apply input to target angles (convert degrees to radians) + m_targetAzimuth += glm::radians(deltaAzimuth * m_orbitSensitivity); + m_targetElevation += glm::radians(deltaElevation * m_orbitSensitivity); + ClampConstraints(); + } + + void Pan(float deltaX, float deltaY, float deltaTime) { + // Get camera's right and up vectors for screen-space panning (like legacy) + glm::mat4 viewMatrix = GetViewMatrix(); + glm::vec3 right = glm::vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]); + glm::vec3 up = glm::vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]); + + // Scale pan speed by distance to focus point for consistent behavior (like legacy) + float panScale = m_currentDistance * m_panSensitivity; + + // Move focus point (like legacy) + glm::vec3 panOffset = right * deltaX * panScale + up * deltaY * panScale; + m_targetFocusPoint += panOffset; + } + + void Zoom(float deltaDistance, float deltaTime) { + // Apply zoom with distance-based scaling for consistent behavior (like legacy) + m_targetDistance += deltaDistance * m_zoomSensitivity * m_currentDistance * 0.1f; + m_targetDistance = glm::clamp(m_targetDistance, m_minDistance, m_maxDistance); + } + + void ZoomImmediate(float deltaDistance, float deltaTime) { + // Apply zoom with immediate response (no smoothing/velocity) + float zoomAmount = deltaDistance * m_zoomSensitivity * m_currentDistance * 0.1f; + + // Set both target AND current immediately (no smoothing) + m_targetDistance += zoomAmount; + m_targetDistance = glm::clamp(m_targetDistance, m_minDistance, m_maxDistance); + m_currentDistance = m_targetDistance; + m_viewMatrixDirty = true; + } + + void Update(float deltaTime) { + // Smooth interpolation towards target values + float lerpFactor = 1.0f - std::pow(m_smoothingFactor, deltaTime); + + m_currentDistance = glm::mix(m_currentDistance, m_targetDistance, lerpFactor); + m_currentAzimuth = LerpAngle(m_currentAzimuth, m_targetAzimuth, lerpFactor); + m_currentElevation = glm::mix(m_currentElevation, m_targetElevation, lerpFactor); + m_currentFocusPoint = glm::mix(m_currentFocusPoint, m_targetFocusPoint, lerpFactor); + + UpdatePosition(); + } + + // Matrix access + glm::mat4 GetViewMatrix() const { + if (m_viewMatrixDirty) { + m_viewMatrix = glm::lookAt(m_currentPosition, m_currentFocusPoint, glm::vec3(0.0f, 1.0f, 0.0f)); + m_viewMatrixDirty = false; + } + return m_viewMatrix; + } + + glm::mat4 GetProjectionMatrix(float aspectRatio, float nearPlane = 0.1f, float farPlane = 256.0f) const { + return glm::perspective(glm::radians(m_fov), aspectRatio, nearPlane, farPlane); + } + + // Getters + glm::vec3 GetPosition() const { return m_currentPosition; } + glm::vec3 GetFocusPoint() const { return m_currentFocusPoint; } + float GetDistance() const { return m_currentDistance; } + float GetFOV() const { return m_fov; } + + // Focus functionality + void SetFocusToSelection(glm::vec3 selectionCenter, float selectionRadius = 1.0f) { + m_targetFocusPoint = selectionCenter; + + // Adjust distance based on selection size + if (selectionRadius > 0.0f) { + float recommendedDistance = selectionRadius * 3.0f; // Good viewing distance + recommendedDistance = glm::clamp(recommendedDistance, m_minDistance, m_maxDistance); + m_targetDistance = recommendedDistance; + } + + std::cout << "OrbitCamera: Focus set to selection at (" + << selectionCenter.x << ", " << selectionCenter.y << ", " << selectionCenter.z + << ") with radius " << selectionRadius << std::endl; + } + + void FrameAll(glm::vec3 sceneCenter, float sceneRadius) { + SetFocusToSelection(sceneCenter, sceneRadius); + } + + // Configuration + void SetSensitivity(float orbitSens, float panSens, float zoomSens) { + m_orbitSensitivity = orbitSens; + m_panSensitivity = panSens; + m_zoomSensitivity = zoomSens; + } + + void SetSmoothingFactor(float factor) { + m_smoothingFactor = glm::clamp(factor, 0.0f, 1.0f); + } + + // Immediate (non-smoothed) operations for F key focus + void SetFocusPointImmediate(glm::vec3 focusPoint) { + m_targetFocusPoint = focusPoint; + m_currentFocusPoint = focusPoint; // Immediate change, no smoothing + m_viewMatrixDirty = true; + } + + void SetDistanceImmediate(float distance) { + m_targetDistance = glm::clamp(distance, m_minDistance, m_maxDistance); + m_currentDistance = m_targetDistance; // Immediate change, no smoothing + UpdatePosition(); + m_viewMatrixDirty = true; + } + + void SetFocusToSelectionImmediate(glm::vec3 selectionCenter, float selectionRadius = 1.0f) { + SetFocusPointImmediate(selectionCenter); + + // Adjust distance based on selection size + if (selectionRadius > 0.0f) { + float recommendedDistance = selectionRadius * 3.0f; // Good viewing distance + recommendedDistance = glm::clamp(recommendedDistance, m_minDistance, m_maxDistance); + SetDistanceImmediate(recommendedDistance); + } + + std::cout << "OrbitCamera: Focus set immediately to selection at (" + << selectionCenter.x << ", " << selectionCenter.y << ", " << selectionCenter.z + << ") with radius " << selectionRadius << std::endl; + } + + void FrameAllImmediate(glm::vec3 sceneCenter, float sceneRadius) { + SetFocusPointImmediate(sceneCenter); + + // Calculate distance needed to frame the scene + float distance = sceneRadius / glm::tan(glm::radians(m_fov * 0.5f)) * 1.5f; // 1.5x padding + SetDistanceImmediate(distance); + } + + void PanImmediate(float deltaX, float deltaY, float deltaTime) { + // Get camera's right and up vectors for screen-space panning (like Pan method) + glm::mat4 viewMatrix = GetViewMatrix(); + glm::vec3 right = glm::vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]); + glm::vec3 up = glm::vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]); + + // Scale pan speed by distance to focus point for consistent behavior + float panScale = m_currentDistance * m_panSensitivity; + + // Calculate pan offset + glm::vec3 panOffset = right * deltaX * panScale + up * deltaY * panScale; + + // Set both target AND current immediately (no smoothing) + m_targetFocusPoint += panOffset; + m_currentFocusPoint += panOffset; + m_viewMatrixDirty = true; + } + +private: + // Current state (interpolated) + glm::vec3 m_currentPosition; + glm::vec3 m_currentFocusPoint; + float m_currentDistance; + float m_currentAzimuth; // Horizontal angle around focus point + float m_currentElevation; // Vertical angle (pitch) + + // Target state (immediate input response) + glm::vec3 m_targetFocusPoint; + float m_targetDistance; + float m_targetAzimuth; + float m_targetElevation; + + // Camera parameters + float m_fov; // Field of view in degrees + float m_smoothingFactor; // 0.0 = no smoothing, 1.0 = maximum smoothing + + // Sensitivity settings + float m_orbitSensitivity; + float m_panSensitivity; + float m_zoomSensitivity; + + // Constraints + float m_minDistance; + float m_maxDistance; + float m_minElevation; // In degrees + float m_maxElevation; // In degrees + + // Cached matrices to avoid recalculation + mutable glm::mat4 m_viewMatrix; + mutable bool m_viewMatrixDirty; + + // Internal methods + void UpdatePosition() { + m_currentPosition = SphericalToCartesian(m_currentDistance, m_currentAzimuth, m_currentElevation) + m_currentFocusPoint; + m_viewMatrixDirty = true; + } + + void ClampConstraints() { + m_targetDistance = glm::clamp(m_targetDistance, m_minDistance, m_maxDistance); + m_targetElevation = glm::clamp(m_targetElevation, glm::radians(m_minElevation), glm::radians(m_maxElevation)); + + // Normalize azimuth to 0-2Ï€ range + while (m_targetAzimuth > 2.0f * M_PI) m_targetAzimuth -= 2.0f * M_PI; + while (m_targetAzimuth < 0.0f) m_targetAzimuth += 2.0f * M_PI; + } + + glm::vec3 SphericalToCartesian(float distance, float azimuth, float elevation) const { + // Standard spherical coordinate conversion (input in radians) + float x = distance * glm::cos(elevation) * glm::cos(azimuth); + float y = distance * glm::sin(elevation); + float z = distance * glm::cos(elevation) * glm::sin(azimuth); + return glm::vec3(x, y, z); + } + + void CartesianToSpherical(glm::vec3 position, glm::vec3 center, float& distance, float& azimuth, float& elevation) const { + glm::vec3 offset = position - center; + distance = glm::length(offset); + + if (distance < 0.001f) { + // Very close to center, use default values + azimuth = 0.0f; + elevation = 0.0f; + return; + } + + // Normalize offset for angle calculations + glm::vec3 normalized = offset / distance; + + // Calculate azimuth (horizontal angle) - using atan for proper quadrant + azimuth = glm::atan(normalized.z, normalized.x); + + // Calculate elevation (vertical angle) - use asin but clamp input to avoid NaN + float sinElevation = glm::clamp(normalized.y, -1.0f, 1.0f); + elevation = glm::asin(sinElevation); + } + + float LerpAngle(float from, float to, float t) const { + // Handle angle wraparound for smooth interpolation (using radians) + float difference = to - from; + + // Wrap difference to [-Ï€, Ï€] range + if (difference > M_PI) difference -= 2.0f * M_PI; + if (difference < -M_PI) difference += 2.0f * M_PI; + + return from + difference * t; + } +}; + + +// Procedural Geometry Generation +struct ProceduralVertex { + glm::vec3 position; + glm::vec3 normal; + glm::vec3 color; +}; + +struct ProceduralShape { + std::vector vertices; + std::vector indices; + std::string name; + int type; // 0=cube, 1=sphere, 2=cylinder, 3=plane, 4=cone, 5=torus + + // Shape parameters + struct { + float width = 2.0f, height = 2.0f, depth = 2.0f; + int subdivisions = 1; + float radius = 1.0f; + int segments = 16; + float majorRadius = 1.0f, minorRadius = 0.3f; + } params; +}; + +class ProceduralGeometry { +public: + static ProceduralShape generateCube(float width = 4.0f, float height = 4.0f, float depth = 4.0f) { + ProceduralShape shape; + shape.name = "Cube"; + shape.type = 0; + shape.params.width = width; + shape.params.height = height; + shape.params.depth = depth; + + float w = width * 0.5f; + float h = height * 0.5f; + float d = depth * 0.5f; + + // Define 24 vertices (4 per face, 6 faces) with colors + shape.vertices = { + // Front face (red) + {{-w, -h, d}, {0, 0, 1}, {1, 0, 0}}, {{ w, -h, d}, {0, 0, 1}, {1, 0, 0}}, + {{ w, h, d}, {0, 0, 1}, {1, 0, 0}}, {{-w, h, d}, {0, 0, 1}, {1, 0, 0}}, + // Back face (green) + {{ w, -h, -d}, {0, 0, -1}, {0, 1, 0}}, {{-w, -h, -d}, {0, 0, -1}, {0, 1, 0}}, + {{-w, h, -d}, {0, 0, -1}, {0, 1, 0}}, {{ w, h, -d}, {0, 0, -1}, {0, 1, 0}}, + // Left face (blue) + {{-w, -h, -d}, {-1, 0, 0}, {0, 0, 1}}, {{-w, -h, d}, {-1, 0, 0}, {0, 0, 1}}, + {{-w, h, d}, {-1, 0, 0}, {0, 0, 1}}, {{-w, h, -d}, {-1, 0, 0}, {0, 0, 1}}, + // Right face (yellow) + {{ w, -h, d}, {1, 0, 0}, {1, 1, 0}}, {{ w, -h, -d}, {1, 0, 0}, {1, 1, 0}}, + {{ w, h, -d}, {1, 0, 0}, {1, 1, 0}}, {{ w, h, d}, {1, 0, 0}, {1, 1, 0}}, + // Top face (magenta) + {{-w, h, d}, {0, 1, 0}, {1, 0, 1}}, {{ w, h, d}, {0, 1, 0}, {1, 0, 1}}, + {{ w, h, -d}, {0, 1, 0}, {1, 0, 1}}, {{-w, h, -d}, {0, 1, 0}, {1, 0, 1}}, + // Bottom face (cyan) + {{-w, -h, -d}, {0, -1, 0}, {0, 1, 1}}, {{ w, -h, -d}, {0, -1, 0}, {0, 1, 1}}, + {{ w, -h, d}, {0, -1, 0}, {0, 1, 1}}, {{-w, -h, d}, {0, -1, 0}, {0, 1, 1}} + }; + + // Define indices for 12 triangles (2 per face) + shape.indices = { + 0,1,2, 0,2,3, // Front + 4,5,6, 4,6,7, // Back + 8,9,10, 8,10,11, // Left + 12,13,14, 12,14,15, // Right + 16,17,18, 16,18,19, // Top + 20,21,22, 20,22,23 // Bottom + }; + + return shape; + } + + static ProceduralShape generateSphere(float radius = 1.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Sphere"; + shape.type = 1; + shape.params.radius = radius; + shape.params.segments = segments; + + // Generate sphere vertices using spherical coordinates + for (int lat = 0; lat <= segments; ++lat) { + float theta = lat * M_PI / segments; + float sinTheta = sin(theta); + float cosTheta = cos(theta); + + for (int lon = 0; lon <= segments; ++lon) { + float phi = lon * 2 * M_PI / segments; + float sinPhi = sin(phi); + float cosPhi = cos(phi); + + glm::vec3 pos(radius * sinTheta * cosPhi, radius * cosTheta, radius * sinTheta * sinPhi); + glm::vec3 normal = glm::normalize(pos); + glm::vec3 color(0.8f, 0.8f, 0.8f); // Light gray color for sphere + + shape.vertices.push_back({pos, normal, color}); + } + } + + // Generate indices + for (int lat = 0; lat < segments; ++lat) { + for (int lon = 0; lon < segments; ++lon) { + int first = lat * (segments + 1) + lon; + int second = first + segments + 1; + + shape.indices.push_back(first); + shape.indices.push_back(second); + shape.indices.push_back(first + 1); + + shape.indices.push_back(second); + shape.indices.push_back(second + 1); + shape.indices.push_back(first + 1); + } + } + + return shape; + } + + static ProceduralShape generatePlane(float width = 2.0f, float height = 2.0f, int subdivisions = 1) { + ProceduralShape shape; + shape.name = "Plane"; + shape.type = 3; + shape.params.width = width; + shape.params.height = height; + shape.params.subdivisions = subdivisions; + + float w = width * 0.5f; + float h = height * 0.5f; + + // Simple quad for now + shape.vertices = { + {{-w, 0, -h}, {0, 1, 0}, {0.5f, 0.5f, 0.5f}}, // Gray plane + {{ w, 0, -h}, {0, 1, 0}, {0.5f, 0.5f, 0.5f}}, + {{ w, 0, h}, {0, 1, 0}, {0.5f, 0.5f, 0.5f}}, + {{-w, 0, h}, {0, 1, 0}, {0.5f, 0.5f, 0.5f}} + }; + + shape.indices = {0, 1, 2, 0, 2, 3}; + + return shape; + } + + static ProceduralShape generateGrid(float size = 10.0f, int divisions = 10) { + ProceduralShape shape; + shape.name = "Grid"; + shape.type = 6; // Grid type + shape.params.width = size; + shape.params.subdivisions = divisions; + + float step = size / divisions; + float halfSize = size * 0.5f; + + // Create grid lines (white grid) + glm::vec3 gridColor(1.0f, 1.0f, 1.0f); + for (int i = 0; i <= divisions; ++i) { + float pos = -halfSize + i * step; + + // Horizontal lines + shape.vertices.push_back({{-halfSize, 0, pos}, {0, 1, 0}, gridColor}); + shape.vertices.push_back({{ halfSize, 0, pos}, {0, 1, 0}, gridColor}); + + // Vertical lines + shape.vertices.push_back({{pos, 0, -halfSize}, {0, 1, 0}, gridColor}); + shape.vertices.push_back({{pos, 0, halfSize}, {0, 1, 0}, gridColor}); + } + + // Generate indices for lines + for (int i = 0; i < (divisions + 1) * 4; i += 2) { + shape.indices.push_back(i); + shape.indices.push_back(i + 1); + } + + return shape; + } + + static ProceduralShape generateCone(float radius = 1.0f, float height = 2.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Cone"; + shape.type = 4; // Cone type + shape.params.radius = radius; + shape.params.height = height; + shape.params.segments = segments; + + // Add tip vertex (red cone tip) + shape.vertices.push_back({{0, height * 0.5f, 0}, {0, 1, 0}, {1.0f, 0.0f, 0.0f}}); + + // Add center vertex for base (dark red) + shape.vertices.push_back({{0, -height * 0.5f, 0}, {0, -1, 0}, {0.5f, 0.0f, 0.0f}}); + + // Generate base vertices + for (int i = 0; i <= segments; ++i) { + float angle = (float)i / segments * 2.0f * M_PI; + float x = cos(angle) * radius; + float z = sin(angle) * radius; + glm::vec3 color(0.8f, 0.2f, 0.2f); // Light red + + // Base vertex + shape.vertices.push_back({{x, -height * 0.5f, z}, {0, -1, 0}, color}); + + // Side vertex (for side triangles) + glm::vec3 sideNormal = glm::normalize(glm::vec3(x, radius / height, z)); + shape.vertices.push_back({{x, -height * 0.5f, z}, sideNormal, color}); + } + + // Generate indices + // Side triangles (tip to base edge) + for (int i = 0; i < segments; ++i) { + int baseStart = 2 + segments + 1; + shape.indices.push_back(0); // tip + shape.indices.push_back(baseStart + (i + 1) * 2); + shape.indices.push_back(baseStart + i * 2); + } + + // Base triangles + for (int i = 0; i < segments; ++i) { + shape.indices.push_back(1); // center + shape.indices.push_back(2 + i); + shape.indices.push_back(2 + ((i + 1) % (segments + 1))); + } + + return shape; + } + + static ProceduralShape generateCylinder(float radius = 1.0f, float height = 2.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Cylinder"; + shape.type = 5; // Cylinder type + shape.params.radius = radius; + shape.params.height = height; + shape.params.segments = segments; + + float halfHeight = height * 0.5f; + + // Add center vertices for caps (blue cylinder) + shape.vertices.push_back({{0, halfHeight, 0}, {0, 1, 0}, {0.0f, 0.0f, 1.0f}}); // top center + shape.vertices.push_back({{0, -halfHeight, 0}, {0, -1, 0}, {0.0f, 0.0f, 1.0f}}); // bottom center + + // Generate side vertices (double for proper normals) + for (int i = 0; i <= segments; ++i) { + float angle = (float)i / segments * 2.0f * M_PI; + float x = cos(angle) * radius; + float z = sin(angle) * radius; + glm::vec3 normal = glm::normalize(glm::vec3(x, 0, z)); + glm::vec3 color(0.2f, 0.4f, 1.0f); // Light blue + + // Top vertices + shape.vertices.push_back({{x, halfHeight, z}, {0, 1, 0}, color}); // top cap + shape.vertices.push_back({{x, halfHeight, z}, normal, color}); // top side + + // Bottom vertices + shape.vertices.push_back({{x, -halfHeight, z}, {0, -1, 0}, color}); // bottom cap + shape.vertices.push_back({{x, -halfHeight, z}, normal, color}); // bottom side + } + + // Generate indices + for (int i = 0; i < segments; ++i) { + int topCapStart = 2; + int bottomCapStart = 4; + + // Top cap triangles + shape.indices.push_back(0); // top center + shape.indices.push_back(topCapStart + ((i + 1) % (segments + 1)) * 4); + shape.indices.push_back(topCapStart + i * 4); + + // Bottom cap triangles + shape.indices.push_back(1); // bottom center + shape.indices.push_back(bottomCapStart + i * 4); + shape.indices.push_back(bottomCapStart + ((i + 1) % (segments + 1)) * 4); + + // Side quads (as two triangles) + int topSide1 = topCapStart + 1 + i * 4; + int topSide2 = topCapStart + 1 + ((i + 1) % (segments + 1)) * 4; + int bottomSide1 = bottomCapStart + 1 + i * 4; + int bottomSide2 = bottomCapStart + 1 + ((i + 1) % (segments + 1)) * 4; + + // First triangle + shape.indices.push_back(topSide1); + shape.indices.push_back(bottomSide1); + shape.indices.push_back(topSide2); + + // Second triangle + shape.indices.push_back(topSide2); + shape.indices.push_back(bottomSide1); + shape.indices.push_back(bottomSide2); + } + + return shape; + } + + static ProceduralShape generateTorus(float majorRadius = 1.0f, float minorRadius = 0.3f, int majorSegments = 16, int minorSegments = 8) { + ProceduralShape shape; + shape.name = "Torus"; + shape.type = 6; // Torus type + shape.params.majorRadius = majorRadius; + shape.params.minorRadius = minorRadius; + shape.params.segments = majorSegments; + shape.params.subdivisions = minorSegments; + + // Generate vertices + for (int i = 0; i <= majorSegments; ++i) { + float majorAngle = (float)i / majorSegments * 2.0f * M_PI; + float cosMajor = cos(majorAngle); + float sinMajor = sin(majorAngle); + + for (int j = 0; j <= minorSegments; ++j) { + float minorAngle = (float)j / minorSegments * 2.0f * M_PI; + float cosMinor = cos(minorAngle); + float sinMinor = sin(minorAngle); + + // Calculate position + float x = (majorRadius + minorRadius * cosMinor) * cosMajor; + float y = minorRadius * sinMinor; + float z = (majorRadius + minorRadius * cosMinor) * sinMajor; + + // Calculate normal + glm::vec3 center(majorRadius * cosMajor, 0, majorRadius * sinMajor); + glm::vec3 position(x, y, z); + glm::vec3 normal = glm::normalize(position - center); + + // Use orange color for torus + glm::vec3 color(1.0f, 0.5f, 0.0f); + + shape.vertices.push_back({{x, y, z}, normal, color}); + } + } + + // Generate indices + for (int i = 0; i < majorSegments; ++i) { + for (int j = 0; j < minorSegments; ++j) { + int current = i * (minorSegments + 1) + j; + int next = ((i + 1) % (majorSegments + 1)) * (minorSegments + 1) + j; + + // First triangle + shape.indices.push_back(current); + shape.indices.push_back(next); + shape.indices.push_back(current + 1); + + // Second triangle + shape.indices.push_back(next); + shape.indices.push_back(next + 1); + shape.indices.push_back(current + 1); + } + } + + return shape; + } +}; + +// Simple Scene Object for basic hierarchy +struct SceneObject { + std::string name; + std::string type; + std::string subtype; // For procedural shapes (Cube, Sphere, etc.) + bool visible = true; + + // Simple geometry data (for procedural shapes) + std::vector vertices; + std::vector indices; + glm::vec3 position = {0.0f, 0.0f, 0.0f}; // Place objects at world origin + glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; + glm::vec3 scale = {1.0f, 1.0f, 1.0f}; // Default unit scale + + // Procedural shape parameters (for real-time editing) + struct ProceduralParams { + // Cube parameters + float cubeWidth = 2.0f; + float cubeHeight = 2.0f; + float cubeDepth = 2.0f; + int cubeSubdivisions = 1; + + // Sphere parameters + float sphereRadius = 1.0f; + int sphereSegments = 16; + + // Cylinder parameters + float cylinderRadius = 1.0f; + float cylinderHeight = 2.0f; + int cylinderSegments = 16; + + // Cone parameters + float coneRadius = 1.0f; + float coneHeight = 2.0f; + int coneSegments = 16; + + // Plane parameters + float planeWidth = 2.0f; + float planeHeight = 2.0f; + int planeSubdivisions = 1; + + // Torus parameters + float torusMajorRadius = 1.0f; + float torusMinorRadius = 0.3f; + int torusMajorSegments = 16; + int torusMinorSegments = 8; + } proceduralParams; + + // Persistent Vulkan buffers (created once, used multiple times) + vks::Buffer vertexBuffer; + vks::Buffer indexBuffer; + bool buffersCreated = false; + + SceneObject(const std::string& objName, const std::string& objType = "Object") + : name(objName), type(objType) {} + + // Cleanup buffers when object is destroyed + void destroyBuffers(vks::VulkanDevice* device) { + if (buffersCreated) { + vertexBuffer.destroy(); + indexBuffer.destroy(); + buffersCreated = false; + } + } + + // Calculate bounding box center for camera focus + glm::vec3 getBoundingBoxCenter() const { + if (vertices.empty()) return position; + + glm::vec3 minPos = vertices[0].position; + glm::vec3 maxPos = vertices[0].position; + + for (const auto& vertex : vertices) { + minPos = glm::min(minPos, vertex.position); + maxPos = glm::max(maxPos, vertex.position); + } + + // Apply object transformation + glm::vec3 center = (minPos + maxPos) * 0.5f; + center = position + center * scale; // Simple transform (rotation not included for simplicity) + + return center; + } + + // Calculate bounding radius for camera focus + float getBoundingRadius() const { + if (vertices.empty()) return 1.0f; + + glm::vec3 center = getBoundingBoxCenter(); + float maxRadius = 0.0f; + + for (const auto& vertex : vertices) { + glm::vec3 transformedPos = position + vertex.position * scale; + float distance = glm::length(transformedPos - center); + maxRadius = std::max(maxRadius, distance); + } + + return std::max(maxRadius, 0.5f); // Minimum radius of 0.5 + } + + // Regenerate geometry based on current procedural parameters + void regenerateGeometry() { + if (type != "Procedural") return; + + ProceduralShape shape; + + if (subtype == "Cube") { + shape = ProceduralGeometry::generateCube(proceduralParams.cubeWidth, + proceduralParams.cubeHeight, + proceduralParams.cubeDepth); + } else if (subtype == "Sphere") { + shape = ProceduralGeometry::generateSphere(proceduralParams.sphereRadius, + proceduralParams.sphereSegments); + } else if (subtype == "Cylinder") { + shape = ProceduralGeometry::generateCylinder(proceduralParams.cylinderRadius, + proceduralParams.cylinderHeight, + proceduralParams.cylinderSegments); + } else if (subtype == "Cone") { + shape = ProceduralGeometry::generateCone(proceduralParams.coneRadius, + proceduralParams.coneHeight, + proceduralParams.coneSegments); + } else if (subtype == "Plane") { + shape = ProceduralGeometry::generatePlane(proceduralParams.planeWidth, + proceduralParams.planeHeight, + proceduralParams.planeSubdivisions); + } else if (subtype == "Torus") { + shape = ProceduralGeometry::generateTorus(proceduralParams.torusMajorRadius, + proceduralParams.torusMinorRadius, + proceduralParams.torusMajorSegments, + proceduralParams.torusMinorSegments); + } + + // Update geometry data + vertices = shape.vertices; + indices = shape.indices; + + // Destroy existing buffers if they exist (to handle size changes) + if (buffersCreated) { + vertexBuffer.destroy(); + indexBuffer.destroy(); + } + + // Mark buffers as needing recreation + buffersCreated = false; + } +}; + +// Simple scene management +struct SimpleSceneManager { + std::vector objects; + int selectedIndex = -1; + vks::VulkanDevice* device = nullptr; + + void addObject(const std::string& name, const std::string& type = "Procedural") { + objects.emplace_back(name, type); + selectedIndex = static_cast(objects.size() - 1); + } + + void addProceduralShape(const std::string& shapeName, const std::string& shapeType) { + std::string fullName = shapeName + " " + std::to_string(getObjectCount() + 1); + objects.emplace_back(fullName, "Procedural"); + + // Generate actual geometry based on shape type + SceneObject& newObj = objects.back(); + newObj.subtype = shapeType; // Set the shape subtype (Cube, Sphere, etc.) + + // Place all objects at world origin (0,0,0) + // Users can move them via transform controls in Inspector + newObj.position.x = 0.0f; + newObj.position.y = 0.0f; + newObj.position.z = 0.0f; + + // Initialize procedural parameters based on shape type (use regenerateGeometry to create initial geometry) + newObj.regenerateGeometry(); + + // Create Vulkan buffers for the geometry (delayed until first render) + // createBuffersForObject(newObj); + + selectedIndex = static_cast(objects.size() - 1); + } + + void createBuffersForObject(SceneObject& obj) { + // Enhanced validation + if (!device) { + std::cout << "Error: Device not available for buffer creation" << std::endl; + return; + } + + if (obj.vertices.empty() || obj.indices.empty()) { + std::cout << "Error: Cannot create buffers for " << obj.name << " - geometry data is empty" << std::endl; + return; + } + + if (obj.buffersCreated) { + std::cout << "Info: Buffers already created for " << obj.name << std::endl; + return; + } + + // Validate geometry data size + if (obj.vertices.size() > 1000000 || obj.indices.size() > 3000000) { + std::cout << "Warning: Large geometry for " << obj.name << " - vertices: " << obj.vertices.size() << ", indices: " << obj.indices.size() << std::endl; + } + + try { + std::cout << "Creating buffers for " << obj.name << " with " << obj.vertices.size() << " vertices and " << obj.indices.size() << " indices" << std::endl; + + // Create vertex buffer + VkDeviceSize vertexBufferSize = obj.vertices.size() * sizeof(ProceduralVertex); + if (vertexBufferSize == 0) { + std::cout << "Error: Zero vertex buffer size for " << obj.name << std::endl; + return; + } + + VkResult result = device->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &obj.vertexBuffer, + vertexBufferSize, + (void*)obj.vertices.data() + ); + + if (result != VK_SUCCESS) { + std::cout << "Failed to create vertex buffer for " << obj.name << " - VkResult: " << result << std::endl; + return; + } + + // Validate vertex buffer creation + if (obj.vertexBuffer.buffer == VK_NULL_HANDLE) { + std::cout << "Error: Vertex buffer handle is null for " << obj.name << std::endl; + return; + } + + // Create index buffer + VkDeviceSize indexBufferSize = obj.indices.size() * sizeof(uint32_t); + if (indexBufferSize == 0) { + std::cout << "Error: Zero index buffer size for " << obj.name << std::endl; + obj.vertexBuffer.destroy(); + return; + } + + result = device->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &obj.indexBuffer, + indexBufferSize, + (void*)obj.indices.data() + ); + + if (result != VK_SUCCESS) { + std::cout << "Failed to create index buffer for " << obj.name << " - VkResult: " << result << std::endl; + obj.vertexBuffer.destroy(); + return; + } + + // Validate index buffer creation + if (obj.indexBuffer.buffer == VK_NULL_HANDLE) { + std::cout << "Error: Index buffer handle is null for " << obj.name << std::endl; + obj.vertexBuffer.destroy(); + return; + } + + obj.buffersCreated = true; + std::cout << "Successfully created buffers for " << obj.name << " (vertex: " << vertexBufferSize << " bytes, index: " << indexBufferSize << " bytes)" << std::endl; + } + catch (const std::exception& e) { + std::cout << "Exception creating buffers for " << obj.name << ": " << e.what() << std::endl; + obj.buffersCreated = false; + // Clean up any partially created buffers + if (obj.vertexBuffer.buffer != VK_NULL_HANDLE) { + obj.vertexBuffer.destroy(); + } + if (obj.indexBuffer.buffer != VK_NULL_HANDLE) { + obj.indexBuffer.destroy(); + } + } + } + + void removeObject(int index) { + if (index >= 0 && index < objects.size()) { + // Clean up buffers before removing object + objects[index].destroyBuffers(device); + objects.erase(objects.begin() + index); + if (selectedIndex >= objects.size()) { + selectedIndex = static_cast(objects.size() - 1); + } + } + } + + void clearScene() { + // Clean up all buffers before clearing + for (auto& obj : objects) { + obj.destroyBuffers(device); + } + objects.clear(); + selectedIndex = -1; + } + + int getObjectCount() const { + return static_cast(objects.size()); + } + + // Selection management + void setSelectedIndex(int index) { + if (index >= -1 && index < static_cast(objects.size())) { + selectedIndex = index; + } + } + + int getSelectedIndex() const { + return selectedIndex; + } + + SceneObject* getSelectedObject() { + if (selectedIndex >= 0 && selectedIndex < static_cast(objects.size())) { + return &objects[selectedIndex]; + } + return nullptr; + } + + bool hasSelection() const { + return selectedIndex >= 0 && selectedIndex < static_cast(objects.size()); + } + + void clearSelection() { + selectedIndex = -1; + } +} sceneManager; // Options and values to display/toggle from the UI struct UISettings { - bool displayModels = true; - bool displayLogos = true; - bool displayBackground = true; + bool displayModels = false; + bool displayBackground = false; bool animateLight = false; float lightSpeed = 0.25f; std::array frameTimes{}; float frameTimeMin = 9999.0f, frameTimeMax = 0.0f; float lightTimer = 0.0f; + bool showGrid = true; + float gridSize = 10.0f; + int gridDivisions = 10; + + // Panel visibility settings (for Windows menu) + bool showViewportPanel = true; + bool showInspectorPanel = true; + bool showSceneHierarchyPanel = true; + bool showAssetBrowserPanel = true; + bool showConsolePanel = false; // Hidden by default } uiSettings; // ---------------------------------------------------------------------------- @@ -133,11 +1110,47 @@ public: case 3: ImGui::StyleColorsLight(); break; + case 4: // Blue theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.3f, 0.8f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.4f, 1.0f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.2f, 0.6f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.3f, 0.7f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.05f, 0.15f, 0.9f); + break; + } + case 5: // Green theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.6f, 0.2f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.8f, 0.3f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.4f, 0.1f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.5f, 0.2f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.15f, 0.05f, 0.9f); + break; + } + case 6: // Purple theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.5f, 0.0f, 0.8f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.7f, 0.0f, 1.0f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.3f, 0.0f, 0.6f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.4f, 0.0f, 0.7f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1f, 0.05f, 0.15f, 0.9f); + break; + } } } // Initialize all Vulkan resources used by the ui - void initResources(VkRenderPass renderPass, VkQueue copyQueue, const std::string& shadersPath) + void initResources(VkRenderPass renderPass, VkQueue copyQueue, const std::string& shadersPath, VkFormat colorFormat = VK_FORMAT_UNDEFINED, VkFormat depthFormat = VK_FORMAT_UNDEFINED) { ImGuiIO& io = ImGui::GetIO(); @@ -337,7 +1350,16 @@ public: std::array shaderStages{}; - VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); + // Create pipeline - use dynamic rendering if no render pass provided + VkGraphicsPipelineCreateInfo pipelineCreateInfo; + if (renderPass == VK_NULL_HANDLE) { + // Dynamic rendering + pipelineCreateInfo = vks::initializers::pipelineCreateInfo(); + pipelineCreateInfo.layout = pipelineLayout; + } else { + // Traditional render pass + pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); + } pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; @@ -366,17 +1388,102 @@ public: pipelineCreateInfo.pVertexInputState = &vertexInputState; + // Dynamic rendering create info for ImGui pipeline (only if using dynamic rendering) + VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo{}; + if (renderPass == VK_NULL_HANDLE && colorFormat != VK_FORMAT_UNDEFINED) { + pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; + pipelineRenderingCreateInfo.colorAttachmentCount = 1; + pipelineRenderingCreateInfo.pColorAttachmentFormats = &colorFormat; + pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat; + pipelineRenderingCreateInfo.stencilAttachmentFormat = depthFormat; + // Chain into the pipeline create info + pipelineCreateInfo.pNext = &pipelineRenderingCreateInfo; + } + 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(); + // Menu Bar + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("New Scene", "Ctrl+N")) { + // Clear scene + sceneManager.clearScene(); + } + if (ImGui::MenuItem("Open Scene", "Ctrl+O")) { + // TODO: Implement scene loading + } + if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { + // TODO: Implement scene saving + } + ImGui::Separator(); + if (ImGui::MenuItem("Exit", "Alt+F4")) { + example->prepared = false; + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Preferences")) + { + if (ImGui::BeginMenu("UI Theme")) + { + if (ImGui::MenuItem("Vulkan Red", nullptr, selectedStyle == 0)) { setStyle(0); selectedStyle = 0; } + if (ImGui::MenuItem("Classic", nullptr, selectedStyle == 1)) { setStyle(1); selectedStyle = 1; } + if (ImGui::MenuItem("Dark", nullptr, selectedStyle == 2)) { setStyle(2); selectedStyle = 2; } + if (ImGui::MenuItem("Light", nullptr, selectedStyle == 3)) { setStyle(3); selectedStyle = 3; } + if (ImGui::MenuItem("Blue", nullptr, selectedStyle == 4)) { setStyle(4); selectedStyle = 4; } + if (ImGui::MenuItem("Green", nullptr, selectedStyle == 5)) { setStyle(5); selectedStyle = 5; } + if (ImGui::MenuItem("Purple", nullptr, selectedStyle == 6)) { setStyle(6); selectedStyle = 6; } + ImGui::EndMenu(); + } + ImGui::Separator(); + // Viewport settings moved to dedicated Viewport panel + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Windows")) + { + ImGui::MenuItem("Scene Hierarchy", nullptr, &uiSettings.showSceneHierarchyPanel); + ImGui::MenuItem("Inspector", nullptr, &uiSettings.showInspectorPanel); + ImGui::MenuItem("Viewport", nullptr, &uiSettings.showViewportPanel); + ImGui::MenuItem("Asset Browser", nullptr, &uiSettings.showAssetBrowserPanel); + ImGui::MenuItem("Console", nullptr, &uiSettings.showConsolePanel); + ImGui::Separator(); + if (ImGui::MenuItem("Show All Panels")) { + uiSettings.showSceneHierarchyPanel = true; + uiSettings.showInspectorPanel = true; + uiSettings.showViewportPanel = true; + uiSettings.showAssetBrowserPanel = true; + uiSettings.showConsolePanel = true; + } + if (ImGui::MenuItem("Hide All Panels")) { + uiSettings.showSceneHierarchyPanel = false; + uiSettings.showInspectorPanel = false; + uiSettings.showViewportPanel = false; + uiSettings.showAssetBrowserPanel = false; + uiSettings.showConsolePanel = false; + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem("About")) { + // TODO: Show about dialog + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + // Init imGui windows and elements // Debug window @@ -404,29 +1511,462 @@ public: 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::Text("Orbit Camera"); + ImGui::Text("Enhanced camera system active"); + ImGui::Separator(); + ImGui::Text("Controls:"); + ImGui::BulletText("F - Focus on selection"); + ImGui::BulletText("Alt+LMB - Orbit"); + ImGui::BulletText("Alt+MMB - Pan"); + ImGui::BulletText("Alt+RMB - Zoom"); + ImGui::BulletText("Mouse Wheel - Zoom"); - // Example settings window - ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 360 * example->ui.scale), ImGuiSetCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 200 * example->ui.scale), 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::ShowStyleSelector("UI style"); - if (ImGui::Combo("UI style", &selectedStyle, "Vulkan\0Classic\0Dark\0Light\0")) { - setStyle(selectedStyle); + // Simple Scene Hierarchy Panel + if (uiSettings.showSceneHierarchyPanel) { + ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 360 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 400 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Scene Hierarchy", &uiSettings.showSceneHierarchyPanel); + + // Header with object count + ImGui::Text("Scene Objects: %d", sceneManager.getObjectCount()); + ImGui::Separator(); + + // Render simple object list + for (int i = 0; i < sceneManager.objects.size(); i++) { + const auto& obj = sceneManager.objects[i]; + bool isSelected = (sceneManager.selectedIndex == i); + + // Object icon based on type + const char* icon = "[P]"; // Default procedural icon + if (obj.type == "Model") icon = "[M]"; + + // Visibility toggle (use object name as unique ImGui ID) + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushID(obj.name.c_str()); // Use object name for unique ImGui ID + if (ImGui::SmallButton(obj.visible ? "V" : "H")) { + // Toggle visibility (note: const_cast needed for modification) + const_cast(obj).visible = !obj.visible; + } + ImGui::PopID(); + ImGui::PopStyleColor(); + ImGui::SameLine(); + + // Selectable object name with highlighting for selected objects + if (isSelected) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); // Gold color for selected + } + if (ImGui::Selectable((std::string(icon) + " " + obj.name).c_str(), isSelected)) { + sceneManager.setSelectedIndex(i); + std::cout << "Selected object: " << obj.name << std::endl; + } + if (isSelected) { + ImGui::PopStyleColor(); + } + + // Right-click context menu + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Delete", "Del")) { + sceneManager.removeObject(i); + ImGui::EndPopup(); + break; // Exit loop since we modified the vector + } + ImGui::EndPopup(); + } } + + // Show message if scene is empty + if (sceneManager.getObjectCount() == 0) { + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Scene is empty"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Add objects from Asset Browser"); + } + + ImGui::End(); + } // End Scene Hierarchy Panel visibility check + + // Inspector Panel + if (uiSettings.showInspectorPanel) { + ImGui::SetNextWindowPos(ImVec2(1180 * example->ui.scale, 20 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 700 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Inspector", &uiSettings.showInspectorPanel); + ImGui::Text("Object Properties:"); + ImGui::Separator(); + + if (sceneManager.selectedIndex >= 0 && sceneManager.selectedIndex < sceneManager.objects.size()) { + auto& selectedObj = sceneManager.objects[sceneManager.selectedIndex]; // Non-const for editing + + // Object Header with Name and Type + ImGui::Text("Selected: %s", selectedObj.name.c_str()); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "(%s)", selectedObj.type.c_str()); + ImGui::Separator(); + + // Transform Section (collapsible like legacy 3D engine) + if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { + // Position Vector3 control + float pos[3] = { selectedObj.position.x, selectedObj.position.y, selectedObj.position.z }; + if (ImGui::DragFloat3("Position", pos, 0.1f)) { + selectedObj.position = glm::vec3(pos[0], pos[1], pos[2]); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Reset##pos")) { + selectedObj.position = glm::vec3(0.0f, 0.0f, 0.0f); + } + + // Rotation Vector3 control (degrees) + float rot[3] = { selectedObj.rotation.x, selectedObj.rotation.y, selectedObj.rotation.z }; + if (ImGui::DragFloat3("Rotation", rot, 1.0f, -360.0f, 360.0f)) { + selectedObj.rotation = glm::vec3(rot[0], rot[1], rot[2]); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Reset##rot")) { + selectedObj.rotation = glm::vec3(0.0f, 0.0f, 0.0f); + } + + // Scale Vector3 control + float scale[3] = { selectedObj.scale.x, selectedObj.scale.y, selectedObj.scale.z }; + if (ImGui::DragFloat3("Scale", scale, 0.01f, 0.01f, 10.0f)) { + selectedObj.scale = glm::vec3(scale[0], scale[1], scale[2]); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Reset##scale")) { + selectedObj.scale = glm::vec3(1.0f, 1.0f, 1.0f); // Default scale is 1.0 + } + + // Transform utilities + ImGui::Spacing(); + if (ImGui::Button("Reset All Transform")) { + selectedObj.position = glm::vec3(0.0f, 0.0f, 0.0f); + selectedObj.rotation = glm::vec3(0.0f, 0.0f, 0.0f); + selectedObj.scale = glm::vec3(1.0f, 1.0f, 1.0f); + } + } + + // Object Properties Section + if (ImGui::CollapsingHeader("Object Properties", ImGuiTreeNodeFlags_DefaultOpen)) { + // Visibility toggle + bool visible = selectedObj.visible; + if (ImGui::Checkbox("Visible", &visible)) { + selectedObj.visible = visible; + } + + // Object name editing + static char nameBuffer[256]; + strncpy_s(nameBuffer, selectedObj.name.c_str(), sizeof(nameBuffer) - 1); + if (ImGui::InputText("Name", nameBuffer, sizeof(nameBuffer))) { + selectedObj.name = std::string(nameBuffer); + } + } + + // Procedural Parameters Section (for procedural shapes) + if (selectedObj.type == "Procedural") { + if (ImGui::CollapsingHeader("Procedural Parameters", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("Shape Type: %s", selectedObj.subtype.c_str()); + + // Dynamic parameter controls based on shape type + bool parametersChanged = false; + + if (selectedObj.subtype == "Cube") { + ImGui::Text("Cube Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Width", &selectedObj.proceduralParams.cubeWidth, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Height", &selectedObj.proceduralParams.cubeHeight, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Depth", &selectedObj.proceduralParams.cubeDepth, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Subdivisions", &selectedObj.proceduralParams.cubeSubdivisions, 1, 10)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Cube Parameters")) { + selectedObj.proceduralParams.cubeWidth = 2.0f; + selectedObj.proceduralParams.cubeHeight = 2.0f; + selectedObj.proceduralParams.cubeDepth = 2.0f; + selectedObj.proceduralParams.cubeSubdivisions = 1; + parametersChanged = true; + } + + } else if (selectedObj.subtype == "Sphere") { + ImGui::Text("Sphere Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Radius", &selectedObj.proceduralParams.sphereRadius, 0.05f, 0.1f, 5.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Segments", &selectedObj.proceduralParams.sphereSegments, 4, 64)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Sphere Parameters")) { + selectedObj.proceduralParams.sphereRadius = 1.0f; + selectedObj.proceduralParams.sphereSegments = 16; + parametersChanged = true; + } + + } else if (selectedObj.subtype == "Cylinder") { + ImGui::Text("Cylinder Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Radius", &selectedObj.proceduralParams.cylinderRadius, 0.05f, 0.1f, 5.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Height", &selectedObj.proceduralParams.cylinderHeight, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Segments", &selectedObj.proceduralParams.cylinderSegments, 3, 64)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Cylinder Parameters")) { + selectedObj.proceduralParams.cylinderRadius = 1.0f; + selectedObj.proceduralParams.cylinderHeight = 2.0f; + selectedObj.proceduralParams.cylinderSegments = 16; + parametersChanged = true; + } + + } else if (selectedObj.subtype == "Cone") { + ImGui::Text("Cone Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Radius", &selectedObj.proceduralParams.coneRadius, 0.05f, 0.1f, 5.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Height", &selectedObj.proceduralParams.coneHeight, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Segments", &selectedObj.proceduralParams.coneSegments, 3, 64)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Cone Parameters")) { + selectedObj.proceduralParams.coneRadius = 1.0f; + selectedObj.proceduralParams.coneHeight = 2.0f; + selectedObj.proceduralParams.coneSegments = 16; + parametersChanged = true; + } + + } else if (selectedObj.subtype == "Plane") { + ImGui::Text("Plane Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Width", &selectedObj.proceduralParams.planeWidth, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Height", &selectedObj.proceduralParams.planeHeight, 0.1f, 0.1f, 10.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Subdivisions", &selectedObj.proceduralParams.planeSubdivisions, 1, 20)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Plane Parameters")) { + selectedObj.proceduralParams.planeWidth = 2.0f; + selectedObj.proceduralParams.planeHeight = 2.0f; + selectedObj.proceduralParams.planeSubdivisions = 1; + parametersChanged = true; + } + + } else if (selectedObj.subtype == "Torus") { + ImGui::Text("Torus Parameters:"); + ImGui::Separator(); + + if (ImGui::DragFloat("Major Radius", &selectedObj.proceduralParams.torusMajorRadius, 0.05f, 0.2f, 5.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::DragFloat("Minor Radius", &selectedObj.proceduralParams.torusMinorRadius, 0.02f, 0.05f, 2.0f, "%.2f")) { + parametersChanged = true; + } + if (ImGui::SliderInt("Major Segments", &selectedObj.proceduralParams.torusMajorSegments, 3, 64)) { + parametersChanged = true; + } + if (ImGui::SliderInt("Minor Segments", &selectedObj.proceduralParams.torusMinorSegments, 3, 32)) { + parametersChanged = true; + } + + if (ImGui::Button("Reset Torus Parameters")) { + selectedObj.proceduralParams.torusMajorRadius = 1.0f; + selectedObj.proceduralParams.torusMinorRadius = 0.3f; + selectedObj.proceduralParams.torusMajorSegments = 16; + selectedObj.proceduralParams.torusMinorSegments = 8; + parametersChanged = true; + } + } + + // Regenerate geometry if parameters changed + if (parametersChanged) { + selectedObj.regenerateGeometry(); + std::cout << "Regenerated geometry for " << selectedObj.name << " (" << selectedObj.subtype << ")" << std::endl; + } + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "✓ Real-time parameter editing enabled"); + } + } + } else { + ImGui::Text("Selected: None"); + ImGui::Text("Select an object in the Scene Hierarchy"); + ImGui::Separator(); + } + + + + // UI Style selection is available in Preferences > UI Theme menu ImGui::End(); + } // End Inspector Panel visibility check + + // Viewport Panel - Settings for 3D viewport rendering + if (uiSettings.showViewportPanel) { + ImGui::SetNextWindowPos(ImVec2(660 * example->ui.scale, 350 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(320 * example->ui.scale, 280 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Viewport", &uiSettings.showViewportPanel); + + ImGui::Text("3D Viewport Settings:"); + ImGui::Separator(); + + // Render Mode Settings + if (ImGui::CollapsingHeader("Render Mode", ImGuiTreeNodeFlags_DefaultOpen)) + { + // TODO: Add render mode dropdown (Solid, X-Ray, etc.) when renderer supports it + ImGui::Text("Mode: Solid"); // Placeholder until render modes implemented + } + + // Grid Settings (moved from Inspector) + if (ImGui::CollapsingHeader("Grid Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Show Grid", &uiSettings.showGrid); + if (uiSettings.showGrid) { + ImGui::DragFloat("Grid Size", &uiSettings.gridSize, 0.5f, 1.0f, 50.0f); + ImGui::DragInt("Grid Divisions", &uiSettings.gridDivisions, 1, 2, 50); + + // Grid color picker (placeholder - needs renderer integration) + static float gridColor[3] = { 0.5f, 0.5f, 0.5f }; + ImGui::ColorEdit3("Grid Color", gridColor); + } + + // Note about grid rendering implementation + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.3f, 1.0f), "Note: Grid rendering requires Vulkan renderer integration."); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Settings are saved but grid is not yet rendered in viewport."); + } + + // Camera Settings + if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("Current Camera: Orbit Camera"); + ImGui::Text("Controls:"); + ImGui::BulletText("Alt + LMB: Orbit"); + ImGui::BulletText("Alt + MMB: Pan"); + ImGui::BulletText("Alt + RMB: Zoom"); + ImGui::BulletText("Mouse Wheel: Zoom"); + ImGui::BulletText("F Key: Focus Selection"); + } + + // Selection Tools + if (ImGui::CollapsingHeader("Selection Tools")) + { + ImGui::Text("Active Tool: Rectangle Select"); + // TODO: Add gizmo tool selection when gizmos implemented + } + + // Lighting Settings (moved from Inspector panel) + if (ImGui::CollapsingHeader("Lighting", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Animate light", &uiSettings.animateLight); + ImGui::SliderFloat("Light speed", &uiSettings.lightSpeed, 0.1f, 1.0f); + } + + ImGui::End(); + } // End Viewport Panel visibility check + + // Asset Browser Panel + if (uiSettings.showAssetBrowserPanel) { + ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600 * example->ui.scale, 220 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Asset Browser", &uiSettings.showAssetBrowserPanel); + ImGui::Text("Project Assets:"); + ImGui::Separator(); + + if (ImGui::CollapsingHeader("Procedural Shapes", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("Basic Geometric Shapes:"); + ImGui::Separator(); + + // Create buttons for each shape type - First row + if (ImGui::Button("Cube")) { + sceneManager.addProceduralShape("Cube", "Cube"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cube to the scene"); + ImGui::SameLine(); + if (ImGui::Button("Sphere")) { + sceneManager.addProceduralShape("Sphere", "Sphere"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural sphere to the scene"); + ImGui::SameLine(); + if (ImGui::Button("Plane")) { + sceneManager.addProceduralShape("Plane", "Plane"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural plane to the scene"); + + // Second row + if (ImGui::Button("Cone")) { + sceneManager.addProceduralShape("Cone", "Cone"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cone to the scene"); + ImGui::SameLine(); + if (ImGui::Button("Cylinder")) { + sceneManager.addProceduralShape("Cylinder", "Cylinder"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cylinder to the scene"); + ImGui::SameLine(); + if (ImGui::Button("Torus")) { + sceneManager.addProceduralShape("Torus", "Torus"); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural torus to the scene"); + } + + if (ImGui::CollapsingHeader("Models")) + { + ImGui::Text("• MobulaBirostris.gltf"); + ImGui::Text("• PolarBear.gltf"); + } + if (ImGui::CollapsingHeader("Textures")) + { + ImGui::Text("• Loading textures from glTF files..."); + } + if (ImGui::CollapsingHeader("Materials")) + { + ImGui::Text("• Default Vulkan Materials"); + } + ImGui::End(); + } // End Asset Browser Panel visibility check + + // Console Panel + if (uiSettings.showConsolePanel) { + ImGui::SetNextWindowPos(ImVec2(640 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(840 * example->ui.scale, 200 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Console", &uiSettings.showConsolePanel); + ImGui::Text("System Console:"); + ImGui::Separator(); + ImGui::Text("[INFO] ProceduralEngine - Vulkan Renderer initialized"); + ImGui::Text("[INFO] Scene loaded successfully - Ready for procedural generation"); + ImGui::Separator(); + static char inputBuf[256] = ""; + if (ImGui::InputText("Command", inputBuf, sizeof(inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) + { + // Process command + inputBuf[0] = '\0'; + } + ImGui::End(); + } // End Console Panel visibility check //SRS - ShowDemoWindow() sets its own initial position and size, cannot override here - ImGui::ShowDemoWindow(); + // ImGui::ShowDemoWindow(); // Render to generate draw buffers ImGui::Render(); @@ -544,6 +2084,12 @@ public: class VulkanExample : public VulkanExampleBase { public: + // Dynamic rendering function pointers + PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; + PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; + + VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; + ImGUI *imGui = nullptr; struct Models { @@ -552,11 +2098,12 @@ public: vkglTF::Model background; } models; + vks::Buffer uniformBufferVS; struct UBOVS { glm::mat4 projection; - glm::mat4 modelview; + glm::mat4 model; glm::vec4 lightPos; } uboVS; @@ -565,17 +2112,48 @@ public: VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSet descriptorSet; - VulkanExample() : VulkanExampleBase() + // Procedural shapes pipeline + VkPipelineLayout proceduralPipelineLayout; + VkPipeline proceduralPipeline; + VkDescriptorSetLayout proceduralDescriptorSetLayout; + VkDescriptorSet proceduralDescriptorSet; + + // Enhanced orbit camera + OrbitCamera orbitCamera; + + // Mouse interaction state + bool mouseInteracting = false; + double lastMouseX = 0.0; + double lastMouseY = 0.0; + + VulkanExample() : VulkanExampleBase(), orbitCamera(glm::vec3(5.0f, 3.0f, 5.0f), glm::vec3(0.0f, 0.0f, -5.0f)) { - title = "User interfaces with ImGui"; + title = "ProceduralEngine - Vulkan 3D Viewport"; camera.type = Camera::CameraType::lookat; - camera.setPosition(glm::vec3(0.0f, 0.0f, -4.8f)); + camera.setPosition(glm::vec3(0.0f, 0.0f, -8.0f)); camera.setRotation(glm::vec3(4.5f, -380.0f, 0.0f)); camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f); + // Configure orbit camera with legacy-compatible settings + orbitCamera.SetSensitivity(0.5f, 0.003f, 0.1f); // Match legacy sensitivity values + orbitCamera.SetSmoothingFactor(0.15f); // Match legacy smoothing + //SRS - Enable VK_KHR_get_physical_device_properties2 to retrieve device driver information for display enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + // Enable dynamic rendering extensions + enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + + // Enable dynamic rendering features + enabledDynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + enabledDynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE; + + deviceCreatepNextChain = &enabledDynamicRenderingFeaturesKHR; + // Don't use the ImGui overlay of the base framework in this sample settings.overlay = false; } @@ -586,39 +2164,133 @@ public: vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + vkDestroyPipeline(device, proceduralPipeline, nullptr); + vkDestroyPipelineLayout(device, proceduralPipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, proceduralDescriptorSetLayout, nullptr); + uniformBufferVS.destroy(); delete imGui; } + 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 + } + + void renderProceduralShapes(VkCommandBuffer commandBuffer) { + // Render procedural shapes using persistent buffers + static int frameCount = 0; + frameCount++; + + for (auto& obj : sceneManager.objects) { + if (obj.type == "Procedural" && obj.visible && !obj.indices.empty() && obj.buffersCreated) { + // Calculate model matrix for this object + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, obj.position); + model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); + model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); + model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); + model = glm::scale(model, obj.scale); + + // Update uniform buffer with object's model matrix while preserving camera matrices + UBOVS tempUBO; + tempUBO.projection = orbitCamera.GetProjectionMatrix((float)width / (float)height, 0.1f, 256.0f); // Use OrbitCamera projection + tempUBO.model = orbitCamera.GetViewMatrix() * model; // Combine view and model matrices + tempUBO.lightPos = uboVS.lightPos; // Preserve light position + + VK_CHECK_RESULT(uniformBufferVS.map()); + memcpy(uniformBufferVS.mapped, &tempUBO, sizeof(tempUBO)); + uniformBufferVS.unmap(); + + // Validate buffers before binding + if (obj.vertexBuffer.buffer == VK_NULL_HANDLE || obj.indexBuffer.buffer == VK_NULL_HANDLE) { + std::cout << "Warning: Invalid buffer handles for " << obj.name << std::endl; + continue; + } + + // Bind the vertex and index buffers for this object + VkBuffer vertexBuffers[] = { obj.vertexBuffer.buffer }; + VkDeviceSize offsets[] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); + vkCmdBindIndexBuffer(commandBuffer, obj.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); + + // Draw the object + vkCmdDrawIndexed(commandBuffer, static_cast(obj.indices.size()), 1, 0, 0, 0); + } + } + } + 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); + // Dynamic rendering requires manual image layout transitions + // Prepare color attachment for rendering + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + swapChain.images[i], + 0, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); + + // Prepare depth attachment for rendering + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + depthStencil.image, + 0, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_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 }); + + // Set up rendering attachments for dynamic rendering + VkRenderingAttachmentInfoKHR colorAttachment{}; + colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + colorAttachment.imageView = swapChain.imageViews[i]; + colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.clearValue.color = { 0.2f, 0.2f, 0.2f, 1.0f }; + + VkRenderingAttachmentInfoKHR depthStencilAttachment{}; + depthStencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + depthStencilAttachment.imageView = depthStencil.view; + depthStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + 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); @@ -626,11 +2298,10 @@ public: VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - // Render scene + // Render vkglTF models with original pipeline 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]); } @@ -639,16 +2310,32 @@ public: models.models.draw(drawCmdBuffers[i]); } - if (uiSettings.displayLogos) { - models.logos.draw(drawCmdBuffers[i]); - } + // Switch to procedural pipeline for procedural shapes + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, proceduralPipelineLayout, 0, 1, &proceduralDescriptorSet, 0, nullptr); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, proceduralPipeline); + + // Render procedural shapes + renderProceduralShapes(drawCmdBuffers[i]); // Render imGui if (ui.visible) { imGui->drawFrame(drawCmdBuffers[i]); } - vkCmdEndRenderPass(drawCmdBuffers[i]); + // End dynamic rendering + vkCmdEndRenderingKHR(drawCmdBuffers[i]); + + // Prepare image for presentation + vks::tools::insertImageMemoryBarrier( + drawCmdBuffers[i], + swapChain.images[i], + 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])); } @@ -656,12 +2343,12 @@ public: void setupLayoutsAndDescriptors() { - // descriptor pool + // descriptor pool (increased to handle procedural pipeline too) std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 4); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); // Set layout @@ -683,6 +2370,20 @@ public: vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + + // Allocate procedural descriptor set (note: proceduralDescriptorSetLayout is created in prepareProceduralPipeline) + // This will be done after pipelines are prepared + } + + void setupProceduralDescriptorSet() + { + // Allocate procedural descriptor set + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &proceduralDescriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &proceduralDescriptorSet)); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(proceduralDescriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } void preparePipelines() @@ -700,7 +2401,9 @@ public: std::array shaderStages; - VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); + // Create pipeline without render pass for dynamic rendering + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(); + pipelineCI.layout = pipelineLayout; pipelineCI.pInputAssemblyState = &inputAssemblyState; pipelineCI.pRasterizationState = &rasterizationState; pipelineCI.pColorBlendState = &colorBlendState; @@ -712,9 +2415,86 @@ public: pipelineCI.pStages = shaderStages.data(); pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });; + // Dynamic rendering 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 create info + pipelineCI.pNext = &pipelineRenderingCreateInfo; + 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)); + + // Create procedural shapes pipeline + prepareProceduralPipeline(); + } + + void prepareProceduralPipeline() + { + // Descriptor set layout for procedural shapes (uniform buffer only) + VkDescriptorSetLayoutBinding layoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0); + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(&layoutBinding, 1); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &proceduralDescriptorSetLayout)); + + // Pipeline layout + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&proceduralDescriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &proceduralPipelineLayout)); + + // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + + // Vertex input for procedural shapes + VkVertexInputBindingDescription vertexInputBinding = vks::initializers::vertexInputBindingDescription(0, sizeof(ProceduralVertex), VK_VERTEX_INPUT_RATE_VERTEX); + std::vector vertexInputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Normal + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 6), // Color + }; + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = 1; + vertexInputState.pVertexBindingDescriptions = &vertexInputBinding; + vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + std::array shaderStages; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(); + pipelineCI.layout = proceduralPipelineLayout; + pipelineCI.pInputAssemblyState = &inputAssemblyState; + pipelineCI.pRasterizationState = &rasterizationState; + pipelineCI.pColorBlendState = &colorBlendState; + pipelineCI.pMultisampleState = &multisampleState; + pipelineCI.pViewportState = &viewportState; + pipelineCI.pDepthStencilState = &depthStencilState; + pipelineCI.pDynamicState = &dynamicState; + pipelineCI.pVertexInputState = &vertexInputState; + pipelineCI.stageCount = static_cast(shaderStages.size()); + pipelineCI.pStages = shaderStages.data(); + + // Dynamic rendering create info + 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; + pipelineCI.pNext = &pipelineRenderingCreateInfo; + + 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, &proceduralPipeline)); } // Prepare and initialize uniform buffer containing shader uniforms @@ -733,9 +2513,12 @@ public: void updateUniformBuffers() { - // Vertex shader - uboVS.projection = camera.matrices.perspective; - uboVS.modelview = camera.matrices.view * glm::mat4(1.0f); + // Update orbit camera + orbitCamera.Update(frameTimer); + + // Vertex shader - use OrbitCamera matrices + uboVS.projection = orbitCamera.GetProjectionMatrix((float)width / (float)height, 0.1f, 256.0f); + uboVS.model = orbitCamera.GetViewMatrix() * glm::mat4(1.0f); // Light source if (uiSettings.animateLight) { @@ -752,6 +2535,23 @@ public: void draw() { VulkanExampleBase::prepareFrame(); + + // Check if we need to create buffers for new objects + bool needsCommandBufferRebuild = false; + for (auto& obj : sceneManager.objects) { + if (obj.type == "Procedural" && !obj.buffersCreated && !obj.vertices.empty() && !obj.indices.empty()) { + // Wait for GPU to finish current operations before creating new buffers + vkDeviceWaitIdle(device); + sceneManager.createBuffersForObject(obj); + needsCommandBufferRebuild = true; + } + } + + // If new objects were added, ensure proper synchronization + if (needsCommandBufferRebuild) { + vkDeviceWaitIdle(device); + } + buildCommandBuffers(); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; @@ -762,25 +2562,40 @@ public: 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); + // Models available in assets but not auto-loaded + // models.models.loadFromFile(getAssetPath() + "models/MobulaBirostris.gltf", vulkanDevice, queue, glTFLoadingFlags); + // models.background.loadFromFile(getAssetPath() + "models/PolarBear.gltf", vulkanDevice, queue, glTFLoadingFlags); } void prepareImGui() { imGui = new ImGUI(this); imGui->init((float)width, (float)height); - imGui->initResources(renderPass, queue, getShadersPath()); + imGui->initResources(renderPass, queue, getShadersPath(), swapChain.colorFormat, depthFormat); } void prepare() { VulkanExampleBase::prepare(); + + // Get dynamic rendering function pointers + vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); + vkCmdEndRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR")); + + // Validate function pointers + if (!vkCmdBeginRenderingKHR || !vkCmdEndRenderingKHR) { + std::cout << "ERROR: Failed to load dynamic rendering function pointers" << std::endl; + exit(1); + } + + // Initialize scene manager with device pointer + sceneManager.device = vulkanDevice; + loadAssets(); prepareUniformBuffers(); setupLayoutsAndDescriptors(); preparePipelines(); + setupProceduralDescriptorSet(); prepareImGui(); buildCommandBuffers(); prepared = true; @@ -804,19 +2619,25 @@ public: io.MouseDown[1] = mouseState.buttons.right && ui.visible; io.MouseDown[2] = mouseState.buttons.middle && ui.visible; + // Handle mouse wheel for camera zoom + if (io.MouseWheel != 0.0f) { + orbitCamera.ZoomImmediate(-io.MouseWheel * 10.0f, frameTimer); + } + draw(); } - virtual void mouseMoved(double x, double y, bool &handled) - { - ImGuiIO& io = ImGui::GetIO(); - handled = io.WantCaptureMouse && ui.visible; - } // Input handling is platform specific, to show how it's basically done this sample implements it for Windows #if defined(_WIN32) virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ImGuiIO& io = ImGui::GetIO(); + + // Handle mouse wheel for ImGui (needed for camera zoom) + if (uMsg == WM_MOUSEWHEEL) { + io.MouseWheel += (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; + } + // Only react to keyboard input if ImGui is active if (io.WantCaptureKeyboard) { // Character input @@ -836,6 +2657,79 @@ public: } #endif + // Override keyPressed for F key focus functionality + virtual void keyPressed(uint32_t keyCode) override + { + // Handle F key for focus (immediate, no smoothing) + if (keyCode == KEY_F) { + if (sceneManager.hasSelection()) { + // Focus on selected object immediately + SceneObject* selectedObject = sceneManager.getSelectedObject(); + if (selectedObject) { + glm::vec3 objectCenter = selectedObject->getBoundingBoxCenter(); + float objectRadius = selectedObject->getBoundingRadius(); + orbitCamera.SetFocusToSelectionImmediate(objectCenter, objectRadius); + std::cout << "Focused camera on selected object: " << selectedObject->name << std::endl; + } + } else { + // No selection, focus on scene center immediately + orbitCamera.FrameAllImmediate(glm::vec3(0.0f, 0.0f, -5.0f), 3.0f); + std::cout << "Focused camera on scene center (no selection)" << std::endl; + } + } + + + // Call base implementation for other keys + VulkanExampleBase::keyPressed(keyCode); + } + + // Override mouseMoved for camera orbit/pan controls + virtual void mouseMoved(double x, double y, bool &handled) override + { + ImGuiIO& io = ImGui::GetIO(); + + // Calculate mouse delta + double deltaX = x - lastMouseX; + double deltaY = y - lastMouseY; + lastMouseX = x; + lastMouseY = y; + + // Handle industry-standard Maya/3ds Max style camera controls (Alt + mouse) + // Use Windows API directly for Alt key detection since ImGui's KeyAlt is not reliable in this Vulkan framework +#if defined(_WIN32) + bool altPressed = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; +#else + bool altPressed = io.KeyAlt; // Fallback for non-Windows platforms +#endif + + // Alt key detection is now working properly using Windows GetAsyncKeyState API + + if (altPressed && mouseState.buttons.left) { + // Alt + Left mouse: orbit around focus point + if (std::abs(deltaX) > 0.1 || std::abs(deltaY) > 0.1) { + orbitCamera.Orbit(deltaX * 0.5f, deltaY * 0.5f, frameTimer); + handled = true; + } + } else if (altPressed && mouseState.buttons.middle) { + // Alt + Middle mouse: pan viewport (immediate, no smoothing) + if (std::abs(deltaX) > 0.1 || std::abs(deltaY) > 0.1) { + orbitCamera.PanImmediate(-deltaX, deltaY, frameTimer); + handled = true; + } + } else if (altPressed && mouseState.buttons.right) { + // Alt + Right mouse: zoom (dolly camera) + if (std::abs(deltaY) > 0.1) { + orbitCamera.Zoom(deltaY * 0.01f, frameTimer); + handled = true; + } + } + + // Call base implementation if not handled + if (!handled) { + VulkanExampleBase::mouseMoved(x, y, handled); + } + } + }; VULKAN_EXAMPLE_MAIN() diff --git a/examples/imgui/main_backup.cpp b/examples/imgui/main_backup.cpp new file mode 100644 index 00000000..8475fcde --- /dev/null +++ b/examples/imgui/main_backup.cpp @@ -0,0 +1,1805 @@ +/* +* Vulkan Example - imGui (https://github.com/ocornut/imgui) +* +* Copyright (C) 2017-2025 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include +#include "vulkanexamplebase.h" +#include "VulkanglTFModel.h" +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// Forward declaration +class VulkanExample; + +// Procedural Geometry Generation +struct ProceduralVertex { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texCoord; +}; + +struct ProceduralShape { + std::vector vertices; + std::vector indices; + std::string name; + int type; // 0=cube, 1=sphere, 2=cylinder, 3=plane, 4=cone, 5=torus + + // Shape parameters + struct { + float width = 2.0f, height = 2.0f, depth = 2.0f; + int subdivisions = 1; + float radius = 1.0f; + int segments = 16; + float majorRadius = 1.0f, minorRadius = 0.3f; + } params; +}; + +class ProceduralGeometry { +public: + static ProceduralShape generateCube(float width = 4.0f, float height = 4.0f, float depth = 4.0f) { + ProceduralShape shape; + shape.name = "Cube"; + shape.type = 0; + shape.params.width = width; + shape.params.height = height; + shape.params.depth = depth; + + float w = width * 0.5f; + float h = height * 0.5f; + float d = depth * 0.5f; + + // Define 24 vertices (4 per face, 6 faces) + shape.vertices = { + // Front face + {{-w, -h, d}, {0, 0, 1}, {0, 0}}, {{ w, -h, d}, {0, 0, 1}, {1, 0}}, + {{ w, h, d}, {0, 0, 1}, {1, 1}}, {{-w, h, d}, {0, 0, 1}, {0, 1}}, + // Back face + {{ w, -h, -d}, {0, 0, -1}, {0, 0}}, {{-w, -h, -d}, {0, 0, -1}, {1, 0}}, + {{-w, h, -d}, {0, 0, -1}, {1, 1}}, {{ w, h, -d}, {0, 0, -1}, {0, 1}}, + // Left face + {{-w, -h, -d}, {-1, 0, 0}, {0, 0}}, {{-w, -h, d}, {-1, 0, 0}, {1, 0}}, + {{-w, h, d}, {-1, 0, 0}, {1, 1}}, {{-w, h, -d}, {-1, 0, 0}, {0, 1}}, + // Right face + {{ w, -h, d}, {1, 0, 0}, {0, 0}}, {{ w, -h, -d}, {1, 0, 0}, {1, 0}}, + {{ w, h, -d}, {1, 0, 0}, {1, 1}}, {{ w, h, d}, {1, 0, 0}, {0, 1}}, + // Top face + {{-w, h, d}, {0, 1, 0}, {0, 0}}, {{ w, h, d}, {0, 1, 0}, {1, 0}}, + {{ w, h, -d}, {0, 1, 0}, {1, 1}}, {{-w, h, -d}, {0, 1, 0}, {0, 1}}, + // Bottom face + {{-w, -h, -d}, {0, -1, 0}, {0, 0}}, {{ w, -h, -d}, {0, -1, 0}, {1, 0}}, + {{ w, -h, d}, {0, -1, 0}, {1, 1}}, {{-w, -h, d}, {0, -1, 0}, {0, 1}} + }; + + // Define indices for 12 triangles (2 per face) + shape.indices = { + 0,1,2, 0,2,3, // Front + 4,5,6, 4,6,7, // Back + 8,9,10, 8,10,11, // Left + 12,13,14, 12,14,15, // Right + 16,17,18, 16,18,19, // Top + 20,21,22, 20,22,23 // Bottom + }; + + return shape; + } + + static ProceduralShape generateSphere(float radius = 1.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Sphere"; + shape.type = 1; + shape.params.radius = radius; + shape.params.segments = segments; + + // Generate sphere vertices using spherical coordinates + for (int lat = 0; lat <= segments; ++lat) { + float theta = lat * M_PI / segments; + float sinTheta = sin(theta); + float cosTheta = cos(theta); + + for (int lon = 0; lon <= segments; ++lon) { + float phi = lon * 2 * M_PI / segments; + float sinPhi = sin(phi); + float cosPhi = cos(phi); + + glm::vec3 pos(radius * sinTheta * cosPhi, radius * cosTheta, radius * sinTheta * sinPhi); + glm::vec3 normal = glm::normalize(pos); + glm::vec2 texCoord((float)lon / segments, (float)lat / segments); + + shape.vertices.push_back({pos, normal, texCoord}); + } + } + + // Generate indices + for (int lat = 0; lat < segments; ++lat) { + for (int lon = 0; lon < segments; ++lon) { + int first = lat * (segments + 1) + lon; + int second = first + segments + 1; + + shape.indices.push_back(first); + shape.indices.push_back(second); + shape.indices.push_back(first + 1); + + shape.indices.push_back(second); + shape.indices.push_back(second + 1); + shape.indices.push_back(first + 1); + } + } + + return shape; + } + + static ProceduralShape generatePlane(float width = 2.0f, float height = 2.0f, int subdivisions = 1) { + ProceduralShape shape; + shape.name = "Plane"; + shape.type = 3; + shape.params.width = width; + shape.params.height = height; + shape.params.subdivisions = subdivisions; + + float w = width * 0.5f; + float h = height * 0.5f; + + // Simple quad for now + shape.vertices = { + {{-w, 0, -h}, {0, 1, 0}, {0, 0}}, + {{ w, 0, -h}, {0, 1, 0}, {1, 0}}, + {{ w, 0, h}, {0, 1, 0}, {1, 1}}, + {{-w, 0, h}, {0, 1, 0}, {0, 1}} + }; + + shape.indices = {0, 1, 2, 0, 2, 3}; + + return shape; + } + + static ProceduralShape generateGrid(float size = 10.0f, int divisions = 10) { + ProceduralShape shape; + shape.name = "Grid"; + shape.type = 6; // Grid type + shape.params.width = size; + shape.params.subdivisions = divisions; + + float step = size / divisions; + float halfSize = size * 0.5f; + + // Create grid lines + for (int i = 0; i <= divisions; ++i) { + float pos = -halfSize + i * step; + + // Horizontal lines + shape.vertices.push_back({{-halfSize, 0, pos}, {0, 1, 0}, {0, 0}}); + shape.vertices.push_back({{ halfSize, 0, pos}, {0, 1, 0}, {1, 0}}); + + // Vertical lines + shape.vertices.push_back({{pos, 0, -halfSize}, {0, 1, 0}, {0, 0}}); + shape.vertices.push_back({{pos, 0, halfSize}, {0, 1, 0}, {1, 0}}); + } + + // Generate indices for lines + for (int i = 0; i < (divisions + 1) * 4; i += 2) { + shape.indices.push_back(i); + shape.indices.push_back(i + 1); + } + + return shape; + } + + static ProceduralShape generateCone(float radius = 1.0f, float height = 2.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Cone"; + shape.type = 4; // Cone type + shape.params.radius = radius; + shape.params.height = height; + shape.params.segments = segments; + + // Add tip vertex + shape.vertices.push_back({{0, height * 0.5f, 0}, {0, 1, 0}, {0.5f, 1.0f}}); + + // Add center vertex for base + shape.vertices.push_back({{0, -height * 0.5f, 0}, {0, -1, 0}, {0.5f, 0.0f}}); + + // Generate base vertices + for (int i = 0; i <= segments; ++i) { + float angle = (float)i / segments * 2.0f * M_PI; + float x = cos(angle) * radius; + float z = sin(angle) * radius; + float u = (cos(angle) + 1.0f) * 0.5f; + float v = (sin(angle) + 1.0f) * 0.5f; + + // Base vertex + shape.vertices.push_back({{x, -height * 0.5f, z}, {0, -1, 0}, {u, v}}); + + // Side vertex (for side triangles) + glm::vec3 sideNormal = glm::normalize(glm::vec3(x, radius / height, z)); + shape.vertices.push_back({{x, -height * 0.5f, z}, sideNormal, {(float)i / segments, 0.0f}}); + } + + // Generate indices + // Side triangles (tip to base edge) + for (int i = 0; i < segments; ++i) { + int baseStart = 2 + segments + 1; + shape.indices.push_back(0); // tip + shape.indices.push_back(baseStart + (i + 1) * 2); + shape.indices.push_back(baseStart + i * 2); + } + + // Base triangles + for (int i = 0; i < segments; ++i) { + shape.indices.push_back(1); // center + shape.indices.push_back(2 + i); + shape.indices.push_back(2 + ((i + 1) % (segments + 1))); + } + + return shape; + } + + static ProceduralShape generateCylinder(float radius = 1.0f, float height = 2.0f, int segments = 16) { + ProceduralShape shape; + shape.name = "Cylinder"; + shape.type = 5; // Cylinder type + shape.params.radius = radius; + shape.params.height = height; + shape.params.segments = segments; + + float halfHeight = height * 0.5f; + + // Add center vertices for caps + shape.vertices.push_back({{0, halfHeight, 0}, {0, 1, 0}, {0.5f, 0.5f}}); // top center + shape.vertices.push_back({{0, -halfHeight, 0}, {0, -1, 0}, {0.5f, 0.5f}}); // bottom center + + // Generate side vertices (double for proper normals) + for (int i = 0; i <= segments; ++i) { + float angle = (float)i / segments * 2.0f * M_PI; + float x = cos(angle) * radius; + float z = sin(angle) * radius; + glm::vec3 normal = glm::normalize(glm::vec3(x, 0, z)); + float u = (float)i / segments; + + // Top vertices + shape.vertices.push_back({{x, halfHeight, z}, {0, 1, 0}, {(cos(angle) + 1) * 0.5f, (sin(angle) + 1) * 0.5f}}); // top cap + shape.vertices.push_back({{x, halfHeight, z}, normal, {u, 1.0f}}); // top side + + // Bottom vertices + shape.vertices.push_back({{x, -halfHeight, z}, {0, -1, 0}, {(cos(angle) + 1) * 0.5f, (sin(angle) + 1) * 0.5f}}); // bottom cap + shape.vertices.push_back({{x, -halfHeight, z}, normal, {u, 0.0f}}); // bottom side + } + + // Generate indices + for (int i = 0; i < segments; ++i) { + int topCapStart = 2; + int bottomCapStart = 4; + + // Top cap triangles + shape.indices.push_back(0); // top center + shape.indices.push_back(topCapStart + ((i + 1) % (segments + 1)) * 4); + shape.indices.push_back(topCapStart + i * 4); + + // Bottom cap triangles + shape.indices.push_back(1); // bottom center + shape.indices.push_back(bottomCapStart + i * 4); + shape.indices.push_back(bottomCapStart + ((i + 1) % (segments + 1)) * 4); + + // Side quads (as two triangles) + int topSide1 = topCapStart + 1 + i * 4; + int topSide2 = topCapStart + 1 + ((i + 1) % (segments + 1)) * 4; + int bottomSide1 = bottomCapStart + 1 + i * 4; + int bottomSide2 = bottomCapStart + 1 + ((i + 1) % (segments + 1)) * 4; + + // First triangle + shape.indices.push_back(topSide1); + shape.indices.push_back(bottomSide1); + shape.indices.push_back(topSide2); + + // Second triangle + shape.indices.push_back(topSide2); + shape.indices.push_back(bottomSide1); + shape.indices.push_back(bottomSide2); + } + + return shape; + } + + static ProceduralShape generateTorus(float majorRadius = 1.0f, float minorRadius = 0.3f, int majorSegments = 16, int minorSegments = 8) { + ProceduralShape shape; + shape.name = "Torus"; + shape.type = 6; // Torus type + shape.params.majorRadius = majorRadius; + shape.params.minorRadius = minorRadius; + shape.params.segments = majorSegments; + shape.params.subdivisions = minorSegments; + + // Generate vertices + for (int i = 0; i <= majorSegments; ++i) { + float majorAngle = (float)i / majorSegments * 2.0f * M_PI; + float cosMajor = cos(majorAngle); + float sinMajor = sin(majorAngle); + + for (int j = 0; j <= minorSegments; ++j) { + float minorAngle = (float)j / minorSegments * 2.0f * M_PI; + float cosMinor = cos(minorAngle); + float sinMinor = sin(minorAngle); + + // Calculate position + float x = (majorRadius + minorRadius * cosMinor) * cosMajor; + float y = minorRadius * sinMinor; + float z = (majorRadius + minorRadius * cosMinor) * sinMajor; + + // Calculate normal + glm::vec3 center(majorRadius * cosMajor, 0, majorRadius * sinMajor); + glm::vec3 position(x, y, z); + glm::vec3 normal = glm::normalize(position - center); + + // Calculate UV coordinates + float u = (float)i / majorSegments; + float v = (float)j / minorSegments; + + shape.vertices.push_back({{x, y, z}, normal, {u, v}}); + } + } + + // Generate indices + for (int i = 0; i < majorSegments; ++i) { + for (int j = 0; j < minorSegments; ++j) { + int current = i * (minorSegments + 1) + j; + int next = ((i + 1) % (majorSegments + 1)) * (minorSegments + 1) + j; + + // First triangle + shape.indices.push_back(current); + shape.indices.push_back(next); + shape.indices.push_back(current + 1); + + // Second triangle + shape.indices.push_back(next); + shape.indices.push_back(next + 1); + shape.indices.push_back(current + 1); + } + } + + return shape; + } +}; + +// Hierarchical Scene Node structure (based on Sascha's gltfscenerendering) +struct SceneNode { + SceneNode* parent = nullptr; + std::vector children; + std::string name; + glm::vec3 position = {0.0f, 0.0f, 0.0f}; + glm::vec3 rotation = {0.0f, 0.0f, 0.0f}; + glm::vec3 scale = {1.0f, 1.0f, 1.0f}; + glm::mat4 matrix = glm::mat4(1.0f); + bool visible = true; + + // Procedural shape data + ProceduralShape* proceduralShape = nullptr; + bool isProceduralShape = false; + + // Object type for icons/identification + enum ObjectType { + SCENE_ROOT, + PROCEDURAL_OBJECT, + MODEL_OBJECT, + LIGHT_OBJECT, + CAMERA_OBJECT + } type = PROCEDURAL_OBJECT; + + // Constructor + SceneNode(const std::string& nodeName = "Object", ObjectType nodeType = PROCEDURAL_OBJECT) + : name(nodeName), type(nodeType) {} + + // Destructor - clean up children + ~SceneNode() { + for (auto child : children) { + delete child; + } + if (proceduralShape) { + delete proceduralShape; + } + } + + // Add child to this node + void addChild(SceneNode* child) { + if (child && child->parent != this) { + if (child->parent) { + child->parent->removeChild(child); + } + child->parent = this; + children.push_back(child); + } + } + + // Remove child from this node + void removeChild(SceneNode* child) { + auto it = std::find(children.begin(), children.end(), child); + if (it != children.end()) { + (*it)->parent = nullptr; + children.erase(it); + } + } + + // Get world transform matrix + glm::mat4 getWorldMatrix() const { + glm::mat4 nodeMatrix = matrix; + SceneNode* currentParent = parent; + while (currentParent) { + nodeMatrix = currentParent->matrix * nodeMatrix; + currentParent = currentParent->parent; + } + return nodeMatrix; + } + + // Update transform matrix from position, rotation, scale + void updateMatrix() { + matrix = glm::mat4(1.0f); + matrix = glm::translate(matrix, position); + matrix = glm::rotate(matrix, glm::radians(rotation.x), glm::vec3(1, 0, 0)); + matrix = glm::rotate(matrix, glm::radians(rotation.y), glm::vec3(0, 1, 0)); + matrix = glm::rotate(matrix, glm::radians(rotation.z), glm::vec3(0, 0, 1)); + matrix = glm::scale(matrix, scale); + } +}; + +// Scene management with hierarchical structure +struct SceneManager { + std::vector rootNodes; + SceneNode* selectedNode = nullptr; + SceneNode* sceneRoot = nullptr; + + SceneManager() { + // Create clean scene root without default categories + sceneRoot = new SceneNode("Scene", SceneNode::SCENE_ROOT); + rootNodes.push_back(sceneRoot); + } + + ~SceneManager() { + for (auto node : rootNodes) { + delete node; + } + } + + void addProceduralShape(const ProceduralShape& shape, SceneNode* parent = nullptr) { + if (!parent) { + // Add directly to scene root for flat hierarchy + parent = sceneRoot; + } + + SceneNode* newNode = new SceneNode(shape.name + " " + std::to_string(getObjectCount() + 1), SceneNode::PROCEDURAL_OBJECT); + newNode->isProceduralShape = true; + newNode->proceduralShape = new ProceduralShape(shape); + newNode->updateMatrix(); + + parent->addChild(newNode); + selectedNode = newNode; + } + + SceneNode* findNodeByName(const std::string& name) { + for (auto root : rootNodes) { + SceneNode* found = findNodeByNameRecursive(root, name); + if (found) return found; + } + return nullptr; + } + + SceneNode* findNodeByNameRecursive(SceneNode* node, const std::string& name) { + if (node->name == name) return node; + for (auto child : node->children) { + SceneNode* found = findNodeByNameRecursive(child, name); + if (found) return found; + } + return nullptr; + } + + int getObjectCount() const { + int count = 0; + for (auto root : rootNodes) { + count += getObjectCountRecursive(root); + } + return count; + } + + int getObjectCountRecursive(SceneNode* node) const { + int count = (node->type == SceneNode::PROCEDURAL_OBJECT || node->type == SceneNode::MODEL_OBJECT) ? 1 : 0; + for (auto child : node->children) { + count += getObjectCountRecursive(child); + } + return count; + } + + void deleteNode(SceneNode* node) { + if (!node || node == sceneRoot) return; + + if (selectedNode == node) { + selectedNode = nullptr; + } + + if (node->parent) { + node->parent->removeChild(node); + } else { + auto it = std::find(rootNodes.begin(), rootNodes.end(), node); + if (it != rootNodes.end()) { + rootNodes.erase(it); + } + } + + delete node; + } + + void clearScene() { + selectedNode = nullptr; + // Clear all objects from scene root + if (sceneRoot) { + for (auto child : sceneRoot->children) { + delete child; + } + sceneRoot->children.clear(); + } + } +} sceneManager; + +// Options and values to display/toggle from the UI +struct UISettings { + bool displayModels = false; + bool displayBackground = false; + bool animateLight = false; + float lightSpeed = 0.25f; + std::array frameTimes{}; + float frameTimeMin = 9999.0f, frameTimeMax = 0.0f; + float lightTimer = 0.0f; + bool showGrid = true; + float gridSize = 10.0f; + int gridDivisions = 10; +} 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; + VkPhysicalDeviceDriverProperties driverProperties = {}; + VulkanExampleBase *example; + ImGuiStyle vulkanStyle; + int selectedStyle = 0; +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(); + + //SRS - Set ImGui font and style scale factors to handle retina and other HiDPI displays + ImGuiIO& io = ImGui::GetIO(); + io.FontGlobalScale = example->ui.scale; + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(example->ui.scale); + }; + + ~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 + vulkanStyle = ImGui::GetStyle(); + vulkanStyle.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f); + vulkanStyle.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f); + vulkanStyle.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); + vulkanStyle.Colors[ImGuiCol_Header] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); + vulkanStyle.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); + + setStyle(0); + + // Dimensions + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(width, height); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); +#if defined(_WIN32) + // If we directly work with os specific key codes, we need to map special key types like tab + io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = VK_UP; + io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; + io.KeyMap[ImGuiKey_Backspace] = VK_BACK; + io.KeyMap[ImGuiKey_Enter] = VK_RETURN; + io.KeyMap[ImGuiKey_Space] = VK_SPACE; + io.KeyMap[ImGuiKey_Delete] = VK_DELETE; +#endif + } + + void setStyle(uint32_t index) + { + switch (index) + { + case 0: + { + ImGuiStyle& style = ImGui::GetStyle(); + style = vulkanStyle; + break; + } + case 1: + ImGui::StyleColorsClassic(); + break; + case 2: + ImGui::StyleColorsDark(); + break; + case 3: + ImGui::StyleColorsLight(); + break; + case 4: // Blue theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.3f, 0.8f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.4f, 1.0f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.2f, 0.6f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.3f, 0.7f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.05f, 0.15f, 0.9f); + break; + } + case 5: // Green theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.0f, 0.6f, 0.2f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.8f, 0.3f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.0f, 0.4f, 0.1f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.0f, 0.5f, 0.2f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.05f, 0.15f, 0.05f, 0.9f); + break; + } + case 6: // Purple theme + { + ImGuiStyle& style = ImGui::GetStyle(); + style = ImGui::GetStyle(); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.5f, 0.0f, 0.8f, 0.6f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.7f, 0.0f, 1.0f, 0.8f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.3f, 0.0f, 0.6f, 0.4f); + style.Colors[ImGuiCol_Header] = ImVec4(0.4f, 0.0f, 0.7f, 0.4f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.1f, 0.05f, 0.15f, 0.9f); + break; + } + } + } + + // 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); + + //SRS - Get Vulkan device driver information if available, use later for display + if (device->extensionSupported(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) + { + VkPhysicalDeviceProperties2 deviceProperties2 = {}; + deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + deviceProperties2.pNext = &driverProperties; + driverProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; + vkGetPhysicalDeviceProperties2(device->physicalDevice, &deviceProperties2); + } + + // 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 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 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 writeDescriptorSets = { + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor) + }; + vkUpdateDescriptorSets(device->logicalDevice, static_cast(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 dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + + std::array 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(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + + // Vertex bindings an attributes based on ImGui vertex definition + std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX), + }; + std::vector 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(vertexInputBindings.size()); + vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(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)); + } + + // Recursive function to render Maya-style outliner nodes + void renderSceneNodeInOutliner(SceneNode* node, int depth = 0) { + if (!node) return; + + // Get type-specific icon + const char* icon = "ðŸ“"; // Default folder + switch (node->type) { + case SceneNode::PROCEDURAL_OBJECT: + switch (node->proceduralShape ? node->proceduralShape->type : -1) { + case 0: icon = "📦"; break; // Cube + case 1: icon = "🔵"; break; // Sphere + case 2: icon = "🔴"; break; // Cylinder + case 3: icon = "ðŸ“"; break; // Plane + case 4: icon = "🔺"; break; // Cone + case 6: icon = "ðŸ©"; break; // Torus + default: icon = "🔷"; break; // Generic shape + } + break; + case SceneNode::MODEL_OBJECT: icon = "🗿"; break; + case SceneNode::LIGHT_OBJECT: icon = "💡"; break; + case SceneNode::CAMERA_OBJECT: icon = "📷"; break; + case SceneNode::SCENE_ROOT: icon = "ðŸ“"; break; + } + + // Create unique ID for ImGui + ImGui::PushID(node); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (sceneManager.selectedNode == node) { + flags |= ImGuiTreeNodeFlags_Selected; + } + if (node->children.empty()) { + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + } + + // Visibility toggle + if (node->type != SceneNode::SCENE_ROOT) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + if (ImGui::SmallButton(node->visible ? "ðŸ‘" : "🚫")) { + node->visible = !node->visible; + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + } + + // Node name with icon + bool nodeOpen = false; + if (node->children.empty()) { + // Leaf node - use Selectable + flags &= ~ImGuiTreeNodeFlags_OpenOnArrow; // Remove arrow for leaf nodes + bool isSelected = (sceneManager.selectedNode == node); + if (ImGui::Selectable((std::string(icon) + " " + node->name).c_str(), isSelected)) { + sceneManager.selectedNode = node; + } + } else { + // Branch node - use TreeNode + nodeOpen = ImGui::TreeNodeEx((std::string(icon) + " " + node->name).c_str(), flags); + if (ImGui::IsItemClicked()) { + sceneManager.selectedNode = node; + } + } + + // Context menu + if (ImGui::BeginPopupContextItem()) { + if (node->type != SceneNode::SCENE_ROOT) { + if (ImGui::MenuItem("Delete", "Del")) { + sceneManager.deleteNode(node); + ImGui::PopID(); + ImGui::EndPopup(); + return; // Node deleted, exit + } + if (ImGui::MenuItem("Duplicate", "Ctrl+D")) { + // TODO: Implement duplication + } + ImGui::Separator(); + } + if (ImGui::MenuItem("Create Child")) { + // TODO: Show submenu for object types + } + ImGui::EndPopup(); + } + + // Render children if node is open + if (nodeOpen && !node->children.empty()) { + for (auto child : node->children) { + renderSceneNodeInOutliner(child, depth + 1); + } + ImGui::TreePop(); + } + + ImGui::PopID(); + } + + // Starts a new imGui frame and sets up windows and ui elements + void newFrame(VulkanExampleBase *example, bool updateFrameGraph) + { + ImGui::NewFrame(); + + // Menu Bar + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("New Scene", "Ctrl+N")) { + // Clear scene + sceneManager.clearScene(); + } + if (ImGui::MenuItem("Open Scene", "Ctrl+O")) { + // TODO: Implement scene loading + } + if (ImGui::MenuItem("Save Scene", "Ctrl+S")) { + // TODO: Implement scene saving + } + ImGui::Separator(); + if (ImGui::MenuItem("Exit", "Alt+F4")) { + example->prepared = false; + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Preferences")) + { + if (ImGui::BeginMenu("UI Theme")) + { + if (ImGui::MenuItem("Vulkan Red", nullptr, selectedStyle == 0)) { setStyle(0); selectedStyle = 0; } + if (ImGui::MenuItem("Classic", nullptr, selectedStyle == 1)) { setStyle(1); selectedStyle = 1; } + if (ImGui::MenuItem("Dark", nullptr, selectedStyle == 2)) { setStyle(2); selectedStyle = 2; } + if (ImGui::MenuItem("Light", nullptr, selectedStyle == 3)) { setStyle(3); selectedStyle = 3; } + if (ImGui::MenuItem("Blue", nullptr, selectedStyle == 4)) { setStyle(4); selectedStyle = 4; } + if (ImGui::MenuItem("Green", nullptr, selectedStyle == 5)) { setStyle(5); selectedStyle = 5; } + if (ImGui::MenuItem("Purple", nullptr, selectedStyle == 6)) { setStyle(6); selectedStyle = 6; } + ImGui::EndMenu(); + } + ImGui::Separator(); + if (ImGui::BeginMenu("Viewport")) + { + ImGui::MenuItem("Show Grid", nullptr, &uiSettings.showGrid); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem("About")) { + // TODO: Show about dialog + } + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + + // Init imGui windows and elements + + // Debug window + ImGui::SetWindowPos(ImVec2(20 * example->ui.scale, 20 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetWindowSize(ImVec2(300 * example->ui.scale, 300 * example->ui.scale), ImGuiSetCond_Always); + ImGui::TextUnformatted(example->title.c_str()); + ImGui::TextUnformatted(device->properties.deviceName); + + //SRS - Display Vulkan API version and device driver information if available (otherwise blank) + ImGui::Text("Vulkan API %i.%i.%i", VK_API_VERSION_MAJOR(device->properties.apiVersion), VK_API_VERSION_MINOR(device->properties.apiVersion), VK_API_VERSION_PATCH(device->properties.apiVersion)); + ImGui::Text("%s %s", driverProperties.driverName, driverProperties.driverInfo); + + // 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); + + + // Maya-Style Scene Hierarchy Panel + ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 360 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 400 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Scene Hierarchy"); + + // Header with object count + ImGui::Text("Scene Objects: %d", sceneManager.getObjectCount()); + ImGui::Separator(); + + // Render the hierarchical scene tree + if (sceneManager.sceneRoot) { + for (auto rootNode : sceneManager.rootNodes) { + renderSceneNodeInOutliner(rootNode); + } + } + + // Show message if scene is empty + if (sceneManager.getObjectCount() == 0) { + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Scene is empty"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Add objects from Asset Browser"); + } + + ImGui::End(); + + // Inspector Panel + ImGui::SetNextWindowPos(ImVec2(1180 * example->ui.scale, 20 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(300 * example->ui.scale, 700 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Inspector"); + ImGui::Text("Object Properties:"); + ImGui::Separator(); + + if (sceneManager.selectedNode && sceneManager.selectedNode->type != SceneNode::SCENE_ROOT) { + SceneNode* selectedNode = sceneManager.selectedNode; + ImGui::Text("Selected: %s", selectedNode->name.c_str()); + ImGui::Separator(); + + // Object type info + const char* typeStr = "Unknown"; + switch (selectedNode->type) { + case SceneNode::PROCEDURAL_OBJECT: typeStr = "Procedural Object"; break; + case SceneNode::MODEL_OBJECT: typeStr = "3D Model"; break; + case SceneNode::LIGHT_OBJECT: typeStr = "Light"; break; + case SceneNode::CAMERA_OBJECT: typeStr = "Camera"; break; + } + ImGui::Text("Type: %s", typeStr); + ImGui::Separator(); + + // Transform controls + ImGui::Text("Transform:"); + bool transformChanged = false; + transformChanged |= ImGui::DragFloat3("Position", &selectedNode->position.x, 0.1f); + transformChanged |= ImGui::DragFloat3("Rotation", &selectedNode->rotation.x, 1.0f); + transformChanged |= ImGui::DragFloat3("Scale", &selectedNode->scale.x, 0.01f); + if (transformChanged) { + selectedNode->updateMatrix(); + } + ImGui::Separator(); + + // Procedural shape parameters + if (selectedNode->isProceduralShape && selectedNode->proceduralShape) { + ImGui::Text("Shape Parameters:"); + ProceduralShape* shape = selectedNode->proceduralShape; + bool regenerate = false; + + switch (shape->type) { + case 0: // Cube + regenerate |= ImGui::DragFloat("Width", &shape->params.width, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragFloat("Depth", &shape->params.depth, 0.1f, 0.1f, 10.0f); + if (regenerate) { + *shape = ProceduralGeometry::generateCube(shape->params.width, shape->params.height, shape->params.depth); + } + break; + case 1: // Sphere + regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); + regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 4, 64); + if (regenerate) { + *shape = ProceduralGeometry::generateSphere(shape->params.radius, shape->params.segments); + } + break; + case 3: // Plane + regenerate |= ImGui::DragFloat("Width", &shape->params.width, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragInt("Subdivisions", &shape->params.subdivisions, 1, 1, 32); + if (regenerate) { + *shape = ProceduralGeometry::generatePlane(shape->params.width, shape->params.height, shape->params.subdivisions); + } + break; + case 4: // Cone + regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); + regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 3, 64); + if (regenerate) { + *shape = ProceduralGeometry::generateCone(shape->params.radius, shape->params.height, shape->params.segments); + } + break; + case 5: // Cylinder + regenerate |= ImGui::DragFloat("Radius", &shape->params.radius, 0.1f, 0.1f, 5.0f); + regenerate |= ImGui::DragFloat("Height", &shape->params.height, 0.1f, 0.1f, 10.0f); + regenerate |= ImGui::DragInt("Segments", &shape->params.segments, 1, 3, 64); + if (regenerate) { + *shape = ProceduralGeometry::generateCylinder(shape->params.radius, shape->params.height, shape->params.segments); + } + break; + case 6: // Torus + regenerate |= ImGui::DragFloat("Major Radius", &shape->params.majorRadius, 0.1f, 0.1f, 5.0f); + regenerate |= ImGui::DragFloat("Minor Radius", &shape->params.minorRadius, 0.1f, 0.05f, 2.0f); + regenerate |= ImGui::DragInt("Major Segments", &shape->params.segments, 1, 3, 64); + regenerate |= ImGui::DragInt("Minor Segments", &shape->params.subdivisions, 1, 3, 32); + if (regenerate) { + *shape = ProceduralGeometry::generateTorus(shape->params.majorRadius, shape->params.minorRadius, shape->params.segments, shape->params.subdivisions); + } + break; + } + ImGui::Separator(); + } + } else { + ImGui::Text("Selected: None"); + ImGui::Text("Select an object in the Scene Hierarchy"); + ImGui::Separator(); + } + + // Lighting Settings + ImGui::Text("Lighting:"); + ImGui::Checkbox("Animate light", &uiSettings.animateLight); + ImGui::SliderFloat("Light speed", &uiSettings.lightSpeed, 0.1f, 1.0f); + ImGui::Separator(); + + // Grid Settings + ImGui::Text("Viewport Grid:"); + ImGui::Checkbox("Show Grid", &uiSettings.showGrid); + if (uiSettings.showGrid) { + ImGui::DragFloat("Grid Size", &uiSettings.gridSize, 0.5f, 1.0f, 50.0f); + ImGui::DragInt("Grid Divisions", &uiSettings.gridDivisions, 1, 2, 50); + } + ImGui::Separator(); + + // Procedural generation settings + ImGui::Text("Procedural Settings:"); + ImGui::Separator(); + //ImGui::ShowStyleSelector("UI style"); + + if (ImGui::Combo("UI style", &selectedStyle, "Vulkan Red\0Classic\0Dark\0Light\0Blue\0Green\0Purple\0")) { + setStyle(selectedStyle); + } + + ImGui::End(); + + // Asset Browser Panel + ImGui::SetNextWindowPos(ImVec2(20 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600 * example->ui.scale, 220 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Asset Browser"); + ImGui::Text("Project Assets:"); + ImGui::Separator(); + + if (ImGui::CollapsingHeader("Procedural Shapes", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("Basic Geometric Shapes:"); + ImGui::Separator(); + + // Create buttons for each shape type - First row + if (ImGui::Button("📦 Cube")) { + ProceduralShape cube = ProceduralGeometry::generateCube(); + sceneManager.addProceduralShape(cube); + ((VulkanExample*)example)->addShapeToRenderer(cube); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cube to the scene"); + ImGui::SameLine(); + if (ImGui::Button("🔵 Sphere")) { + ProceduralShape sphere = ProceduralGeometry::generateSphere(); + sceneManager.addProceduralShape(sphere); + static_cast(example)->addShapeToRenderer(sphere); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural sphere to the scene"); + ImGui::SameLine(); + if (ImGui::Button("📠Plane")) { + ProceduralShape plane = ProceduralGeometry::generatePlane(); + sceneManager.addProceduralShape(plane); + static_cast(example)->addShapeToRenderer(plane); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural plane to the scene"); + + // Second row + if (ImGui::Button("🔺 Cone")) { + ProceduralShape cone = ProceduralGeometry::generateCone(); + sceneManager.addProceduralShape(cone); + static_cast(example)->addShapeToRenderer(cone); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cone to the scene"); + ImGui::SameLine(); + if (ImGui::Button("🔴 Cylinder")) { + ProceduralShape cylinder = ProceduralGeometry::generateCylinder(); + sceneManager.addProceduralShape(cylinder); + static_cast(example)->addShapeToRenderer(cylinder); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural cylinder to the scene"); + ImGui::SameLine(); + if (ImGui::Button("🩠Torus")) { + ProceduralShape torus = ProceduralGeometry::generateTorus(); + sceneManager.addProceduralShape(torus); + static_cast(example)->addShapeToRenderer(torus); + } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Add a procedural torus to the scene"); + } + + if (ImGui::CollapsingHeader("Models")) + { + ImGui::Text("• MobulaBirostris.gltf"); + ImGui::Text("• PolarBear.gltf"); + } + if (ImGui::CollapsingHeader("Textures")) + { + ImGui::Text("• Loading textures from glTF files..."); + } + if (ImGui::CollapsingHeader("Materials")) + { + ImGui::Text("• Default Vulkan Materials"); + } + ImGui::End(); + + // Console Panel + ImGui::SetNextWindowPos(ImVec2(640 * example->ui.scale, 780 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(840 * example->ui.scale, 200 * example->ui.scale), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Console"); + ImGui::Text("System Console:"); + ImGui::Separator(); + ImGui::Text("[INFO] ProceduralEngine - Vulkan Renderer initialized"); + ImGui::Text("[INFO] Scene loaded successfully - Ready for procedural generation"); + ImGui::Separator(); + static char inputBuf[256] = ""; + if (ImGui::InputText("Command", inputBuf, sizeof(inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) + { + // Process command + inputBuf[0] = '\0'; + } + ImGui::End(); + + //SRS - ShowDemoWindow() sets its own initial position and size, cannot override here + // 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; + } +#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && TARGET_OS_SIMULATOR + // Apple Device Simulator does not support vkCmdDrawIndexed() with vertexOffset > 0, so rebind vertex buffer instead + offsets[0] += cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.buffer, offsets); +#else + vertexOffset += cmd_list->VtxBuffer.Size; +#endif + } + } + } + +}; + +// ---------------------------------------------------------------------------- +// 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() + { + title = "ProceduralEngine - Vulkan 3D Viewport"; + camera.type = Camera::CameraType::lookat; + camera.setPosition(glm::vec3(0.0f, 0.0f, -8.0f)); + camera.setRotation(glm::vec3(4.5f, -380.0f, 0.0f)); + camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f); + + //SRS - Enable VK_KHR_get_physical_device_properties2 to retrieve device driver information for display + enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + // 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(); + proceduralRenderer.cleanup(device); + + delete imGui; + } + + void addShapeToRenderer(const ProceduralShape& shape) { + proceduralRenderer.addShape(shape, vulkanDevice, queue); + } + + void renderProceduralShapes(VkCommandBuffer commandBuffer) { + // Render all procedural shapes in the scene + uint32_t shapeIndex = 0; + renderSceneNodeShapes(sceneManager.sceneRoot, commandBuffer, shapeIndex); + } + + void renderSceneNodeShapes(SceneNode* node, VkCommandBuffer commandBuffer, uint32_t& shapeIndex) { + if (!node) return; + + // Render this node if it's a procedural shape and visible + if (node->type == SceneNode::PROCEDURAL_OBJECT && node->visible && node->isProceduralShape && node->proceduralShape) { + proceduralRenderer.draw(commandBuffer, shapeIndex); + shapeIndex++; + } + + // Recursively render children + for (auto child : node->children) { + renderSceneNodeShapes(child, commandBuffer, shapeIndex); + } + } + + 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]); + } + + // Render procedural shapes + renderProceduralShapes(drawCmdBuffers[i]); + + // Render imGui + if (ui.visible) { + imGui->drawFrame(drawCmdBuffers[i]); + } + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void setupLayoutsAndDescriptors() + { + // descriptor pool + std::vector 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 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 pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + // Descriptor set + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); + + std::array 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(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 available in assets but not auto-loaded + // models.models.loadFromFile(getAssetPath() + "models/MobulaBirostris.gltf", vulkanDevice, queue, glTFLoadingFlags); + // models.background.loadFromFile(getAssetPath() + "models/PolarBear.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; + + updateUniformBuffers(); + + // Update imGui + ImGuiIO& io = ImGui::GetIO(); + + io.DisplaySize = ImVec2((float)width, (float)height); + io.DeltaTime = frameTimer; + + io.MousePos = ImVec2(mouseState.position.x, mouseState.position.y); + io.MouseDown[0] = mouseState.buttons.left && ui.visible; + io.MouseDown[1] = mouseState.buttons.right && ui.visible; + io.MouseDown[2] = mouseState.buttons.middle && ui.visible; + + draw(); + } + + virtual void mouseMoved(double x, double y, bool &handled) + { + ImGuiIO& io = ImGui::GetIO(); + handled = io.WantCaptureMouse && ui.visible; + } + +// Input handling is platform specific, to show how it's basically done this sample implements it for Windows +#if defined(_WIN32) + virtual void OnHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + ImGuiIO& io = ImGui::GetIO(); + // Only react to keyboard input if ImGui is active + if (io.WantCaptureKeyboard) { + // Character input + if (uMsg == WM_CHAR) { + if (wParam > 0 && wParam < 0x10000) { + io.AddInputCharacter((unsigned short)wParam); + } + } + // Special keys (tab, cursor, etc.) + if ((wParam < 256) && (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN)) { + io.KeysDown[wParam] = true; + } + if ((wParam < 256) && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) { + io.KeysDown[wParam] = false; + } + } + } +#endif + +}; + +VULKAN_EXAMPLE_MAIN() diff --git a/examples/indirectdraw/README.md b/examples/indirectdraw/README.md deleted file mode 100644 index 3fdcd9af..00000000 --- a/examples/indirectdraw/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# Indirect drawing - - - -## 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` 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/examples/indirectdraw/indirectdraw.cpp b/examples/indirectdraw/indirectdraw.cpp deleted file mode 100644 index ced8d3c7..00000000 --- a/examples/indirectdraw/indirectdraw.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* -* Vulkan Example - Indirect drawing -* -* Copyright (C) 2016-2023 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" - -// 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{ 0 }; - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline plants{ VK_NULL_HANDLE }; - VkPipeline ground{ VK_NULL_HANDLE }; - VkPipeline skysphere{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VkSampler samplerRepeat{ VK_NULL_HANDLE }; - - uint32_t objectCount = 0; - - // Store the indirect draw commands containing index offsets and instance count per object - std::vector indirectCommands; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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(); - uniformBuffer.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], 0, 1, &models.plants.vertices.buffer, offsets); - // Binding point 1 : Instance data buffer - vkCmdBindVertexBuffers(drawCmdBuffers[i], 1, 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 bindingDescriptions; - std::vector 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(0, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - // Binding point 1: Instanced data at per-instance rate - vks::initializers::vertexInputBindingDescription(1, 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(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1: Normal - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 2: Texture coordinates - vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8), // Location 3: Color - // Per-Instance attributes - // These are fetched for each instance rendered - vks::initializers::vertexInputAttributeDescription(1, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)), // Location 4: Position - vks::initializers::vertexInputAttributeDescription(1, 5, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, rot)), // Location 5: Rotation - vks::initializers::vertexInputAttributeDescription(1, 6, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)), // Location 6: Scale - vks::initializers::vertexInputAttributeDescription(1, 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(bindingDescriptions.size()); - inputState.vertexAttributeDescriptionCount = static_cast(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; - // A glTF node may consist of multiple primitives, but for this saample we only care for the first primitive - indirectCmd.firstIndex = node->mesh->primitives[0]->firstIndex; - indirectCmd.indexCount = node->mesh->primitives[0]->indexCount; - - indirectCommands.push_back(indirectCmd); - - m++; - } - } - - indirectDrawCount = static_cast(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.resize(objectCount); - - std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::uniform_real_distribution 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, &uniformBuffer, sizeof(uniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffer() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareIndirectData(); - prepareInstanceData(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - } - updateUniformBuffer(); - draw(); - } - - 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() diff --git a/examples/inlineuniformblocks/inlineuniformblocks.cpp b/examples/inlineuniformblocks/inlineuniformblocks.cpp deleted file mode 100644 index 8d79a908..00000000 --- a/examples/inlineuniformblocks/inlineuniformblocks.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* -* 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-2023 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" - -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(bool applyRandomSeed) { - std::random_device rndDevice; - std::default_random_engine rndEngine(applyRandomSeed ? rndDevice() : 0); - std::uniform_real_distribution rndDist(0.1f, 1.0f); - material.r = rndDist(rndEngine); - material.g = rndDist(rndEngine); - material.b = rndDist(rndEngine); - material.ambient = 0.0025f; - material.roughness = glm::clamp(rndDist(rndEngine), 0.005f, 1.0f); - material.metallic = glm::clamp(rndDist(rndEngine), 0.005f, 1.0f); - } - }; - std::array objects{}; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - glm::vec3 camPos; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - struct DescriptorSetLaysts { - VkDescriptorSetLayout scene{ VK_NULL_HANDLE }; - VkDescriptorSetLayout object{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - - VulkanExample() : VulkanExampleBase() - { - 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; - - /* - [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); - - /* - [POI] We also need to 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; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.object, nullptr); - uniformBuffer.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); - - // Render objects - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - - uint32_t objcount = static_cast(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 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(!benchmark.active); - } - } - - void setupDescriptors() - { - // Pool - std::vector 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(objects.size()) * sizeof(Object::Material)), - }; - VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, static_cast(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(objects.size()); - descriptorPoolCI.pNext = &descriptorPoolInlineUniformBlockCreateInfo; - - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); - - // Layouts - std::vector setLayoutBindings{}; - VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; - // Scene matrices - setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0), - }; - descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.scene)); - 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)), - }; - descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.object)); - - // Sets - // Scene - VkDescriptorSetAllocateInfo descriptorAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocateInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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() - { - /* - [POI] Pipeline layout usin two sets, one for the scene matrices and one for the per-object inline uniform blocks - */ - std::vector setLayouts = { - descriptorSetLayouts.scene, // Set 0 = Scene matrices - descriptorSetLayouts.object // Set 1 = Object inline uniform block - }; - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); - - // We use push constants for passing object positions - std::vector 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)); - - // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, &uniformBuffer, sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f)); - uniformData.camPos = camera.position * glm::vec3(-1.0f, 1.0f, -1.0f); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - /* - [POI] Update descriptor sets at runtime, called from the UI to randomize materials - */ - void updateMaterials() { - // Setup random materials for every object in the scene - for (uint32_t i = 0; i < objects.size(); i++) { - objects[i].setRandomMaterial(!benchmark.active); - } - - 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() diff --git a/examples/inputattachments/inputattachments.cpp b/examples/inputattachments/inputattachments.cpp deleted file mode 100644 index 24b5130f..00000000 --- a/examples/inputattachments/inputattachments.cpp +++ /dev/null @@ -1,634 +0,0 @@ -/* - * Vulkan Example - Using input attachments - * - * Copyright (C) 2018-2024 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" - -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{ VK_NULL_HANDLE }; - VkPipeline attachmentRead{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout attachmentWrite{ VK_NULL_HANDLE }; - VkPipelineLayout attachmentRead{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet attachmentWrite{ VK_NULL_HANDLE }; - std::vector attachmentRead{ VK_NULL_HANDLE }; - } descriptorSets; - - struct { - VkDescriptorSetLayout attachmentWrite{ VK_NULL_HANDLE }; - VkDescriptorSetLayout attachmentRead{ VK_NULL_HANDLE }; - } 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; - VkExtent2D attachmentSize{}; - - const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; - - VulkanExample() : VulkanExampleBase() - { - 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); - ui.subpass = 1; - } - - ~VulkanExample() - { - if (device) { - 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); - } - - // SRS - Recreate attachments and descriptors in case number of swapchain images has changed on resize - attachments.resize(swapChain.images.size()); - 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); - } - - vkDestroyPipelineLayout(device, pipelineLayouts.attachmentWrite, nullptr); - vkDestroyPipelineLayout(device, pipelineLayouts.attachmentRead, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.attachmentWrite, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.attachmentRead, nullptr); - vkDestroyDescriptorPool(device, descriptorPool, nullptr); - - // Since the framebuffers/attachments are referred in the descriptor sets, these need to be updated on resize - setupDescriptors(); - } - - 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.images.size()); - for (uint32_t i = 0; i < frameBuffers.size(); i++) - { - views[0] = swapChain.imageViews[i]; - 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.images.size()); - 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 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 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 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 | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_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(attachments.size()); - renderPassInfoCI.pAttachments = attachments.data(); - renderPassInfoCI.subpassCount = static_cast(subpassDescriptions.size()); - renderPassInfoCI.pSubpasses = subpassDescriptions.data(); - renderPassInfoCI.dependencyCount = static_cast(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); - - /* - First sub pass - Fills the attachments - */ - { - vks::debugutils::cmdBeginLabel(drawCmdBuffers[i], "Subpass 0: Writing attachments", { 1.0f, 0.78f, 0.05f, 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::debugutils::cmdEndLabel(drawCmdBuffers[i]); - } - - /* - Second sub pass - Render a full screen quad, reading from the previously written attachments via input attachments - */ - { - vks::debugutils::cmdBeginLabel(drawCmdBuffers[i], "Subpass 1: Reading attachments", { 0.0f, 0.5f, 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::debugutils::cmdEndLabel(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 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 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void setupDescriptors() - { - /* - Pool - */ - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, static_cast(attachments.size()) + 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(attachments.size()) + 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, static_cast(attachments.size()) * 2 + 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), static_cast(attachments.size()) + 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - /* - Attachment write - */ - { - std::vector 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 pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.attachmentWrite, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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 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 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 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(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; - updateUniformBuffers(); - draw(); - } - - 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/examples/instancing/instancing.cpp b/examples/instancing/instancing.cpp deleted file mode 100644 index 9050cb71..00000000 --- a/examples/instancing/instancing.cpp +++ /dev/null @@ -1,463 +0,0 @@ -/* -* Vulkan Example - Instanced mesh rendering, uses a separate vertex buffer for instanced data -* -* Copyright (C) 2016-2023 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" - -#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{}; - - // We provide position, rotation and scale per mesh instance - struct InstanceData { - glm::vec3 pos; - glm::vec3 rot; - float scale{ 0.0f }; - uint32_t texIndex{ 0 }; - }; - // Contains the instanced data - struct InstanceBuffer { - VkBuffer buffer{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - size_t size = 0; - VkDescriptorBufferInfo descriptor{ VK_NULL_HANDLE }; - } instanceBuffer; - - struct UniformData { - 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; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - struct { - VkPipeline instancedRocks{ VK_NULL_HANDLE }; - VkPipeline planet{ VK_NULL_HANDLE }; - VkPipeline starfield{ VK_NULL_HANDLE }; - } pipelines; - - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - struct { - VkDescriptorSet instancedRocks{ VK_NULL_HANDLE }; - VkDescriptorSet planet{ VK_NULL_HANDLE }; - } descriptorSets; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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(); - uniformBuffer.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], 0, 1, &models.rock.vertices.buffer, offsets); - // Binding point 1 : Instance data buffer - vkCmdBindVertexBuffers(drawCmdBuffers[i], 1, 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Sets - VkDescriptorSetAllocateInfo descripotrSetAllocInfo; - std::vector writeDescriptorSets; - - descripotrSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - - // Instanced rocks - // Binding 0 : Vertex shader uniform buffer - // Binding 1 : Color map - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descripotrSetAllocInfo, &descriptorSets.instancedRocks)); - writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.instancedRocks, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.instancedRocks, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.rocks.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Planet - // Binding 0 : Vertex shader uniform buffer - // Binding 1 : Color map - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descripotrSetAllocInfo, &descriptorSets.planet)); - writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.planet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - vks::initializers::writeDescriptorSet(descriptorSets.planet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.planet.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 bindingDescriptions; - std::vector 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(0, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - // Binding point 1: Instanced data at per-instance rate - vks::initializers::vertexInputBindingDescription(1, 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(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1: Normal - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 2: Texture coordinates - vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8), // Location 3: Color - // Per-Instance attributes - // These are advanced for each instance rendered - vks::initializers::vertexInputAttributeDescription(1, 4, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 4: Position - vks::initializers::vertexInputAttributeDescription(1, 5, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 5: Rotation - vks::initializers::vertexInputAttributeDescription(1, 6, VK_FORMAT_R32_SFLOAT,sizeof(float) * 6), // Location 6: Scale - vks::initializers::vertexInputAttributeDescription(1, 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(bindingDescriptions.size()); - inputState.vertexAttributeDescriptionCount = static_cast(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)); - } - - // Create a buffer with per-instance data that is sourced in the shaders - void prepareInstanceData() - { - std::vector instanceData; - instanceData.resize(INSTANCE_COUNT); - - std::default_random_engine rndGenerator(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::uniform_real_distribution uniformDist(0.0, 1.0); - std::uniform_int_distribution 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 = static_cast(2.0f * 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 = static_cast(2.0f * 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, - ©Region); - - 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, &uniformBuffer, sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - - updateUniformBuffer(); - } - - void updateUniformBuffer() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - - if (!paused) { - uniformData.locSpeed += frameTimer * 0.35f; - uniformData.globSpeed += frameTimer * 0.01f; - } - - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareInstanceData(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - } - updateUniformBuffer(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Statistics")) { - overlay->text("Instances: %d", INSTANCE_COUNT); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/meshshader/meshshader.cpp b/examples/meshshader/meshshader.cpp deleted file mode 100644 index 38bfdfe2..00000000 --- a/examples/meshshader/meshshader.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Vulkan Example - Basic sample for using mesh and task shader to replace the traditional vertex pipeline - * - * Copyright (C) 2022-2023 by Sascha Willems - www.saschawillems.de - * - * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) - */ - -#include "vulkanexamplebase.h" - -class VulkanExample : public VulkanExampleBase -{ -public: - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - } uniformData; - vks::Buffer uniformBuffer; - - uint32_t indexCount{ 0 }; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT{ VK_NULL_HANDLE }; - - VkPhysicalDeviceMeshShaderFeaturesEXT enabledMeshShaderFeatures{}; - - VulkanExample() : VulkanExampleBase() - { - title = "Mesh 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, 15.0f, 0.0f)); - camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f)); - - // The mesh shader extension requires at least Vulkan Core 1.1 - apiVersion = VK_API_VERSION_1_1; - - // Extensions required by mesh shading - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME); - 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); - - // We need to enable the mesh and task shader feature using a new struct introduced with the extension - enabledMeshShaderFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT; - enabledMeshShaderFeatures.meshShader = VK_TRUE; - enabledMeshShaderFeatures.taskShader = VK_TRUE; - - deviceCreatepNextChain = &enabledMeshShaderFeatures; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - } - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - 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.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); - - // Use mesh and task shader to draw the scene - vkCmdDrawMeshTasksEXT(drawCmdBuffers[i], 1, 1, 1); - - drawUI(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - void setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_MESH_BIT_EXT, 0), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector modelWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); - - pipelineCI.pRasterizationState = &rasterizationState; - pipelineCI.pColorBlendState = &colorBlendState; - pipelineCI.pMultisampleState = &multisampleState; - pipelineCI.pViewportState = &viewportState; - pipelineCI.pDepthStencilState = &depthStencilState; - pipelineCI.pDynamicState = &dynamicState; - pipelineCI.stageCount = static_cast(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - - // Not using a vertex shader, mesh shading doesn't require vertex input state - pipelineCI.pInputAssemblyState = nullptr; - pipelineCI.pVertexInputState = nullptr; - - // Instead of a vertex shader, we use a mesh and task shader - shaderStages[0] = loadShader(getShadersPath() + "meshshader/meshshader.mesh.spv", VK_SHADER_STAGE_MESH_BIT_EXT); - shaderStages[1] = loadShader(getShadersPath() + "meshshader/meshshader.task.spv", VK_SHADER_STAGE_TASK_BIT_EXT); - - shaderStages[2] = loadShader(getShadersPath() + "meshshader/meshshader.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))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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(); - - // Get the function pointer of the mesh shader drawing funtion - vkCmdDrawMeshTasksEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdDrawMeshTasksEXT")); - - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/multisampling/multisampling.cpp b/examples/multisampling/multisampling.cpp deleted file mode 100644 index 430ab642..00000000 --- a/examples/multisampling/multisampling.cpp +++ /dev/null @@ -1,550 +0,0 @@ -/* -* Vulkan Example - Multisampling using resolve attachments (MSAA) -* -* This sample shows how to do multisampled anti aliasing using built-in hardware via resolve attachments -* These are special attachments that a multi-sampled is resolved to using a fixed sample pattern -* -* Copyright (C) 2016-2024 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool useSampleShading = false; - VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; - - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(5.0f, -5.0f, 5.0f, 1.0f); - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline MSAA{ VK_NULL_HANDLE }; - VkPipeline MSAASampleShading{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkExtent2D attachmentSize{}; - - // Holds the Vulkan resources required for the final multi sample output target - struct MultiSampleTarget { - struct { - VkImage image{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - } color; - struct { - VkImage image{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - } depth; - } multisampleTarget; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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; - if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) - viewInfo.subresourceRange.aspectMask |= 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 - - attachmentSize = { width, height }; - - std::array 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 dependencies{}; - - // Depth attachment - dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; - dependencies[0].dependencyFlags = 0; - // Color attachment - dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].dstSubpass = 0; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].srcAccessMask = 0; - dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - dependencies[1].dependencyFlags = 0; - - VkRenderPassCreateInfo renderPassInfo = vks::initializers::renderPassCreateInfo(); - renderPassInfo.attachmentCount = static_cast(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 - - // SRS - If the window is resized, the MSAA attachments need to be released and recreated - if (attachmentSize.width != width || attachmentSize.height != height) - { - attachmentSize = { width, height }; - - // 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); - } - - std::array 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 = static_cast(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.images.size()); - for (uint32_t i = 0; i < frameBuffers.size(); i++) - { - attachments[1] = swapChain.imageViews[i]; - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - const std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model) - const std::vector setLayouts = { - descriptorSetLayout, - vkglTF::descriptorSetLayoutImage, - }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 = static_cast(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(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.model = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - // Select the highest sample count usable by the platform - // In a realworld application, this would be a user setting instead - VkSampleCountFlagBits getMaxAvailableSampleCount() - { - VkSampleCountFlags supportedSampleCount = std::min(deviceProperties.limits.framebufferColorSampleCounts, deviceProperties.limits.framebufferDepthSampleCounts); - std::vector< VkSampleCountFlagBits> possibleSampleCounts { - VK_SAMPLE_COUNT_64_BIT, VK_SAMPLE_COUNT_32_BIT, VK_SAMPLE_COUNT_16_BIT, VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT - }; - for (auto& possibleSampleCount : possibleSampleCounts) { - if (supportedSampleCount & possibleSampleCount) { - return possibleSampleCount; - } - } - return VK_SAMPLE_COUNT_1_BIT; - } - - void prepare() - { - sampleCount = getMaxAvailableSampleCount(); - ui.rasterizationSamples = sampleCount; - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/multithreading/multithreading.cpp b/examples/multithreading/multithreading.cpp deleted file mode 100644 index 8f5d7bc1..00000000 --- a/examples/multithreading/multithreading.cpp +++ /dev/null @@ -1,520 +0,0 @@ -/* -* Vulkan Example - Multi threaded command buffer generation and rendering -* -* Copyright (C) 2016-2025 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" - -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{ VK_NULL_HANDLE }; - VkPipeline starsphere{ VK_NULL_HANDLE }; - } pipelines; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkCommandBuffer primaryCommandBuffer{ VK_NULL_HANDLE }; - - // Secondary scene command buffers used to store backdrop and user interface - struct SecondaryCommandBuffers { - VkCommandBuffer background{ VK_NULL_HANDLE }; - VkCommandBuffer ui{ VK_NULL_HANDLE }; - } secondaryCommandBuffers; - - // Number of animated objects to be renderer - // by using threads and secondary command buffers - uint32_t numObjectsPerThread{ 0 }; - - // Multi threaded stuff - // Max. number of concurrent threads - uint32_t numThreads{ 0 }; - - // 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{ VK_NULL_HANDLE }; - // One command buffer per render object - std::vector commandBuffer; - // One push constant block per render object - std::vector pushConstBlock; - // Per object information (position, rotation, etc.) - std::vector objectData; - }; - std::vector threadData; - - vks::ThreadPool threadPool; - - // Fence to wait for all command buffers to finish before - // presenting to the swap chain - VkFence renderFence{ VK_NULL_HANDLE }; - - // View frustum for culling invisible objects - vks::Frustum frustum; - - std::default_random_engine rndEngine; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipelines.phong, nullptr); - vkDestroyPipeline(device, pipelines.starsphere, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - for (auto& thread : threadData) { - vkFreeCommandBuffers(device, thread.commandPool, static_cast(thread.commandBuffer.size()), thread.commandBuffer.data()); - vkDestroyCommandPool(device, thread.commandPool, nullptr); - } - vkDestroyFence(device, renderFence, nullptr); - } - } - - float rnd(float range) - { - std::uniform_real_distribution 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 = static_cast(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, - static_cast(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); - - 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 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([=, this] { 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 (ui.visible) { - commandBuffers.push_back(secondaryCommandBuffers.ui); - } - - // Execute render commands from the secondary command buffer - vkCmdExecuteCommands(primaryCommandBuffer, static_cast(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 preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = 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 - pipelineLayoutCreateInfo.pushConstantRangeCount = 1; - pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 prepare() - { - VulkanExampleBase::prepare(); - // Create a fence for synchronization - VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT); - vkCreateFence(device, &fenceCreateInfo, nullptr, &renderFence); - loadAssets(); - preparePipelines(); - prepareMultiThreadedRenderer(); - updateMatrices(); - prepared = true; - } - - 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(); - } - - virtual void render() - { - if (!prepared) - return; - updateMatrices(); - draw(); - } - - 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() diff --git a/examples/multiview/multiview.cpp b/examples/multiview/multiview.cpp deleted file mode 100644 index 6c8ff242..00000000 --- a/examples/multiview/multiview.cpp +++ /dev/null @@ -1,742 +0,0 @@ -/* -* 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-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - struct MultiviewPass { - struct FrameBufferAttachment { - VkImage image{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - } color, depth; - VkFramebuffer frameBuffer{ VK_NULL_HANDLE }; - VkRenderPass renderPass{ VK_NULL_HANDLE }; - VkDescriptorImageInfo descriptor{ VK_NULL_HANDLE }; - VkSampler sampler{ VK_NULL_HANDLE }; - VkSemaphore semaphore{ VK_NULL_HANDLE }; - std::vector commandBuffers{}; - std::vector waitFences{}; - } multiviewPass; - - vkglTF::Model scene; - - struct UniformData { - 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; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - 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() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - 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); - vkFreeCommandBuffers(device, cmdPool, static_cast(multiviewPass.commandBuffers.size()), multiviewPass.commandBuffers.data()); - 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; - if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { - depthStencilView.subresourceRange.aspectMask |= 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 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 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(attachments.size()); - renderPassCI.pAttachments = attachments.data(); - renderPassCI.subpassCount = 1; - renderPassCI.pSubpasses = &subpassDescription; - renderPassCI.dependencyCount = static_cast(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() - { - if (resized) - return; - - /* - 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 - */ - - { - 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 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(poolSizes.size()), poolSizes.data(), 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - /* - Layouts - */ - std::vector 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 pipelineLayoutCreateInfo =vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - /* - Descriptors - */ - VkDescriptorSetAllocateInfo allocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocateInfo, &descriptorSet)); - updateDescriptors(); - } - - void updateDescriptors() - { - std::vector 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(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(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(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 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 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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - 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)); - - uniformData.projection[0] = glm::frustum(left, right, bottom, top, zNear, zFar); - uniformData.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)); - - uniformData.projection[1] = glm::frustum(left, right, bottom, top, zNear, zFar); - uniformData.modelview[1] = rotM * transM; - - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareMultiview(); - prepareUniformBuffers(); - prepareDescriptors(); - preparePipelines(); - - VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(drawCmdBuffers.size())); - multiviewPass.commandBuffers.resize(drawCmdBuffers.size()); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, multiviewPass.commandBuffers.data())); - - 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; - } - - // SRS - Recreate and update Multiview resources when window size has changed - virtual void windowResized() - { - 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); - - prepareMultiview(); - updateDescriptors(); - - // SRS - Recreate Multiview command buffers in case number of swapchain images has changed on resize - vkFreeCommandBuffers(device, cmdPool, static_cast(multiviewPass.commandBuffers.size()), multiviewPass.commandBuffers.data()); - - VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(drawCmdBuffers.size())); - multiviewPass.commandBuffers.resize(drawCmdBuffers.size()); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, multiviewPass.commandBuffers.data())); - - resized = false; - buildCommandBuffers(); - - // SRS - Recreate Multiview fences in case number of swapchain images has changed on resize - for (auto& fence : multiviewPass.waitFences) { - vkDestroyFence(device, fence, nullptr); - } - - 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)); - } - } - - 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(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - 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", &uniformData.distortionAlpha, -0.6f, 0.6f)) { - updateUniformBuffers(); - } - } - } - -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/negativeviewportheight/negativeviewportheight.cpp b/examples/negativeviewportheight/negativeviewportheight.cpp deleted file mode 100644 index 473c4eb1..00000000 --- a/examples/negativeviewportheight/negativeviewportheight.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/* -* Vulkan Example - Using negative viewport heights for changing Vulkan's coordinate system -* -* Note: Requires a device that supports VK_KHR_MAINTENANCE1 -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool negativeViewport = true; - float offsety = 0.0f; - float offsetx = 0.0f; - 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() - { - 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 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 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 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 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 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 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(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 vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(float) * 5, VK_VERTEX_INPUT_RATE_VERTEX), - }; - std::vector 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(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(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 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(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->sliderFloat("offset x", &offsetx, -(float)width, (float)width)) { - buildCommandBuffers(); - } - if (overlay->sliderFloat("offset y", &offsety, -(float)height, (float)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/examples/occlusionquery/occlusionquery.cpp b/examples/occlusionquery/occlusionquery.cpp deleted file mode 100644 index ca502213..00000000 --- a/examples/occlusionquery/occlusionquery.cpp +++ /dev/null @@ -1,424 +0,0 @@ -/* -* Vulkan Example - Using occlusion query for visibility testing -* -* Copyright (C) 2016-2023 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" - -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() - { - 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); - - 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 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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - // One uniform buffer block for each mesh - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3) - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - // Occluder (plane) - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.occluder.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - // Teapot - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.teapot)); - writeDescriptorSets[0].dstSet = descriptorSets.teapot; - writeDescriptorSets[0].pBufferInfo = &uniformBuffers.teapot.descriptor; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - // Sphere - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.sphere)); - writeDescriptorSets[0].dstSet = descriptorSets.sphere; - writeDescriptorSets[0].pBufferInfo = &uniformBuffers.sphere.descriptor; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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; - - // 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(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - 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(); - } - - 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/examples/offscreen/offscreen.cpp b/examples/offscreen/offscreen.cpp deleted file mode 100644 index 93dc8ff9..00000000 --- a/examples/offscreen/offscreen.cpp +++ /dev/null @@ -1,620 +0,0 @@ -/* -* Vulkan Example - Offscreen rendering using a separate framebuffer -* -* Copyright (C) 2016-2024 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" - -// 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 UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - } uniformData; - - struct { - VkPipeline debug{ VK_NULL_HANDLE }; - VkPipeline shaded{ VK_NULL_HANDLE }; - VkPipeline shadedOffscreen{ VK_NULL_HANDLE }; - VkPipeline mirror{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout textured{ VK_NULL_HANDLE }; - VkPipelineLayout shaded{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet offscreen{ VK_NULL_HANDLE }; - VkDescriptorSet mirror{ VK_NULL_HANDLE }; - VkDescriptorSet model{ VK_NULL_HANDLE }; - } descriptorSets; - - struct { - VkDescriptorSetLayout textured{ VK_NULL_HANDLE }; - VkDescriptorSetLayout shaded{ VK_NULL_HANDLE }; - } 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() - { - 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() - { - if (device) { - // 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; - if (fbDepthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { - depthStencilView.subresourceRange.aspectMask |= 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 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 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 | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_NONE_KHR; - dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | 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_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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: 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); - - // 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 - */ - { - 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); - - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo; - - // Shaded layout - setLayoutBindings = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), - }; - descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.shaded)); - - // Textured layouts - 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) - }; - descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.textured)); - - // Sets - // Mirror plane descriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textured, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.mirror)); - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Shaded descriptor sets - allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.shaded, 1); - // Model - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.model)); - std::vector 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(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, nullptr); - - // Offscreen - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen)); - std::vector 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(offScreenWriteDescriptorSets.size()), offScreenWriteDescriptorSets.data(), 0, nullptr); - - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.shaded, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayouts.shaded)); - - pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.textured, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayouts.textured)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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(UniformData))); - // 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(UniformData))); - // 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(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffers.vsShared.map()); - VK_CHECK_RESULT(uniformBuffers.vsMirror.map()); - VK_CHECK_RESULT(uniformBuffers.vsOffScreen.map()); - - updateUniformBuffers(); - updateUniformBufferOffscreen(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - - // Model - uniformData.model = glm::mat4(1.0f); - uniformData.model = glm::rotate(uniformData.model, glm::radians(modelRotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - uniformData.model = glm::translate(uniformData.model, modelPosition); - memcpy(uniformBuffers.vsShared.mapped, &uniformData, sizeof(UniformData)); - - // Mirror - uniformData.model = glm::mat4(1.0f); - memcpy(uniformBuffers.vsMirror.mapped, &uniformData, sizeof(UniformData)); - } - - void updateUniformBufferOffscreen() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - uniformData.model = glm::rotate(uniformData.model, glm::radians(modelRotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - uniformData.model = glm::scale(uniformData.model, glm::vec3(1.0f, -1.0f, 1.0f)); - uniformData.model = glm::translate(uniformData.model, modelPosition); - memcpy(uniformBuffers.vsOffScreen.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareOffscreen(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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) - { - 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/examples/oit/oit.cpp b/examples/oit/oit.cpp deleted file mode 100644 index 5448c19a..00000000 --- a/examples/oit/oit.cpp +++ /dev/null @@ -1,592 +0,0 @@ -/* -* Vulkan Example - Order Independent Transparency rendering using linked lists -* -* 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 NODE_COUNT 20 - -class VulkanExample : public VulkanExampleBase -{ -public: - struct { - vkglTF::Model sphere; - vkglTF::Model cube; - } models; - - struct Node { - glm::vec4 color; - float depth{ 0.0f }; - uint32_t next{ 0 }; - }; - - struct { - uint32_t count{ 0 }; - uint32_t maxNodeCount{ 0 }; - } geometrySBO; - - struct GeometryPass { - VkRenderPass renderPass{ VK_NULL_HANDLE }; - VkFramebuffer framebuffer{ VK_NULL_HANDLE }; - vks::Buffer geometry; - vks::Texture headIndex; - vks::Buffer linkedList; - } geometryPass; - - struct RenderPassUniformData { - glm::mat4 projection; - glm::mat4 view; - } renderPassUniformData; - vks::Buffer renderPassUniformBuffer; - - struct ObjectData { - glm::mat4 model; - glm::vec4 color; - }; - - struct { - VkDescriptorSetLayout geometry{ VK_NULL_HANDLE }; - VkDescriptorSetLayout color{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - - struct { - VkPipelineLayout geometry{ VK_NULL_HANDLE }; - VkPipelineLayout color{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkPipeline geometry{ VK_NULL_HANDLE }; - VkPipeline color{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkDescriptorSet geometry{ VK_NULL_HANDLE }; - VkDescriptorSet color{ VK_NULL_HANDLE }; - } descriptorSets; - - VkDeviceSize objectUniformBufferSize{ 0 }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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(); - renderPassUniformBuffer.destroy(); - } - } - - void getEnabledFeatures() override - { - // The linked lists are built in a fragment shader using atomic stores, so the sample won't work without that feature available - 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 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, &renderPassUniformBuffer, sizeof(RenderPassUniformData))); - VK_CHECK_RESULT(renderPassUniformBuffer.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, ©Region); - 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; -#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // SRS - On macOS/iOS use linear tiling for atomic image access, see https://github.com/KhronosGroup/MoltenVK/issues/1027 - imageInfo.tiling = VK_IMAGE_TILING_LINEAR; -#else - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; -#endif - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layouts - - // Create a geometry descriptor set layout - std::vector setLayoutBindings = { - // renderPassUniformData - 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 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)); - - updateDescriptors(); - } - - void updateDescriptors() - { - // Images and linked buffers are recreated on resize and part of the descriptors, so we need to update those at runtime - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.geometry, 1); - - // Update a geometry descriptor set - - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.geometry)); - - std::vector writeDescriptorSets = { - // Binding 0: renderPassUniformData - vks::initializers::writeDescriptorSet(descriptorSets.geometry, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &renderPassUniformBuffer.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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - - // 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 pipeline layout - pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.color, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.color)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position }); - - // Create a geometry pipeline - 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(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 buildCommandBuffers() override - { - 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() - { - renderPassUniformData.projection = camera.matrices.perspective; - renderPassUniformData.view = camera.matrices.view; - memcpy(renderPassUniformBuffer.mapped, &renderPassUniformData, sizeof(RenderPassUniformData)); - } - - void prepare() override - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - prepareGeometryPass(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - updateUniformBuffers(); - 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(); - } - - void render() override - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - void windowResized() override - { - destroyGeometryPass(); - prepareGeometryPass(); - vkResetDescriptorPool(device, descriptorPool, 0); - updateDescriptors(); - resized = false; - buildCommandBuffers(); - } - - void destroyGeometryPass() - { - vkDestroyRenderPass(device, geometryPass.renderPass, nullptr); - vkDestroyFramebuffer(device, geometryPass.framebuffer, nullptr); - geometryPass.geometry.destroy(); - geometryPass.headIndex.destroy(); - geometryPass.linkedList.destroy(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/parallaxmapping/parallaxmapping.cpp b/examples/parallaxmapping/parallaxmapping.cpp deleted file mode 100644 index 6875ea85..00000000 --- a/examples/parallaxmapping/parallaxmapping.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* -* 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" - -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 UniformDataVertexShader { - 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; - } uniformDataVertexShader; - - struct UniformDataFragmentShader { - 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; - } uniformDataFragmentShader; - - struct { - vks::Buffer vertexShader; - vks::Buffer fragmentShader; - } uniformBuffers; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - const std::vector mappingModes = { - "Color only", - "Normal mapping", - "Parallax mapping", - "Steep parallax mapping", - "Parallax occlusion mapping", - }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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), - // Binding 2: Fragment combined normal and heightmap - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2), - // Binding 3: Fragment shader uniform buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 3), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.vertexShader.descriptor), - // Binding 1: Fragment shader image sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.colorMap.descriptor), - // Binding 2: Combined normal and heightmap - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.normalHeightMap.descriptor), - // Binding 3: Fragment shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, &uniformBuffers.fragmentShader.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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(UniformDataVertexShader))); - - // 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(UniformDataFragmentShader))); - // Map persistent - VK_CHECK_RESULT(uniformBuffers.vertexShader.map()); - VK_CHECK_RESULT(uniformBuffers.fragmentShader.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - // Vertex shader - uniformDataVertexShader.projection = camera.matrices.perspective; - uniformDataVertexShader.view = camera.matrices.view; - uniformDataVertexShader.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.2f)); - - if (!paused) { - uniformDataVertexShader.lightPos.x = sin(glm::radians(timer * 360.0f)) * 1.5f; - uniformDataVertexShader.lightPos.z = cos(glm::radians(timer * 360.0f)) * 1.5f; - } - - uniformDataVertexShader.cameraPos = glm::vec4(camera.position, -1.0f) * -1.0f; - memcpy(uniformBuffers.vertexShader.mapped, &uniformDataVertexShader, sizeof(UniformDataVertexShader)); - - // Fragment shader - memcpy(uniformBuffers.fragmentShader.mapped, &uniformDataFragmentShader, sizeof(UniformDataFragmentShader)); - } - - 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; - if (!paused || camera.updated) { - updateUniformBuffers(); - } - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->comboBox("Mode", &uniformDataFragmentShader.mappingMode, mappingModes)) { - updateUniformBuffers(); - } - } - } - -}; - -VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/examples/particlesystem/particlesystem.cpp b/examples/particlesystem/particlesystem.cpp deleted file mode 100644 index ca01acbf..00000000 --- a/examples/particlesystem/particlesystem.cpp +++ /dev/null @@ -1,565 +0,0 @@ -/* -* Vulkan Example - CPU based particle system -* -* This sample renders a particle system that is updated on the host (by the CPU) and rendered by the GPU using a vertex buffer -* -* Copyright (C) 2016-2023 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 PARTICLE_COUNT 512 - -#define FLAME_RADIUS 8.0f - -// The particle system is made from two different particle types -// That type defines how a particle is rendered -#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; - 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; - - // These parameters define the particle system behaviour - 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 Particles { - VkBuffer buffer{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - // Store the mapped address of the particle data for reuse - void *mappedMemory; - // Size of the particle buffer in bytes - size_t size{ 0 }; - } particles; - - struct { - vks::Buffer particles; - vks::Buffer environment; - } uniformBuffers; - - struct UniformDataParticles { - glm::mat4 projection; - glm::mat4 modelView; - // The viewport dimension is used by the particle system vertex shader - // to calculate the absolute point size based on the current viewport size - glm::vec2 viewportDim; - // This is the base point size for all particles - float pointSize{ 10.0f }; - } uniformDataParticles; - - struct UniformDataEnvironment { - glm::mat4 projection; - glm::mat4 modelView; - glm::mat4 normal; - glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - } uniformDataEnvironment; - - struct { - VkPipeline particles{ VK_NULL_HANDLE }; - VkPipeline environment{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - struct { - VkDescriptorSet particles{ VK_NULL_HANDLE }; - VkDescriptorSet environment{ VK_NULL_HANDLE }; - } descriptorSets; - - std::vector particleBuffer{}; - - std::default_random_engine rndEngine; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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.particles.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], static_cast(particleBuffer.size()), 1, 0, 0); - - drawUI(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - float rnd(float range) - { - std::uniform_real_distribution 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); - } - - // Change the type of a particle, e.g. from flame to smoke - 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; - } - } - - // Initialize the particle system and create a vertex buffer for rendering the particles - 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)); - } - - // Update the state of all particles - 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; - // If a particle has faded out, turn it into the other type (e.g. flame to smoke and vice versa) - if (particle.alpha > 2.0f) - { - transitionParticle(&particle); - } - } - - // Copy the updated particles to the vertex buffer - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Sets - std::vector 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.particles.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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 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(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() + "particlesystem/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "particlesystem/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() + "particlesystem/normalmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "particlesystem/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.particles, sizeof(UniformDataParticles))); - // 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(UniformDataEnvironment))); - // Map persistent - VK_CHECK_RESULT(uniformBuffers.particles.map()); - VK_CHECK_RESULT(uniformBuffers.environment.map()); - } - - void updateUniformBuffers() - { - // Particle system fire - uniformDataParticles.projection = camera.matrices.perspective; - uniformDataParticles.modelView = camera.matrices.view; - uniformDataParticles.viewportDim = glm::vec2((float)width, (float)height); - memcpy(uniformBuffers.particles.mapped, &uniformDataParticles, sizeof(UniformDataParticles)); - - // Environment - uniformDataEnvironment.projection = camera.matrices.perspective; - uniformDataEnvironment.modelView = camera.matrices.view; - uniformDataEnvironment.normal = glm::inverseTranspose(uniformDataEnvironment.modelView); - // Update light position - if (!paused) { - uniformDataEnvironment.lightPos.x = sin(timer * 2.0f * float(M_PI)) * 1.5f; - uniformDataEnvironment.lightPos.y = 0.0f; - uniformDataEnvironment.lightPos.z = cos(timer * 2.0f * float(M_PI)) * 1.5f; - } - memcpy(uniformBuffers.environment.mapped, &uniformDataEnvironment, sizeof(UniformDataEnvironment)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareParticles(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - if (!paused) { - updateParticles(); - } - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/pbrbasic/pbrbasic.cpp b/examples/pbrbasic/pbrbasic.cpp deleted file mode 100644 index b62f84db..00000000 --- a/examples/pbrbasic/pbrbasic.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* -* 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-2024 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" - -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 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{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - // Default materials to select from - std::vector materials; - int32_t materialIndex = 0; - - std::vector materialNames; - std::vector objectNames; - - VulkanExample() : VulkanExampleBase() - { - 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; - 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() - { - if (device) { - 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); - - // Draw a grid of spheres using varying material parameters - 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]; - - const uint32_t gridSize = 7; - - // Render a 2D grid of objects with varying PBR parameters - for (uint32_t y = 0; y < gridSize; y++) { - for (uint32_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); - // Vary metallic and roughness, two important PBR parameters - mat.params.metallic = glm::clamp((float)x / (float)(gridSize - 1), 0.1f, 1.0f); - mat.params.roughness = glm::clamp((float)y / (float)(gridSize - 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]); - } - } - - drawUI(drawCmdBuffers[i]); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); - } - } - - void loadAssets() - { - std::vector 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - // We use push constant to pass material information - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - std::vector 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)); - - // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); - - std::array 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(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()); - } - - 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(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - if (!paused) { - updateLights(); - } - draw(); - } - - 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/examples/pbribl/pbribl.cpp b/examples/pbribl/pbribl.cpp deleted file mode 100644 index 6c063b49..00000000 --- a/examples/pbribl/pbribl.cpp +++ /dev/null @@ -1,1420 +0,0 @@ -/* -* Vulkan Example - Physical based rendering with image based lighting -* -* This sample adds imaged based lighting from an environment map to the PBR equation -* -* Copyright (C) 2016-2023 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" - -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 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{ VK_NULL_HANDLE }; - VkPipeline pbr{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkDescriptorSet object{ VK_NULL_HANDLE }; - VkDescriptorSet skybox{ VK_NULL_HANDLE }; - } descriptorSets; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // Default materials to select from - std::vector materials; - int32_t materialIndex = 0; - - std::vector materialNames; - std::vector objectNames; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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); - - // Render a line of objects with the selected material and vary roughness/metallic material parameters - Material mat = materials[materialIndex]; - const 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]); - - } - 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 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 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 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 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(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(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 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 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 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(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 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 setLayoutBindings = {}; - VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout)); - - // Descriptor Pool - std::vector 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - std::array 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); - - 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(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(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(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 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; - - // Offscreen 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 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 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 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 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(dim * std::pow(0.5f, m)); - viewport.height = static_cast(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(viewport.width); - copyRegion.extent.height = static_cast(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, - ©Region); - - // 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(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(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(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 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 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 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 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 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(dim * std::pow(0.5f, m)); - viewport.height = static_cast(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(viewport.width); - copyRegion.extent.height = static_cast(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, - ©Region); - - // 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(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 prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - generateBRDFLUT(); - generateIrradianceCube(); - generatePrefilteredCube(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - 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 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() diff --git a/examples/pbrtexture/pbrtexture.cpp b/examples/pbrtexture/pbrtexture.cpp deleted file mode 100644 index 74bf5733..00000000 --- a/examples/pbrtexture/pbrtexture.cpp +++ /dev/null @@ -1,1352 +0,0 @@ -/* -* Vulkan Example - Physical based rendering a textured object (metal/roughness workflow) with image based lighting -* -* Copyright (C) 2016-2023 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" - -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{ VK_NULL_HANDLE }; - VkPipeline pbr{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkDescriptorSet object{ VK_NULL_HANDLE }; - VkDescriptorSet skybox{ VK_NULL_HANDLE }; - } descriptorSets; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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 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 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 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(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(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 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 setLayoutBindings = {}; - VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout)); - - // Descriptor Pool - std::vector 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - std::array 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(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(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(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 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 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 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 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 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(dim * std::pow(0.5f, m)); - viewport.height = static_cast(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(viewport.width); - copyRegion.extent.height = static_cast(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, - ©Region); - - // 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(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(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(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 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 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 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 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 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(dim * std::pow(0.5f, m)); - viewport.height = static_cast(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(viewport.width); - copyRegion.extent.height = static_cast(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, - ©Region); - - // 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(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 prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - generateBRDFLUT(); - generateIrradianceCube(); - generatePrefilteredCube(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/pipelines/pipelines.cpp b/examples/pipelines/pipelines.cpp deleted file mode 100644 index 7f812536..00000000 --- a/examples/pipelines/pipelines.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* -* Vulkan Example - Using different pipelines in a single renderpass -* -* This sample shows how to setup multiple graphics pipelines and how to use them for drawing objects with differring visuals -* -* Copyright (C) 2016-2023 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" - -class VulkanExample: public VulkanExampleBase -{ -public: - vkglTF::Model scene; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos{ 0.0f, 2.0f, 1.0f, 0.0f }; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - struct { - VkPipeline phong{ VK_NULL_HANDLE }; - VkPipeline wireframe{ VK_NULL_HANDLE }; - VkPipeline toon{ VK_NULL_HANDLE }; - } pipelines; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipelines.phong, nullptr); - if (enabledFeatures.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) - { - 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 : Render the scene using the solid colored pipeline with phong shading - viewport.width = (float)width / 3.0f; - vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phong); - vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f); - scene.draw(drawCmdBuffers[i]); - - // Center : Render the scene using a toon style pipeline - viewport.x = (float)width / 3.0f; - 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 (enabledFeatures.wideLines) { - vkCmdSetLineWidth(drawCmdBuffers[i], 2.0f); - } - scene.draw(drawCmdBuffers[i]); - - // Right : Render the scene as wireframe (if that feature is supported by the implementation) - if (enabledFeatures.fillModeNonSolid) { - 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.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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - - // Most state is shared between all pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH, }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color}); - - // Create the different pipelines used in this sample - - // 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 (enabledFeatures.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(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - // Override the base sample camera setup, since we use three viewports - camera.setPerspective(60.0f, (float)(width / 3.0f) / (float)height, 0.1f, 256.0f); - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (!enabledFeatures.fillModeNonSolid) { - if (overlay->header("Info")) { - overlay->text("Non solid fill modes not supported!"); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/pipelinestatistics/pipelinestatistics.cpp b/examples/pipelinestatistics/pipelinestatistics.cpp deleted file mode 100644 index 216f1d47..00000000 --- a/examples/pipelinestatistics/pipelinestatistics.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/* -* Vulkan Example - Retrieving pipeline statistics -* -* Copyright (C) 2017-2024 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - // This sample lets you select between different models to display - struct Models { - std::vector objects; - int32_t objectIndex{ 3 }; - std::vector names; - } models; - // Size for the two-dimensional grid of objects (e.g. 3 = draws 3x3 objects) - int32_t gridSize{ 3 }; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelview; - glm::vec4 lightPos{ -10.0f, -10.0f, 10.0f, 1.0f }; - } uniformData; - vks::Buffer uniformBuffer; - - int32_t cullMode{ VK_CULL_MODE_BACK_BIT }; - bool blending{ false }; - bool discard{ false }; - bool wireframe{ false }; - bool tessellation{ false }; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VkQueryPool queryPool{ VK_NULL_HANDLE }; - - // Vector for storing pipeline statistics results - std::vector pipelineStats{}; - std::vector pipelineStatNames{}; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vkDestroyQueryPool(device, queryPool, nullptr); - uniformBuffer.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 = 1; - VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool)); - } - - // Retrieves the results of the pipeline statistics query submitted to the command buffer - void getQueryResults() - { - // The size of the data we want to fetch ist based on the count of statistics values - uint32_t dataSize = static_cast(pipelineStats.size()) * sizeof(uint64_t); - // The stride between queries is the no. of unique value entries - uint32_t stride = static_cast(pipelineStatNames.size()) * sizeof(uint64_t); - // Note: for one query both values have the same size, but to make it easier to expand this sample these are properly calculated - vkGetQueryPoolResults( - device, - queryPool, - 0, - 1, - dataSize, - pipelineStats.data(), - stride, - 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) { - renderPassBeginInfo.framebuffer = frameBuffers[i]; - - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - - // Reset timestamp query pool - vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, 1); - - 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 loadAssets() - { - // Objects - std::vector 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - if (pipelineLayout == VK_NULL_HANDLE) { - 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)); - } - - // Pipeline - if (pipeline != VK_NULL_HANDLE) { - // Destroy old pipeline if we're going to recreate it - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(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 shaderStages{}; - shaderStages.push_back(loadShader(getShadersPath() + "pipelinestatistics/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT)); - if (!discard) { - // When discard is enabled a pipeline must not contain a fragment shader - shaderStages.push_back(loadShader(getShadersPath() + "pipelinestatistics/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)); - } - - if (tessellation) { - inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; - pipelineCI.pTessellationState = &tessellationState; - shaderStages.push_back(loadShader(getShadersPath() + "pipelinestatistics/scene.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT)); - shaderStages.push_back(loadShader(getShadersPath() + "pipelinestatistics/scene.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)); - } - - pipelineCI.stageCount = static_cast(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, &uniformBuffer, sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelview = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - setupQueryPool(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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(); - - // Read query results for displaying in next frame - getQueryResults(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - 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(); - } - // To avoid having to create pipelines for all the settings up front, we recreate a single pipelin with different settings instead - bool recreatePipeline{ false }; - std::vector cullModeNames = { "None", "Front", "Back", "Back and front" }; - recreatePipeline |= overlay->comboBox("Cull mode", &cullMode, cullModeNames); - recreatePipeline |= overlay->checkBox("Blending", &blending); - recreatePipeline |= overlay->checkBox("Discard", &discard); - // These features may not be supported by all implementations - if (deviceFeatures.fillModeNonSolid) { - recreatePipeline |= overlay->checkBox("Wireframe", &wireframe); - } - if (deviceFeatures.tessellationShader) { - recreatePipeline |= overlay->checkBox("Tessellation", &tessellation); - } - if (recreatePipeline) { - 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/examples/pushconstants/pushconstants.cpp b/examples/pushconstants/pushconstants.cpp deleted file mode 100644 index 58376dfe..00000000 --- a/examples/pushconstants/pushconstants.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* -* Vulkan Example - Push constants example (small shader block accessed outside of uniforms for fast updates) -* -* Copyright (C) 2016-2023 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" - -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 spheres; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - } - } - - void setupSpheres() - { - // Setup random colors and fixed positions for every sphere in the scene - std::random_device rndDevice; - std::default_random_engine rndEngine(benchmark.active ? 0 : rndDevice()); - std::uniform_real_distribution rndDist(0.1f, 1.0f); - for (uint32_t i = 0; i < spheres.size(); i++) { - spheres[i].color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f); - const float rad = glm::radians(i * 360.0f / static_cast(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) - { - 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(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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor); - vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); - } - - void preparePipelines() - { - // Layout - // [POI] 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{}; - // Push constants will only be accessible at the selected pipeline stages, for this sample it's the vertex shader that reads them - 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)); - - // 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_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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() - { - 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))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f)); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - setupSpheres(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/pushdescriptors/pushdescriptors.cpp b/examples/pushdescriptors/pushdescriptors.cpp deleted file mode 100644 index da2c3e2c..00000000 --- a/examples/pushdescriptors/pushdescriptors.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/* -* 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-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool animate = true; - - PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSetKHR{ VK_NULL_HANDLE }; - VkPhysicalDevicePushDescriptorPropertiesKHR pushDescriptorProps{}; - - struct Cube { - vks::Texture2D texture; - vks::Buffer uniformBuffer; - glm::vec3 rotation; - glm::mat4 modelMat; - }; - std::array cubes; - - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - for (auto cube : cubes) { - cube.uniformBuffer.destroy(); - cube.texture.destroy(); - } - uniformBuffer.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 (const 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 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 = &uniformBuffer.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 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(setLayoutBindings.size()); - descriptorLayoutCI.pBindings = setLayoutBindings.data(); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); - - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, &uniformBuffer, sizeof(UniformData))); - VK_CHECK_RESULT(uniformBuffer.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() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - 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 && !paused) { - 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.y > 360.0f) - cubes[1].rotation.y -= 360.0f; - } - } - - 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(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; - } - - 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; - updateUniformBuffers(); - if (animate && !paused) { - updateCubeUniformBuffers(); - } - draw(); - } - - 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() diff --git a/examples/radialblur/radialblur.cpp b/examples/radialblur/radialblur.cpp deleted file mode 100644 index bf59b794..00000000 --- a/examples/radialblur/radialblur.cpp +++ /dev/null @@ -1,614 +0,0 @@ -/* -* Vulkan Example - Fullscreen radial blur (Single pass offscreen effect) -* -* This samples shows how to implement a simple post-processing effect -* -* Copyright (C) 2016-2024 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool blur = true; - bool displayTexture = false; - - vks::Texture2D gradientTexture; - vkglTF::Model scene; - - struct UniformDataScene { - glm::mat4 projection; - glm::mat4 modelView; - float gradientPos = 0.0f; - } uniformDataScene; - - struct UniformDataBlurParams { - float radialBlurScale = 0.35f; - float radialBlurStrength = 0.75f; - glm::vec2 radialOrigin = glm::vec2(0.5f, 0.5f); - } uniformDataBlurParams; - - struct { - vks::Buffer scene; - vks::Buffer blurParams; - } uniformBuffers; - - struct { - VkPipeline radialBlur{ VK_NULL_HANDLE }; - VkPipeline colorPass{ VK_NULL_HANDLE }; - VkPipeline phongPass{ VK_NULL_HANDLE }; - VkPipeline offscreenDisplay{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout radialBlur{ VK_NULL_HANDLE }; - VkPipelineLayout scene{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet scene{ VK_NULL_HANDLE }; - VkDescriptorSet radialBlur{ VK_NULL_HANDLE }; - } descriptorSets; - - struct { - VkDescriptorSetLayout scene{ VK_NULL_HANDLE }; - VkDescriptorSetLayout radialBlur{ VK_NULL_HANDLE }; - } 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{}; - - // Size of the shadow map texture (per face) - const uint32_t offscreenImageSize{ 512 }; - // We use an 8 bit per component RGBA offscreen image for storing the scene parts that will be blurred - const VkFormat offscreenImageFormat{ VK_FORMAT_R8G8B8A8_UNORM }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // 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(); - - gradientTexture.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 = offscreenImageSize; - offscreenPass.height = offscreenImageSize; - - // 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 = offscreenImageFormat; - 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 = offscreenImageFormat; - 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; - if (fbDepthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) - depthStencilView.subresourceRange.aspectMask |= 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 attchmentDescriptions = {}; - // Color attachment - attchmentDescriptions[0].format = offscreenImageFormat; - 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 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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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; - - 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); - gradientTexture.loadFromFile(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layouts - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo descriptorLayout; - - // 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.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 1: 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.radialBlur)); - - // Sets - VkDescriptorSetAllocateInfo descriptorSetAllocInfo; - - // Scene rendering - descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.scene)); - - std::vector 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, &gradientTexture.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(offScreenWriteDescriptorSets.size()), offScreenWriteDescriptorSets.data(), 0, nullptr); - - // Fullscreen radial blur - descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.radialBlur, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.radialBlur)); - - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.radialBlur, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.blurParams.descriptor), - // Binding 1: Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSets.radialBlur, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.radialBlur, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.radialBlur)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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 = static_cast(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 (the vertex shader generates a screen covering triangle) - 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(UniformDataScene))); - // 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(UniformDataBlurParams), &uniformDataBlurParams)); - // Map persistent - VK_CHECK_RESULT(uniformBuffers.scene.map()); - VK_CHECK_RESULT(uniformBuffers.blurParams.map()); - } - - // Update parameters for the radial blur pass - // This only does the copy, actual parameters are set via the UI - void updateUniformBuffersBlurParams() - { - memcpy(uniformBuffers.blurParams.mapped, &uniformDataBlurParams, sizeof(UniformDataBlurParams)); - } - - // Update uniform buffers for rendering the 3D scene - void updateUniformBuffers() - { - uniformDataScene.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)); - uniformDataScene.projection = camera.matrices.perspective; - uniformDataScene.modelView = camera.matrices.view; - // Add some animation to the post processing effect by moving through a color gradient for the radial blur - if (!paused) { - uniformDataScene.gradientPos += frameTimer * 0.1f; - } - memcpy(uniformBuffers.scene.mapped, &uniformDataScene, sizeof(UniformDataScene)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareOffscreen(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->checkBox("Radial blur", &blur)) { - buildCommandBuffers(); - } - if (overlay->checkBox("Display render target only", &displayTexture)) { - buildCommandBuffers(); - } - if (blur) { - if (overlay->header("Blur parameters")) { - bool updateParams = false; - updateParams |= overlay->sliderFloat("Scale", &uniformDataBlurParams.radialBlurScale, 0.1f, 1.0f); - updateParams |= overlay->sliderFloat("Strength", &uniformDataBlurParams.radialBlurStrength, 0.1f, 2.0f); - updateParams |= overlay->sliderFloat("Horiz. origin", &uniformDataBlurParams.radialOrigin.x, 0.0f, 1.0f); - updateParams |= overlay->sliderFloat("Vert. origin", &uniformDataBlurParams.radialOrigin.y, 0.0f, 1.0f); - if (updateParams) { - updateUniformBuffersBlurParams(); - } - } - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/rayquery/rayquery.cpp b/examples/rayquery/rayquery.cpp deleted file mode 100644 index 8e0e3269..00000000 --- a/examples/rayquery/rayquery.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* -* Vulkan Example - Using ray queries for hardware accelerated ray tracing -* -* Ray queries (aka inline ray tracing) can be used in non-raytracing shaders. This sample makes use of that by -* doing ray traced shadows in a fragment shader -* -* Copyright (C) 2020-2023 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" - -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 uniformBuffer; - - vkglTF::Model scene; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - 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() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.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(scene.indices.count) / 3; - - // 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 = scene.vertices.count - 1; - 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 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 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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1) - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), - // Binding 1: Acceleration structure - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_FRAGMENT_BIT, 1), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Sets - std::vector writeDescriptorSets; - - // Debug display - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - - vkUpdateDescriptorSets(device, static_cast(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, &uniformBuffer.descriptor) - }; - - // The fragment needs access to the ray tracing acceleration structure, so we pass it as a descriptor - - // As this isn't part of Vulkan's core, we need to pass this informationen via pNext chaining - 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 = 1; - accelerationStructureWrite.descriptorCount = 1; - accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; - - writeDescriptorSets.push_back(accelerationStructureWrite); - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, - &uniformBuffer, - sizeof(UniformData))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffer.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(uniformBuffer.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; - - enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; - enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE; - enabledAccelerationStructureFeatures.pNext = &enabledBufferDeviceAddresFeatures; - - enabledRayQueryFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; - enabledRayQueryFeatures.rayQuery = VK_TRUE; - enabledRayQueryFeatures.pNext = &enabledAccelerationStructureFeatures; - - deviceCreatepNextChain = &enabledRayQueryFeatures; - } - - 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() - { - VulkanRaytracingSample::prepare(); - loadAssets(); - prepareUniformBuffers(); - createBottomLevelAccelerationStructure(); - createTopLevelAccelerationStructure(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - if (!paused || camera.updated) { - updateLight(); - } - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/raytracingbasic/raytracingbasic.cpp b/examples/raytracingbasic/raytracingbasic.cpp deleted file mode 100644 index d67f4c15..00000000 --- a/examples/raytracingbasic/raytracingbasic.cpp +++ /dev/null @@ -1,909 +0,0 @@ -/* -* Vulkan Example - Basic hardware accelerated ray tracing example -* -* Copyright (C) 2019-2023 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 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 vertices = { - { { 1.0f, 1.0f, 0.0f } }, - { { -1.0f, 1.0f, 0.0f } }, - { { 0.0f, -1.0f, 0.0f } } - }; - - // Setup indices - std::vector indices = { 0, 1, 2 }; - indexCount = static_cast(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 = 2; - 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 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 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 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 writeDescriptorSets = { - accelerationStructureWrite, - resultImageWrite, - uniformBufferWrite - }; - vkUpdateDescriptorSets(device, static_cast(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 bindings({ - accelerationStructureLayoutBinding, - resultImageLayoutBinding, - uniformBufferBinding - }); - - VkDescriptorSetLayoutCreateInfo descriptorSetlayoutCI{}; - descriptorSetlayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorSetlayoutCI.bindingCount = static_cast(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 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(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(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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(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); - resized = false; - } - - /* - 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, ©Region); - - // 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(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR")); - vkCmdBuildAccelerationStructuresKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR")); - vkBuildAccelerationStructuresKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR")); - vkCreateAccelerationStructureKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR")); - vkDestroyAccelerationStructureKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR")); - vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR")); - vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR")); - vkCmdTraceRaysKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR")); - vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR")); - vkCreateRayTracingPipelinesKHR = reinterpret_cast(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/examples/raytracingcallable/raytracingcallable.cpp b/examples/raytracingcallable/raytracingcallable.cpp deleted file mode 100644 index ea42641d..00000000 --- a/examples/raytracingcallable/raytracingcallable.cpp +++ /dev/null @@ -1,650 +0,0 @@ -/* -* 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-2024 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 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 vertices = { - { { 1.0f, 1.0f, 0.0f } }, - { { -1.0f, 1.0f, 0.0f } }, - { { 0.0f, -1.0f, 0.0f } } - }; - - // Setup indices - std::vector indices = { 0, 1, 2 }; - uint32_t indexCount = static_cast(indices.size()); - - // Setup transform matrices for the geometries in the bottom level AS - std::vector 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 geometryCounts; - std::vector 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 = 2; - 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(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(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 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 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 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 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 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector 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 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(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(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(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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); - rayTracingPipelineCI.pGroups = shaderGroups.data(); - rayTracingPipelineCI.maxPipelineRayRecursionDepth = std::min(uint32_t(2), rayTracingPipelineProperties.maxRayRecursionDepth); - 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); - resized = false; - } - - /* - 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, ©Region); - - // 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/examples/raytracinggltf/raytracinggltf.cpp b/examples/raytracinggltf/raytracinggltf.cpp deleted file mode 100644 index 61255fd8..00000000 --- a/examples/raytracinggltf/raytracinggltf.cpp +++ /dev/null @@ -1,793 +0,0 @@ -/* - * Vulkan Example - Rendering a glTF model using hardware accelerated ray tracing example (for proper transparency, this sample does frame accumulation) - * - * Copyright (C) 2023-2025 by Sascha Willems - www.saschawillems.de - * - * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) - */ - -#include "VulkanRaytracingSample.h" -#define VK_GLTF_MATERIAL_IDS -#include "VulkanglTFModel.h" - -class VulkanExample : public VulkanRaytracingSample -{ -public: - AccelerationStructure bottomLevelAS{}; - AccelerationStructure topLevelAS{}; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - vks::Buffer transformBuffer; - - struct GeometryNode { - uint64_t vertexBufferDeviceAddress; - uint64_t indexBufferDeviceAddress; - int32_t textureIndexBaseColor; - int32_t textureIndexOcclusion; - }; - vks::Buffer geometryNodesBuffer; - - std::vector shaderGroups{}; - struct ShaderBindingTables { - ShaderBindingTable raygen; - ShaderBindingTable miss; - ShaderBindingTable hit; - } shaderBindingTables; - - vks::Texture2D texture; - - struct UniformData { - glm::mat4 viewInverse; - glm::mat4 projInverse; - uint32_t frame{ 0 }; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - vkglTF::Model model; - - VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures{}; - - VulkanExample() : VulkanRaytracingSample() - { - title = "Ray tracing glTF model"; - 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.1f, -1.0f)); - - enableExtensions(); - - // Buffer device address requires the 64-bit integer feature to be enabled - enabledFeatures.shaderInt64 = VK_TRUE; - - enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - deleteStorageImage(); - deleteAccelerationStructure(bottomLevelAS); - deleteAccelerationStructure(topLevelAS); - vertexBuffer.destroy(); - indexBuffer.destroy(); - transformBuffer.destroy(); - shaderBindingTables.raygen.destroy(); - shaderBindingTables.miss.destroy(); - shaderBindingTables.hit.destroy(); - uniformBuffer.destroy(); - geometryNodesBuffer.destroy(); - } - } - - 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)); - } - - /* - Create the bottom level acceleration structure that contains the scene's actual geometry (vertices, triangles) - */ - void createBottomLevelAccelerationStructure() - { - // Use transform matrices from the glTF nodes - std::vector transformMatrices{}; - for (auto node : model.linearNodes) { - if (node->mesh) { - for (auto primitive : node->mesh->primitives) { - if (primitive->indexCount > 0) { - VkTransformMatrixKHR transformMatrix{}; - auto m = glm::mat3x4(glm::transpose(node->getMatrix())); - memcpy(&transformMatrix, (void*)&m, sizeof(glm::mat3x4)); - transformMatrices.push_back(transformMatrix); - } - } - } - } - - // 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, - static_cast(transformMatrices.size()) * sizeof(VkTransformMatrixKHR), - transformMatrices.data())); - - // Build - // One geometry per glTF node, so we can index materials using gl_GeometryIndexEXT - std::vector maxPrimitiveCounts{}; - std::vector geometries{}; - std::vector buildRangeInfos{}; - std::vector pBuildRangeInfos{}; - std::vector geometryNodes{}; - for (auto node : model.linearNodes) { - if (node->mesh) { - for (auto primitive : node->mesh->primitives) { - if (primitive->indexCount > 0) { - VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{}; - VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{}; - VkDeviceOrHostAddressConstKHR transformBufferDeviceAddress{}; - - vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.vertices.buffer);// +primitive->firstVertex * sizeof(vkglTF::Vertex); - indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.indices.buffer) + primitive->firstIndex * sizeof(uint32_t); - transformBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(transformBuffer.buffer) + static_cast(geometryNodes.size()) * sizeof(VkTransformMatrixKHR); - - VkAccelerationStructureGeometryKHR geometry{}; - geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; - geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; - geometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; - geometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; - geometry.geometry.triangles.vertexData = vertexBufferDeviceAddress; - geometry.geometry.triangles.maxVertex = model.vertices.count; - //geometry.geometry.triangles.maxVertex = primitive->vertexCount; - geometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex); - geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; - geometry.geometry.triangles.indexData = indexBufferDeviceAddress; - geometry.geometry.triangles.transformData = transformBufferDeviceAddress; - geometries.push_back(geometry); - maxPrimitiveCounts.push_back(primitive->indexCount / 3); - - VkAccelerationStructureBuildRangeInfoKHR buildRangeInfo{}; - buildRangeInfo.firstVertex = 0; - buildRangeInfo.primitiveOffset = 0; // primitive->firstIndex * sizeof(uint32_t); - buildRangeInfo.primitiveCount = primitive->indexCount / 3; - buildRangeInfo.transformOffset = 0; - buildRangeInfos.push_back(buildRangeInfo); - - GeometryNode geometryNode{}; - geometryNode.vertexBufferDeviceAddress = vertexBufferDeviceAddress.deviceAddress; - geometryNode.indexBufferDeviceAddress = indexBufferDeviceAddress.deviceAddress; - geometryNode.textureIndexBaseColor = primitive->material.baseColorTexture->index; - geometryNode.textureIndexOcclusion = primitive->material.occlusionTexture ? primitive->material.occlusionTexture->index : -1; - geometryNodes.push_back(geometryNode); - } - } - } - } - for (auto& rangeInfo : buildRangeInfos) { - pBuildRangeInfos.push_back(&rangeInfo); - } - - 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, - static_cast(geometryNodes.size()) * sizeof(GeometryNode), - geometryNodes.data())); - - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &geometryNodesBuffer, - static_cast(geometryNodes.size()) * sizeof(GeometryNode))); - - vulkanDevice->copyBuffer(&stagingBuffer, &geometryNodesBuffer, queue); - - stagingBuffer.destroy(); - - // 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 = static_cast(geometries.size()); - accelerationStructureBuildGeometryInfo.pGeometries = geometries.data(); - - VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo{}; - accelerationStructureBuildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; - vkGetAccelerationStructureBuildSizesKHR( - device, - VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, - &accelerationStructureBuildGeometryInfo, - maxPrimitiveCounts.data(), - &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 - ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); - - accelerationStructureBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; - accelerationStructureBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle; - accelerationStructureBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; - - const VkAccelerationStructureBuildRangeInfoKHR* buildOffsetInfo = buildRangeInfos.data(); - - // 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, - &accelerationStructureBuildGeometryInfo, - pBuildRangeInfos.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() - { - // We flip the matrix [1][1] = -1.0f to accomodate for the glTF up vector - 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 - ScratchBuffer 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 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; - - 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 + shadow | - |-----------| - | hit + any | - \-----------/ - - */ - 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector shaderHandleStorage(sbtSize); - VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data())); - - createShaderBindingTable(shaderBindingTables.raygen, 1); - 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 our ray tracing pipeline - */ - void createRayTracingPipeline() - { - const uint32_t imageCount = static_cast(model.textures.size()); - - std::vector setLayoutBindings = { - // Binding 0: Top level 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: Ray tracing result 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: Texture image - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 3), - // Binding 4: Geometry node information SSBO - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 4), - // Binding 5: All images used by the glTF model - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 5, imageCount) - }; - - // Unbound set - VkDescriptorSetLayoutBindingFlagsCreateInfoEXT setLayoutBindingFlags{}; - setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; - setLayoutBindingFlags.bindingCount = 6; - std::vector descriptorBindingFlags = { - 0, - 0, - 0, - 0, - 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)); - - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - /* - Setup ray tracing shader groups - */ - std::vector shaderStages; - - // Ray generation group - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/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(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() + "raytracinggltf/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(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() + "raytracinggltf/shadow.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR)); - shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; - shaderGroups.push_back(shaderGroup); - } - - // Closest hit group for doing texture lookups - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/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(shaderStages.size()) - 1; - shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; - // This group also uses an anyhit shader for doing transparency (see anyhit.rahit for details) - shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/anyhit.rahit.spv", VK_SHADER_STAGE_ANY_HIT_BIT_KHR)); - shaderGroup.anyHitShader = static_cast(shaderStages.size()) - 1; - 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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(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 descriptor sets used for the ray tracing dispatch - */ - void createDescriptorSets() - { - uint32_t imageCount = static_cast(model.textures.size()); - std::vector poolSizes = { - { VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }, - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }, - { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 }, - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(model.textures.size()) } - }; - VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); - - VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo{}; - uint32_t variableDescCounts[] = { imageCount }; - variableDescriptorCountAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT; - variableDescriptorCountAllocInfo.descriptorSetCount = 1; - variableDescriptorCountAllocInfo.pDescriptorCounts = variableDescCounts; - - VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - descriptorSetAllocateInfo.pNext = &variableDescriptorCountAllocInfo; - 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 }; - - std::vector 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, &uniformBuffer.descriptor), - // Binding 4: Geometry node information SSBO - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &geometryNodesBuffer.descriptor), - }; - - // Image descriptors for the image array - std::vector textureDescriptors{}; - for (auto texture : model.textures) { - VkDescriptorImageInfo descriptor{}; - descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - descriptor.sampler = texture.sampler;; - descriptor.imageView = texture.view; - textureDescriptors.push_back(descriptor); - } - - VkWriteDescriptorSet writeDescriptorImgArray{}; - writeDescriptorImgArray.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeDescriptorImgArray.dstBinding = 5; - writeDescriptorImgArray.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - writeDescriptorImgArray.descriptorCount = imageCount; - writeDescriptorImgArray.dstSet = descriptorSet; - writeDescriptorImgArray.pImageInfo = textureDescriptors.data(); - writeDescriptorSets.push_back(writeDescriptorImgArray); - - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - 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, - &uniformBuffer, - sizeof(uniformData), - &uniformData)); - VK_CHECK_RESULT(uniformBuffer.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); - resized = false; - } - - /* - 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, ©Region); - - // 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); - // This value is used to accumulate multiple frames into the finale picture - // It's required as ray tracing needs to do multiple passes for transparency - // In this sample we use noise offset by this frame index to shoot rays for transparency into different directions - // Once enough frames with random ray directions have been accumulated, it looks like proper transparency - uniformData.frame++; - memcpy(uniformBuffer.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; - - physicalDeviceDescriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; - physicalDeviceDescriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = VK_TRUE; - physicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray = VK_TRUE; - physicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = VK_TRUE; - physicalDeviceDescriptorIndexingFeatures.pNext = &enabledAccelerationStructureFeatures; - - deviceCreatepNextChain = &physicalDeviceDescriptorIndexingFeatures; - - enabledFeatures.samplerAnisotropy = VK_TRUE; - } - - 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; - model.loadFromFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf", vulkanDevice, queue); - } - - void prepare() - { - VulkanRaytracingSample::prepare(); - - loadAssets(); - - // 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; - updateUniformBuffers(); - if (camera.updated) { - // If the camera's view has been updated we reset the frame accumulation - uniformData.frame = -1; - } - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/raytracingintersection/raytracingintersection.cpp b/examples/raytracingintersection/raytracingintersection.cpp deleted file mode 100644 index b716eba6..00000000 --- a/examples/raytracingintersection/raytracingintersection.cpp +++ /dev/null @@ -1,622 +0,0 @@ -/* - * Vulkan Example - Hardware accelerated ray tracing intersection shader samples - * - * Copyright (C) 2023-2024 by Sascha Willems - www.saschawillems.de - * - * This sample uses intersection shaders for doing prodcedural ray traced geometry - * Instead of passing actual geometry, this samples only passes bounding boxes and sphere descriptions - * The bounding boxes are used for the ray traversal and the sphere intersections are done - * within the intersection shader - * - * 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 shaderGroups{}; - struct ShaderBindingTables { - ShaderBindingTable raygen; - ShaderBindingTable miss; - ShaderBindingTable hit; - } shaderBindingTables; - - struct UniformData { - glm::mat4 viewInverse; - glm::mat4 projInverse; - glm::vec4 lightPos; - } uniformData; - vks::Buffer ubo; - - VkPipeline pipeline; - VkPipelineLayout pipelineLayout; - VkDescriptorSet descriptorSet; - VkDescriptorSetLayout descriptorSetLayout; - - struct Sphere { - glm::vec3 center; - float radius; - glm::vec4 color; - }; - - struct AABB { - glm::vec3 min; - glm::vec3 max; - }; - - vks::Buffer spheresBuffer; - vks::Buffer aabbsBuffer; - uint32_t aabbCount{ 0 }; - - // This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate - VulkanExample() : VulkanRaytracingSample() - { - title = "Ray tracing intersection 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, -60.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(); - spheresBuffer.destroy(); - aabbsBuffer.destroy(); - } - - void createBuffers() - { - // We'll be using two buffers to describe the procedural geometry - - // A buffer with randpmly generatd sphere descriptions (center, radius, material) that'll be passed to the ray tracing shaders as a shader storage buffer object - std::vector spheres{}; - std::default_random_engine rndGenerator(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::uniform_real_distribution uniformDist(0.0, 1.0); - std::uniform_real_distribution sizeDist(1.0, 2.0); - for (uint32_t i = 0; i < 1024; i++) { - Sphere sphere{}; - //sphere.center = - sphere.radius = sizeDist(rndGenerator); - sphere.color = glm::vec4(uniformDist(rndGenerator), uniformDist(rndGenerator), uniformDist(rndGenerator), 1.0f); - // Get a random point in a sphere - float x,y,z,d{ 0.0f }; - do { - x = uniformDist(rndGenerator) * 2.0f - 1.0f; - y = uniformDist(rndGenerator) * 2.0f - 1.0f; - z = uniformDist(rndGenerator) * 2.0f - 1.0f; - d = x * x + y * y + z * z; - - } while (d > 1.0); - sphere.center = glm::vec3(x, y, z) * 25.0f; - spheres.push_back(sphere); - } - - // A buffer with the (axis aligned) bounding boxes of our sphere, which is used during the ray tracing traversal for hit detection - std::vector aabbs{}; - for (auto& sphere : spheres) { - aabbs.push_back({ sphere.center - glm::vec3(sphere.radius), sphere.center + glm::vec3(sphere.radius) }); - } - aabbCount = static_cast(aabbs.size()); - - // Copy the buffer to the device for performance reasons - vks::Buffer stagingBuffer{}; - VkBufferUsageFlags usageFlags = 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_BUFFER_USAGE_TRANSFER_DST_BIT; - - // Spheres - 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(Sphere)* spheres.size(), spheres.data())); - VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &spheresBuffer, sizeof(Sphere)* spheres.size())); - vulkanDevice->copyBuffer(&stagingBuffer, &spheresBuffer, queue); - stagingBuffer.destroy(); - - // AABBs - 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(AABB)* aabbs.size(), aabbs.data())); - VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &aabbsBuffer, sizeof(AABB)* aabbs.size())); - vulkanDevice->copyBuffer(&stagingBuffer, &aabbsBuffer, queue); - stagingBuffer.destroy(); - } - - /* - Create the bottom level acceleration structure only containing axis aligned bounding boxes for our procedural geometry - */ - void createBottomLevelAccelerationStructure() - { - // Build - VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR(); - accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; - // Instead of providing actual geometry (e.g. triangles), we only provide the axis aligned bounding boxes (AABBs) of the spheres - // The data for the actual spheres is passed elsewhere as a shader storage buffer object - accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; - accelerationStructureGeometry.geometry.aabbs.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR; - accelerationStructureGeometry.geometry.aabbs.data.deviceAddress = getBufferDeviceAddress(aabbsBuffer.buffer); - accelerationStructureGeometry.geometry.aabbs.stride = sizeof(AABB); - - // 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, - &aabbCount, - &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 = aabbCount; - std::vector 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 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 + int | - \-----------/ - - */ - 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 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; - - // We pass the sphere descriptions as shader storage buffer, so the ray tracing shaders can source properties from it - VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; - VkDescriptorBufferInfo spheresBufferDescriptor{ spheresBuffer.buffer, 0, VK_WHOLE_SIZE }; - - std::vector 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: Spheres buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &spheresBufferDescriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector 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 | VK_SHADER_STAGE_INTERSECTION_BIT_KHR, 2), - // Binding 3: Spheres buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR, 3), - }; - - 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 shaderStages; - - // Ray generation group - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/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(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() + "raytracingintersection/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(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 (procedural) - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/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_PROCEDURAL_HIT_GROUP_KHR; - shaderGroup.generalShader = VK_SHADER_UNUSED_KHR; - shaderGroup.closestHitShader = static_cast(shaderStages.size()) - 1; - shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; - // This group als uses an intersection shader for proedural geometry (see interseciton.rint for details) - shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/intersection.rint.spv", VK_SHADER_STAGE_INTERSECTION_BIT_KHR)); - shaderGroup.intersectionShader = static_cast(shaderStages.size()) - 1; - shaderGroups.push_back(shaderGroup); - } - - VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR(); - rayTracingPipelineCI.stageCount = static_cast(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); - rayTracingPipelineCI.pGroups = shaderGroups.data(); - rayTracingPipelineCI.maxPipelineRayRecursionDepth = std::min(uint32_t(2), rayTracingPipelineProperties.maxRayRecursionDepth); - 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); - resized = false; - } - - /* - 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, ©Region); - - // 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)) * 60.0f, 0.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 60.0f, 0.0f); - 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(); - - createBuffers(); - - // 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/examples/raytracingpositionfetch/raytracingpositionfetch.cpp b/examples/raytracingpositionfetch/raytracingpositionfetch.cpp deleted file mode 100644 index 21d6012f..00000000 --- a/examples/raytracingpositionfetch/raytracingpositionfetch.cpp +++ /dev/null @@ -1,557 +0,0 @@ -/* -* Vulkan Example - Using position fetch with hardware accelerated ray tracing -* -* Shows how to use the VK_KHR_ray_tracing_position_fetch extension to fetch the vertex positions in the shader from a hit triangle as stored in the acceleration structure -* See https://www.khronos.org/blog/introducing-vulkan-ray-tracing-position-fetch-extension -* -* Copyright (C) 2024 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 shaderGroups{}; - struct ShaderBindingTables { - ShaderBindingTable raygen; - ShaderBindingTable miss; - ShaderBindingTable hit; - } shaderBindingTables; - - struct UniformData { - glm::mat4 viewInverse; - glm::mat4 projInverse; - glm::vec4 lightPos; - } uniformData; - vks::Buffer ubo; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - vkglTF::Model scene; - - // This extension comes with a dedicated feature structure - VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR enabledRayTracingPositionFetchFeatures{}; - - // This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate - VulkanExample() : VulkanRaytracingSample() - { - title = "Ray tracing position fetch"; - 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, 3.0f, -10.0f)); - enableExtensions(); - // Enable new extension - enabledDeviceExtensions.push_back(VK_KHR_RAY_TRACING_POSITION_FETCH_EXTENSION_NAME); - // The corresponding GLSL extension is GL_EXT_ray_tracing_position_fetch - } - - ~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 containing 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/treasure_smooth.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(scene.indices.count) / 3; - - // 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 = scene.vertices.count - 1; - 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; - // We will access vertex positions from the bottom AS in the shader, so we need to set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR - accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR | 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; - // We will access vertex positions from the bottom AS in the shader, so we need to set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR - accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR | 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 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 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 createDescriptors() - { - // Pools - std::vector 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)); - - // Descriptors - 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 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), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector 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), - }; - - 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 shaderStages; - - // Ray generation group - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingpositionfetch/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(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() + "raytracingpositionfetch/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(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() + "raytracingpositionfetch/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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(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() - { - // 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); - resized = false; - } - - /* - 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)); - - 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 - */ - 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, ©Region); - - // 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)) * 25.0f, 25.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f, 0.0f); - 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; - - // VK_KHR_ray_tracing_position_fetch has a new feature struct - enabledRayTracingPositionFetchFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR; - enabledRayTracingPositionFetchFeatures.rayTracingPositionFetch = VK_TRUE; - enabledRayTracingPositionFetchFeatures.pNext = &enabledAccelerationStructureFeatures; - - deviceCreatepNextChain = &enabledRayTracingPositionFetchFeatures; - } - - 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(); - createDescriptors(); - 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/examples/raytracingreflections/raytracingreflections.cpp b/examples/raytracingreflections/raytracingreflections.cpp deleted file mode 100644 index 82505113..00000000 --- a/examples/raytracingreflections/raytracingreflections.cpp +++ /dev/null @@ -1,584 +0,0 @@ -/* -* 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-2024 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 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(scene.indices.count) / 3; - - // 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 = scene.vertices.count - 1; - 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 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 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 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 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector 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 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(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(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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); - rayTracingPipelineCI.pGroups = shaderGroups.data(); - rayTracingPipelineCI.maxPipelineRayRecursionDepth = std::min(uint32_t(4), rayTracingPipelineProperties.maxRayRecursionDepth); - 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); - resized = false; - } - - /* - 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)); - - 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 - */ - 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, ©Region); - - // 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/examples/raytracingsbtdata/raytracingsbtdata.cpp b/examples/raytracingsbtdata/raytracingsbtdata.cpp deleted file mode 100644 index 46739460..00000000 --- a/examples/raytracingsbtdata/raytracingsbtdata.cpp +++ /dev/null @@ -1,937 +0,0 @@ -/* -* Vulkan Example - Hardware accelerated ray tracing example using SBT data -* -* Uses the data section of each shader binding table record to color the background and geometry -* -* Example by Nate Morrical (https://github.com/natevm) -* -* Copyright (C) 2019-2024 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{ VK_NULL_HANDLE }; - VkBuffer buffer; -}; - -class VulkanExample : public VulkanExampleBase -{ -public: - PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR{ VK_NULL_HANDLE }; - PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR{ VK_NULL_HANDLE }; - PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR{ VK_NULL_HANDLE }; - PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR{ VK_NULL_HANDLE }; - PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR{ VK_NULL_HANDLE }; - PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR{ VK_NULL_HANDLE }; - PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR{ VK_NULL_HANDLE }; - PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR{ VK_NULL_HANDLE }; - PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR{ VK_NULL_HANDLE }; - PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR{ VK_NULL_HANDLE }; - - VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingPipelineProperties{}; - VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures{}; - - VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{}; - VkPhysicalDeviceRayTracingPipelineFeaturesKHR enabledRayTracingPipelineFeatures{}; - VkPhysicalDeviceAccelerationStructureFeaturesKHR enabledAccelerationStructureFeatures{}; - - AccelerationStructure bottomLevelAS{}; - AccelerationStructure topLevelAS{}; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - vks::Buffer transformBuffer; - std::vector 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{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - title = "Ray tracing SBT data"; - 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 vertices = { - { { 1.0f, 1.0f, 0.0f } }, - { { -1.0f, 1.0f, 0.0f } }, - { { 0.0f, -1.0f, 0.0f } } - }; - - // Setup indices - std::vector indices = { 0, 1, 2 }; - indexCount = static_cast(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 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 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 - In this example, we embed data in each record that can be read by the device during ray tracing - - SBT Layout used in this sample: - - /----------------\ - | raygen handle | - | - - - - - - - | - | raygen data | - |----------------| - | miss handle | - | - - - - - - - | - | miss data | - |----------------| - | hit handle | - | - - - - - - - | - | hit data | - \----------------/ - - */ - 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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; - - // We allocate space for the handle (which is like lambda function pointers to call in the ray tracing pipeline) - // as well as the data to pass to those functions (which act as the variables being "captured" by those lambda functions) - uint32_t bufferSize = vks::tools::alignedSize(handleSize + 3 * sizeof(float), rayTracingPipelineProperties.shaderGroupBaseAlignment); - VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &raygenShaderBindingTable, bufferSize)); - VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &missShaderBindingTable, bufferSize)); - VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &hitShaderBindingTable, bufferSize)); - - // 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); - - // Copy over raygen record data - glm::vec3 color1(0.5f, 0.5f, 0.5f); - memcpy(((uint8_t*)(raygenShaderBindingTable.mapped)) + handleSize, &color1, sizeof(glm::vec3)); - - // Copy over miss record data - glm::vec3 color2(1.f, 1.f, 1.f); - memcpy(((uint8_t*)(missShaderBindingTable.mapped)) + handleSize, &color2, sizeof(glm::vec3)); - - // Copy over hit group record data - glm::vec3 color3(1.f, 0.f, 0.f); - memcpy(((uint8_t*)(hitShaderBindingTable.mapped)) + handleSize, &color3, sizeof(glm::vec3)); - } - - /* - Create the descriptor sets used for the ray tracing dispatch - */ - void createDescriptorSets() - { - std::vector 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 writeDescriptorSets = { - accelerationStructureWrite, - resultImageWrite, - uniformBufferWrite - }; - vkUpdateDescriptorSets(device, static_cast(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 bindings({ - accelerationStructureLayoutBinding, - resultImageLayoutBinding, - uniformBufferBinding - }); - - VkDescriptorSetLayoutCreateInfo descriptorSetlayoutCI{}; - descriptorSetlayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorSetlayoutCI.bindingCount = static_cast(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 shaderStages; - - // Ray generation group - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingsbtdata/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(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() + "raytracingsbtdata/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(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() + "raytracingsbtdata/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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(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); - - // Note, we add 3 * sizeof(float) to each SBT entry size to account for the data sections of these records - // that we use to store our color data - VkStridedDeviceAddressRegionKHR raygenShaderSbtEntry{}; - raygenShaderSbtEntry.deviceAddress = getBufferDeviceAddress(raygenShaderBindingTable.buffer); - raygenShaderSbtEntry.size = vks::tools::alignedSize(handleSizeAligned + 3 * sizeof(float), rayTracingPipelineProperties.shaderGroupBaseAlignment); - raygenShaderSbtEntry.stride = raygenShaderSbtEntry.size; - - VkStridedDeviceAddressRegionKHR missShaderSbtEntry{}; - missShaderSbtEntry.deviceAddress = getBufferDeviceAddress(missShaderBindingTable.buffer); - missShaderSbtEntry.size = vks::tools::alignedSize(handleSizeAligned + 3 * sizeof(float), rayTracingPipelineProperties.shaderGroupBaseAlignment); - missShaderSbtEntry.stride = missShaderSbtEntry.size; - - VkStridedDeviceAddressRegionKHR hitShaderSbtEntry{}; - hitShaderSbtEntry.deviceAddress = getBufferDeviceAddress(hitShaderBindingTable.buffer); - hitShaderSbtEntry.size = vks::tools::alignedSize(handleSizeAligned + 3 * sizeof(float), rayTracingPipelineProperties.shaderGroupBaseAlignment); - hitShaderSbtEntry.stride = hitShaderSbtEntry.size; - - 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, ©Region); - - // 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(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR")); - vkCmdBuildAccelerationStructuresKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR")); - vkBuildAccelerationStructuresKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR")); - vkCreateAccelerationStructureKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR")); - vkDestroyAccelerationStructureKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR")); - vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR")); - vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR")); - vkCmdTraceRaysKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR")); - vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR")); - vkCreateRayTracingPipelinesKHR = reinterpret_cast(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/examples/raytracingshadows/raytracingshadows.cpp b/examples/raytracingshadows/raytracingshadows.cpp deleted file mode 100644 index 24825e96..00000000 --- a/examples/raytracingshadows/raytracingshadows.cpp +++ /dev/null @@ -1,584 +0,0 @@ -/* -* 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 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(scene.indices.count) / 3; - - // 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 = scene.vertices.count - 1; - 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 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 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(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 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 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector 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 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(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(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(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(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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); - rayTracingPipelineCI.pGroups = shaderGroups.data(); - rayTracingPipelineCI.maxPipelineRayRecursionDepth = std::min(uint32_t(2), rayTracingPipelineProperties.maxRayRecursionDepth); - 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); - resized = false; - } - - /* - 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, ©Region); - - // 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/examples/raytracingtextures/raytracingtextures.cpp b/examples/raytracingtextures/raytracingtextures.cpp deleted file mode 100644 index 28a540a2..00000000 --- a/examples/raytracingtextures/raytracingtextures.cpp +++ /dev/null @@ -1,715 +0,0 @@ -/* - * Vulkan Example - Texture mapping with transparency using accelerated ray tracing example - * - * Copyright (C) 2024 by Sascha Willems - www.saschawillems.de - * - * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) - */ - -/* - * This hardware accelerated ray tracing sample renders a texture mapped quad with transparency - * The sample also makes use of buffer device addresses to pass references for vertex and index buffers - * to the shader, making data access a bit more straightforward than using descriptors. - * Buffer references themselves are then simply set at draw time using push constants. - * In addition to a closest hit shader, that now samples from the texture, an any hit shader is - * added to the closest hit shader group. We use this shader to check if the texel we want to - * sample at the currently hit ray position is transparent, and if that's the case the any hit - * shader will cancel the intersection. - */ - -#include "VulkanRaytracingSample.h" - -class VulkanExample : public VulkanRaytracingSample -{ -public: - AccelerationStructure bottomLevelAS{}; - AccelerationStructure topLevelAS{}; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - vks::Buffer transformBuffer; - std::vector shaderGroups{}; - struct ShaderBindingTables { - ShaderBindingTable raygen; - ShaderBindingTable miss; - ShaderBindingTable hit; - } shaderBindingTables; - - vks::Texture2D texture; - - struct UniformData { - glm::mat4 viewInverse; - glm::mat4 projInverse; - } uniformData; - vks::Buffer ubo; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanRaytracingSample() - { - title = "Ray tracing textures"; - camera.type = Camera::CameraType::lookat; - camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); - camera.setRotation(glm::vec3(45.0f, 0.0f, 0.0f)); - camera.setTranslation(glm::vec3(0.0f, 0.0f, -1.0f)); - enableExtensions(); - // Buffer device address requires the 64-bit integer feature to be enabled - enabledFeatures.shaderInt64 = VK_TRUE; - } - - ~VulkanExample() - { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - deleteStorageImage(); - deleteAccelerationStructure(bottomLevelAS); - deleteAccelerationStructure(topLevelAS); - vertexBuffer.destroy(); - indexBuffer.destroy(); - transformBuffer.destroy(); - shaderBindingTables.raygen.destroy(); - shaderBindingTables.miss.destroy(); - shaderBindingTables.hit.destroy(); - ubo.destroy(); - texture.destroy(); - } - - 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)); - } - - /* - 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]; - float normal[3]; - float uv[2]; - }; - std::vector vertices = { - { { 0.5f, 0.5f, 0.0f }, {.0f, .0f, -1.0f}, { 1.0f, 1.0f} }, - { { -.5f, 0.5f, 0.0f }, {.0f, .0f, -1.0f}, { 0.0f, 1.0f} }, - { { -.5f, -.5f, 0.0f }, {.0f, .0f, -1.0f}, { 0.0f, 0.0f} }, - { { 0.5f, -.5f, 0.0f }, {.0f, .0f, -1.0f}, { 1.0f, 0.0f} }, - }; - - // Setup indices - std::vector indices = { 0, 1, 2, 0, 3, 2 }; - indexCount = static_cast(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_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())); - // 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.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 = static_cast(indices.size() / 3); - - 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 - ScratchBuffer 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 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 - ScratchBuffer 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 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 createShaderBindingTables() { - const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize; - const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment); - const uint32_t groupCount = static_cast(shaderGroups.size()); - const uint32_t sbtSize = groupCount * handleSizeAligned; - - std::vector 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 poolSizes = { - { VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }, - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 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 = 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 }; - - std::vector 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: Texture image - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texture.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); - } - - /* - Create our ray tracing pipeline - */ - void createRayTracingPipeline() - { - std::vector setLayoutBindings = { - // Binding 0: Top level 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: Ray tracing result 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: Texture image - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 3) - }; - - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout)); - - // We pass buffer references for vertex and index buffers via push constants - VkPushConstantRange pushConstantRange{}; - pushConstantRange.stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR; - pushConstantRange.offset = 0; - pushConstantRange.size = sizeof(uint64_t) * 2; - - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; - pipelineLayoutCI.pushConstantRangeCount = 1; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - /* - Setup ray tracing shader groups - */ - std::vector shaderStages; - - // Ray generation group - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingtextures/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(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() + "raytracingtextures/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(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 for doing texture lookups - { - shaderStages.push_back(loadShader(getShadersPath() + "raytracingtextures/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(shaderStages.size()) - 1; - shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; - // This group also uses an anyhit shader for doing transparency (see anyhit.rahit for details) - shaderStages.push_back(loadShader(getShadersPath() + "raytracingtextures/anyhit.rahit.spv", VK_SHADER_STAGE_ANY_HIT_BIT_KHR)); - shaderGroup.anyHitShader = static_cast(shaderStages.size()) - 1; - 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(shaderStages.size()); - rayTracingPipelineCI.pStages = shaderStages.data(); - rayTracingPipelineCI.groupCount = static_cast(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() - { - // 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); - resized = false; - } - - /* - 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); - - struct BufferReferences { - uint64_t vertices; - uint64_t indices; - } bufferReferences; - - bufferReferences.vertices = getBufferDeviceAddress(vertexBuffer.buffer); - bufferReferences.indices = getBufferDeviceAddress(indexBuffer.buffer); - - // We set the buffer references for the mesh to be rendered using a push constant - // If we wanted to render multiple objecets this would make it very easy to access their vertex and index buffers - vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 0, sizeof(uint64_t) * 2, &bufferReferences); - - 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, ©Region); - - // 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 loadAssets() - { - texture.loadFromFile(getAssetPath() + "textures/gratefloor_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - - void prepare() - { - VulkanRaytracingSample::prepare(); - - loadAssets(); - - // 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 (camera.updated) - updateUniformBuffers(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/renderheadless/renderheadless.cpp b/examples/renderheadless/renderheadless.cpp deleted file mode 100644 index 59dbbac1..00000000 --- a/examples/renderheadless/renderheadless.cpp +++ /dev/null @@ -1,974 +0,0 @@ -/* -* Vulkan Example - Minimal headless rendering example -* -* Copyright (C) 2017-2025 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 -#include -#include -#include -#include "VulkanAndroid.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include - -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) -#define VK_ENABLE_BETA_EXTENSIONS -#endif -#include -#include "VulkanTools.h" -#include "CommandLineParser.hpp" - -#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; -} - -CommandLineParser commandLineParser; - -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 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{}; - - std::string shaderDir = "glsl"; - - 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 - - if (commandLineParser.isSet("shaders")) { - shaderDir = commandLineParser.getValueAsString("shaders", "glsl"); - } - - VkApplicationInfo appInfo = {}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Vulkan headless example"; - appInfo.pEngineName = "VulkanExample"; - appInfo.apiVersion = VK_API_VERSION_1_0; - // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions - if (shaderDir == "slang") { - appInfo.apiVersion = VK_API_VERSION_1_1; - } - - /* - Vulkan instance creation (without surface extensions) - */ - VkInstanceCreateInfo instanceCreateInfo = {}; - instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instanceCreateInfo.pApplicationInfo = &appInfo; - - uint32_t layerCount = 1; - const char* validationLayers[] = { "VK_LAYER_KHRONOS_validation" }; - - std::vector instanceExtensions = {}; -#if DEBUG - // Check if layers are available - uint32_t instanceLayerCount; - vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); - std::vector 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; - } - } - - if (layersAvailable) { - instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); - instanceCreateInfo.ppEnabledLayerNames = validationLayers; - instanceCreateInfo.enabledLayerCount = layerCount; - } -#endif -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) - // SRS - When running on macOS with MoltenVK, enable VK_KHR_get_physical_device_properties2 (required by VK_KHR_portability_subset) - instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); -#if defined(VK_KHR_portability_enumeration) - // SRS - When running on macOS with MoltenVK and VK_KHR_portability_enumeration is defined and supported by the instance, enable the extension and the flag - uint32_t instanceExtCount = 0; - vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, nullptr); - if (instanceExtCount > 0) - { - std::vector extensions(instanceExtCount); - if (vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, &extensions.front()) == VK_SUCCESS) - { - for (VkExtensionProperties extension : extensions) - { - if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) - { - instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; - break; - } - } - } - } -#endif -#endif - instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size(); - instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data(); - 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(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 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 queueFamilyProperties(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data()); - for (uint32_t i = 0; i < static_cast(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; - std::vector deviceExtensions = {}; - - // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions - if (shaderDir == "slang") { - deviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME); - deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); - } - -#if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset) - // When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension - uint32_t deviceExtCount = 0; - vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, nullptr); - if (deviceExtCount > 0) - { - std::vector extensions(deviceExtCount); - if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, &extensions.front()) == VK_SUCCESS) - { - for (VkExtensionProperties extension : extensions) - { - if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) - { - deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); - break; - } - } - } - } -#endif - deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size(); - deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); - 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 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 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, ©Cmd)); - 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, ©Region); - 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, ©Region); - 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; - if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) - depthStencilView.subresourceRange.aspectMask |= 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 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 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(attchmentDescriptions.size()); - renderPassInfo.pAttachments = attchmentDescriptions.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpassDescription; - renderPassInfo.dependencyCount = static_cast(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 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 dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); - - std::array 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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - // Vertex bindings an attributes - // Binding description - std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), - }; - - // Attribute descriptions - std::vector 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(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - pipelineCreateInfo.pVertexInputState = &vertexInputState; - - if (commandLineParser.isSet("shaders")) { - shaderDir = commandLineParser.getValueAsString("shaders", "glsl"); - } - const std::string shadersPath = getShaderBasePath() + shaderDir + "/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 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, ©Cmd)); - 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 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(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_pollOnce(-1, NULL, &events, (void**)&source)) > ALOOPER_POLL_TIMEOUT) { - if (source != NULL) { - source->process(androidapp, source); - } - if (androidapp->destroyRequested != 0) { - break; - } - } -} -#else -int main(int argc, char* argv[]) { - commandLineParser.add("help", { "--help" }, 0, "Show help"); - commandLineParser.add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl, hlsl or slang)"); - commandLineParser.parse(argc, argv); - if (commandLineParser.isSet("help")) { - commandLineParser.printHelp(); - std::cin.get(); - return 0; - } - VulkanExample *vulkanExample = new VulkanExample(); - std::cout << "Finished. Press enter to terminate..."; - std::cin.get(); - delete(vulkanExample); - return 0; -} -#endif diff --git a/examples/screenshot/screenshot.cpp b/examples/screenshot/screenshot.cpp deleted file mode 100644 index 3e2a48c7..00000000 --- a/examples/screenshot/screenshot.cpp +++ /dev/null @@ -1,433 +0,0 @@ -/* -* Vulkan Example - Taking screenshots -* -* This sample shows how to get the conents of the swapchain (render output) and store them to disk (see saveScreenshot) -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 view; - int32_t texIndex = 0; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - bool screenshotSaved{ false }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), // Binding 0: Vertex shader uniform buffer - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - std::array 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 = static_cast(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(UniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - uniformBuffer.copyTo(&uniformData, sizeof(UniformData)); - } - - // 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 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 prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/shaderobjects/shaderobjects.cpp b/examples/shaderobjects/shaderobjects.cpp deleted file mode 100644 index 3ba4e49e..00000000 --- a/examples/shaderobjects/shaderobjects.cpp +++ /dev/null @@ -1,477 +0,0 @@ -/* - * Vulkan Example - Using shader objects via VK_EXT_shader_object - * - * Copyright (C) 2023 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" - -class VulkanExample: public VulkanExampleBase -{ -public: - vkglTF::Model scene; - - // Same uniform buffer layout as shader - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos = glm::vec4(0.0f, 2.0f, 1.0f, 0.0f); - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VkShaderEXT shaders[2]; - - VkPhysicalDeviceShaderObjectFeaturesEXT enabledShaderObjectFeaturesEXT{}; - VkPhysicalDeviceDynamicRenderingFeaturesKHR enabledDynamicRenderingFeaturesKHR{}; - - PFN_vkCreateShadersEXT vkCreateShadersEXT{ VK_NULL_HANDLE }; - PFN_vkDestroyShaderEXT vkDestroyShaderEXT{ VK_NULL_HANDLE }; - PFN_vkCmdBindShadersEXT vkCmdBindShadersEXT{ VK_NULL_HANDLE }; - PFN_vkGetShaderBinaryDataEXT vkGetShaderBinaryDataEXT{ VK_NULL_HANDLE }; - - // VK_EXT_shader_objects requires render passes to be dynamic - PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR{ VK_NULL_HANDLE }; - PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR{ VK_NULL_HANDLE }; - - // With VK_EXT_shader_object pipeline state must be set at command buffer creation using these functions - PFN_vkCmdSetAlphaToCoverageEnableEXT vkCmdSetAlphaToCoverageEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetDepthBiasEnableEXT vkCmdSetDepthBiasEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetPolygonModeEXT vkCmdSetPolygonModeEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetPrimitiveRestartEnableEXT vkCmdSetPrimitiveRestartEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetRasterizationSamplesEXT vkCmdSetRasterizationSamplesEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetRasterizerDiscardEnableEXT vkCmdSetRasterizerDiscardEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetSampleMaskEXT vkCmdSetSampleMaskEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetScissorWithCountEXT vkCmdSetScissorWithCountEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{ VK_NULL_HANDLE }; - PFN_vkCmdSetViewportWithCountEXT vkCmdSetViewportWithCountEXT{ VK_NULL_HANDLE }; - - // VK_EXT_vertex_input_dynamic_state - PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - title = "Shader objects (VK_EXT_shader_object)"; - 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) / (float)height, 0.1f, 256.0f); - - enabledDeviceExtensions.push_back(VK_EXT_SHADER_OBJECT_EXTENSION_NAME); - - enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - - // With VK_EXT_shader_object all baked pipeline state is set dynamically at command buffer creation, so we need to enable additional extensions - enabledDeviceExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); - - // Since we are not requiring Vulkan 1.2, we need to enable some additional extensios for dynamic rendering - enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - - enabledShaderObjectFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; - enabledShaderObjectFeaturesEXT.shaderObject = VK_TRUE; - - enabledDynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; - enabledDynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE; - enabledDynamicRenderingFeaturesKHR.pNext = &enabledShaderObjectFeaturesEXT; - - deviceCreatepNextChain = &enabledDynamicRenderingFeaturesKHR; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - vkDestroyShaderEXT(device, shaders[0], nullptr); - vkDestroyShaderEXT(device, shaders[1], nullptr); - } - } - - 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 setupDescriptors() - { - // Pool - std::vector 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)); - // Layout - std::vector 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 pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - // Loads a binary shader file - void _loadShader(std::string filename, char* &code, size_t &size) { - // @todo: Android - std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate); - if (is.is_open()) - { - size = is.tellg(); - is.seekg(0, std::ios::beg); - code = new char[size]; - is.read(code, size); - is.close(); - assert(size > 0); - } - else - { - vks::tools::exitFatal("Error: Could not open shader " + filename, VK_ERROR_UNKNOWN); - } - } - - void createShaderObjects() - { - size_t shaderCodeSizes[2]{}; - char* shaderCodes[2]{}; - - VkShaderCreateInfoEXT shaderCreateInfos[2]{}; - - // With VK_EXT_shader_object we can generate an implementation dependent binary file that's faster to load - // So we check if the binray files exist and if we can load it instead of the SPIR-V - bool binaryShadersLoaded = false; - - if (vks::tools::fileExists(getShadersPath() + "shaderobjects/phong.vert.bin") && vks::tools::fileExists(getShadersPath() + "shaderobjects/phong.frag.bin")) { - // VS - _loadShader(getShadersPath() + "shaderobjects/phong.vert.bin", shaderCodes[0], shaderCodeSizes[0]); - shaderCreateInfos[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; - shaderCreateInfos[0].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; - shaderCreateInfos[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shaderCreateInfos[0].nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderCreateInfos[0].codeType = VK_SHADER_CODE_TYPE_BINARY_EXT; - shaderCreateInfos[0].pCode = shaderCodes[0]; - shaderCreateInfos[0].codeSize = shaderCodeSizes[0]; - shaderCreateInfos[0].pName = "main"; - shaderCreateInfos[0].setLayoutCount = 1; - shaderCreateInfos[0].pSetLayouts = &descriptorSetLayout; - - // FS - _loadShader(getShadersPath() + "shaderobjects/phong.frag.bin", shaderCodes[1], shaderCodeSizes[1]); - shaderCreateInfos[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; - shaderCreateInfos[1].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; - shaderCreateInfos[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderCreateInfos[1].nextStage = 0; - shaderCreateInfos[1].codeType = VK_SHADER_CODE_TYPE_BINARY_EXT; - shaderCreateInfos[1].pCode = shaderCodes[1]; - shaderCreateInfos[1].codeSize = shaderCodeSizes[1]; - shaderCreateInfos[1].pName = "main"; - shaderCreateInfos[1].setLayoutCount = 1; - shaderCreateInfos[1].pSetLayouts = &descriptorSetLayout; - - VkResult result = vkCreateShadersEXT(device, 2, shaderCreateInfos, nullptr, shaders); - // If the function returns e.g. VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT, the binary file is no longer (or not at all) compatible with the current implementation - if (result == VK_SUCCESS) { - binaryShadersLoaded = true; - } else { - std::cout << "Could not load binary shader files (" << vks::tools::errorString(result) << ", loading SPIR - V instead\n"; - } - } - - // If the binary files weren't present, or we could not load them, we load from SPIR-V - if (!binaryShadersLoaded) { - // VS - _loadShader(getShadersPath() + "shaderobjects/phong.vert.spv", shaderCodes[0], shaderCodeSizes[0]); - shaderCreateInfos[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; - shaderCreateInfos[0].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; - shaderCreateInfos[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shaderCreateInfos[0].nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderCreateInfos[0].codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; - shaderCreateInfos[0].pCode = shaderCodes[0]; - shaderCreateInfos[0].codeSize = shaderCodeSizes[0]; - shaderCreateInfos[0].pName = "main"; - shaderCreateInfos[0].setLayoutCount = 1; - shaderCreateInfos[0].pSetLayouts = &descriptorSetLayout; - - // FS - _loadShader(getShadersPath() + "shaderobjects/phong.frag.spv", shaderCodes[1], shaderCodeSizes[1]); - shaderCreateInfos[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; - shaderCreateInfos[1].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; - shaderCreateInfos[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderCreateInfos[1].nextStage = 0; - shaderCreateInfos[1].codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; - shaderCreateInfos[1].pCode = shaderCodes[1]; - shaderCreateInfos[1].codeSize = shaderCodeSizes[1]; - shaderCreateInfos[1].pName = "main"; - shaderCreateInfos[1].setLayoutCount = 1; - shaderCreateInfos[1].pSetLayouts = &descriptorSetLayout; - - VK_CHECK_RESULT(vkCreateShadersEXT(device, 2, shaderCreateInfos, nullptr, shaders)); - - // Store the binary shader files so we can try to load them at the next start - size_t dataSize{ 0 }; - char* data{ nullptr }; - std::fstream is; - - vkGetShaderBinaryDataEXT(device, shaders[0], &dataSize, nullptr); - data = new char[dataSize]; - vkGetShaderBinaryDataEXT(device, shaders[0], &dataSize, data); - is.open(getShadersPath() + "shaderobjects/phong.vert.bin", std::ios::binary | std::ios::out); - is.write(data, dataSize); - is.close(); - delete[] data; - - vkGetShaderBinaryDataEXT(device, shaders[1], &dataSize, nullptr); - data = new char[dataSize]; - vkGetShaderBinaryDataEXT(device, shaders[1], &dataSize, data); - is.open(getShadersPath() + "shaderobjects/phong.frag.bin", std::ios::binary | std::ios::out); - is.write(data, dataSize); - is.close(); - delete[] data; - } - } - - 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.images[i], - 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_STENCIL_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.imageViews[i]; - colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - 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_STENCIL_ATTACHMENT_OPTIMAL; - 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); - VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); - - // No more pipelines required, everything is bound at command buffer level - // This also means that we need to explicitly set a lot of the state to be spec compliant - - vkCmdSetViewportWithCountEXT(drawCmdBuffers[i], 1, &viewport); - vkCmdSetScissorWithCountEXT(drawCmdBuffers[i], 1, &scissor); - vkCmdSetCullModeEXT(drawCmdBuffers[i], VK_CULL_MODE_BACK_BIT); - vkCmdSetFrontFaceEXT(drawCmdBuffers[i], VK_FRONT_FACE_COUNTER_CLOCKWISE); - vkCmdSetDepthTestEnableEXT(drawCmdBuffers[i], VK_TRUE); - vkCmdSetDepthWriteEnableEXT(drawCmdBuffers[i], VK_TRUE); - vkCmdSetDepthCompareOpEXT(drawCmdBuffers[i], VK_COMPARE_OP_LESS_OR_EQUAL); - vkCmdSetPrimitiveTopologyEXT(drawCmdBuffers[i], VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - vkCmdSetRasterizerDiscardEnableEXT(drawCmdBuffers[i], VK_FALSE); - vkCmdSetPolygonModeEXT(drawCmdBuffers[i], VK_POLYGON_MODE_FILL); - vkCmdSetRasterizationSamplesEXT(drawCmdBuffers[i], VK_SAMPLE_COUNT_1_BIT); - vkCmdSetAlphaToCoverageEnableEXT(drawCmdBuffers[i], VK_FALSE); - vkCmdSetDepthBiasEnableEXT(drawCmdBuffers[i], VK_FALSE); - vkCmdSetStencilTestEnableEXT(drawCmdBuffers[i], VK_FALSE); - vkCmdSetPrimitiveRestartEnableEXT(drawCmdBuffers[i], VK_FALSE); - - const uint32_t sampleMask = 0xFF; - vkCmdSetSampleMaskEXT(drawCmdBuffers[i], VK_SAMPLE_COUNT_1_BIT, &sampleMask); - - const VkBool32 colorBlendEnables = false; - const VkColorComponentFlags colorBlendComponentFlags = 0xf; - const VkColorBlendEquationEXT colorBlendEquation{}; - vkCmdSetColorBlendEnableEXT(drawCmdBuffers[i], 0, 1, &colorBlendEnables); - vkCmdSetColorWriteMaskEXT(drawCmdBuffers[i], 0, 1, &colorBlendComponentFlags); - - VkVertexInputBindingDescription2EXT vertexInputBinding{}; - vertexInputBinding.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; - vertexInputBinding.binding = 0; - vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - vertexInputBinding.stride = sizeof(vkglTF::Vertex); - vertexInputBinding.divisor = 1; - - std::vector vertexAttributes = { - { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, pos) }, - { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, normal) }, - { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(vkglTF::Vertex, color) } - }; - - vkCmdSetVertexInputEXT(drawCmdBuffers[i], 1, &vertexInputBinding, 3, vertexAttributes.data()); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - scene.bindBuffers(drawCmdBuffers[i]); - - // Binding the shaders - VkShaderStageFlagBits stages[2] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT }; - vkCmdBindShadersEXT(drawCmdBuffers[i], 2, stages, shaders); - scene.draw(drawCmdBuffers[i]); - - // @todo: Currently disabled, the UI needs to be adopated to work with shader objects - // drawUI(drawCmdBuffers[i]); - - // End dynamic rendering - vkCmdEndRenderingKHR(drawCmdBuffers[i]); - - // Transition color image for presentation - vks::tools::insertImageMemoryBarrier( - drawCmdBuffers[i], - swapChain.images[i], - 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])); - } - } - - // 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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - updateUniformBuffers(); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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(); - - // As this is an extension, we need to explicitly load the function pointers for the shader object commands used in this sample - - vkCreateShadersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateShadersEXT")); - vkDestroyShaderEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkDestroyShaderEXT")); - vkCmdBindShadersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindShadersEXT")); - vkGetShaderBinaryDataEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetShaderBinaryDataEXT")); - - vkCmdBeginRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR")); - vkCmdEndRenderingKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR")); - - vkCmdSetAlphaToCoverageEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetAlphaToCoverageEnableEXT")); - vkCmdSetColorBlendEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetColorBlendEnableEXT")); - vkCmdSetColorWriteMaskEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetColorWriteMaskEXT")); - vkCmdSetCullModeEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetCullModeEXT")); - vkCmdSetDepthBiasEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthBiasEnableEXT")); - vkCmdSetDepthCompareOpEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthCompareOpEXT")); - vkCmdSetDepthTestEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthTestEnableEXT")); - vkCmdSetDepthWriteEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthWriteEnableEXT")); - vkCmdSetFrontFaceEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetFrontFaceEXT")); - vkCmdSetPolygonModeEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetPolygonModeEXT")); - vkCmdSetPrimitiveRestartEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetPrimitiveRestartEnableEXT")); - vkCmdSetPrimitiveTopologyEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetPrimitiveTopologyEXT")); - vkCmdSetRasterizationSamplesEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetRasterizationSamplesEXT")); - vkCmdSetRasterizerDiscardEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetRasterizerDiscardEnableEXT")); - vkCmdSetSampleMaskEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetSampleMaskEXT")); - vkCmdSetScissorWithCountEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetScissorWithCountEXT")); - vkCmdSetStencilTestEnableEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetStencilTestEnableEXT")); - vkCmdSetVertexInputEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetVertexInputEXT")); - vkCmdSetViewportWithCountEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetViewportWithCountEXT"));; - - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - createShaderObjects(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - draw(); - updateUniformBuffers(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/shadowmapping/shadowmapping.cpp b/examples/shadowmapping/shadowmapping.cpp deleted file mode 100644 index 59d9474e..00000000 --- a/examples/shadowmapping/shadowmapping.cpp +++ /dev/null @@ -1,598 +0,0 @@ -/* -* Vulkan Example - Shadow mapping for directional light sources -* -* Copyright (C) 2016-2023 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" - -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 scenes; - std::vector sceneNames; - int32_t sceneIndex = 0; - - struct UniformDataScene { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - glm::mat4 depthBiasMVP; - glm::vec4 lightPos; - // Used for depth map visualization - float zNear; - float zFar; - } uniformDataScene; - - struct UniformDataOffscreen { - glm::mat4 depthMVP; - } uniformDataOffscreen; - - struct { - vks::Buffer scene; - vks::Buffer offscreen; - } uniformBuffers; - - struct { - VkPipeline offscreen{ VK_NULL_HANDLE }; - VkPipeline sceneShadow{ VK_NULL_HANDLE }; - // Pipeline with percentage close filtering (PCF) of the shadow map - VkPipeline sceneShadowPCF{ VK_NULL_HANDLE }; - VkPipeline debug{ VK_NULL_HANDLE }; - } pipelines; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - - struct { - VkDescriptorSet offscreen{ VK_NULL_HANDLE }; - VkDescriptorSet scene{ VK_NULL_HANDLE }; - VkDescriptorSet debug{ VK_NULL_HANDLE }; - } descriptorSets; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // 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{}; - - // 16 bits of depth is enough for such a small scene - const VkFormat offscreenDepthFormat{ VK_FORMAT_D16_UNORM }; - // Shadow map dimension -#if defined(__ANDROID__) - // Use a smaller size on Android for performance reasons - const uint32_t shadowMapize{ 1024 }; -#else - const uint32_t shadowMapize{ 2048 }; -#endif - - VulkanExample() : VulkanExampleBase() - { - title = "Projected shadow mapping"; - camera.type = Camera::CameraType::lookat; - camera.setPosition(glm::vec3(0.0f, 0.0f, -12.5f)); - camera.setRotation(glm::vec3(-25.0f, -390.0f, 0.0f)); - camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f); - timerSpeed *= 0.5f; - } - - ~VulkanExample() - { - if (device) { - // 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 = offscreenDepthFormat; - 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 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(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 = shadowMapize; - offscreenPass.height = shadowMapize; - - // 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 = offscreenDepthFormat; // 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 = offscreenDepthFormat; - 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, offscreenDepthFormat, VK_IMAGE_TILING_OPTIMAL) ? VK_FILTER_LINEAR : 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); - } else { - // Render the shadows 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : Vertex shader 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 (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)); - - // Sets - std::vector 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 0 : Parameters uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.debug, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor), - // Binding 1 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSets.debug, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &shadowMapDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(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, static_cast(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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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; - // Disable culling, so all faces contribute to shadows - rasterizationStateCI.cullMode = VK_CULL_MODE_NONE; - 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); - - 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(UniformDataOffscreen))); - // 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(UniformDataScene))); - // 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() - { - uniformDataScene.projection = camera.matrices.perspective; - uniformDataScene.view = camera.matrices.view; - uniformDataScene.model = glm::mat4(1.0f); - uniformDataScene.lightPos = glm::vec4(lightPos, 1.0f); - uniformDataScene.depthBiasMVP = uniformDataOffscreen.depthMVP; - uniformDataScene.zNear = zNear; - uniformDataScene.zFar = zFar; - memcpy(uniformBuffers.scene.mapped, &uniformDataScene, sizeof(uniformDataScene)); - } - - 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); - - uniformDataOffscreen.depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix; - - memcpy(uniformBuffers.offscreen.mapped, &uniformDataOffscreen, sizeof(uniformDataOffscreen)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareOffscreenFramebuffer(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - if (!paused || camera.updated) { - updateLight(); - updateUniformBufferOffscreen(); - updateUniformBuffers(); - } - draw(); - } - - 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/examples/shadowmappingcascade/shadowmappingcascade.cpp b/examples/shadowmappingcascade/shadowmappingcascade.cpp deleted file mode 100644 index 55c6328b..00000000 --- a/examples/shadowmappingcascade/shadowmappingcascade.cpp +++ /dev/null @@ -1,785 +0,0 @@ -/* - Vulkan Example - Cascaded shadow mapping for directional light sources - Copyright (c) 2016-2025 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" - -#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 inverseViewMat; - glm::vec3 lightDir; - float _pad; - int32_t colorCascades; - } uboFS; - - VkPipelineLayout pipelineLayout; - struct Pipelines { - VkPipeline debugShadowMap; - VkPipeline sceneShadow; - VkPipeline sceneShadowPCF; - } pipelines; - - VkDescriptorSetLayout descriptorSetLayout; - 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; - } 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; - VkImageView view; - float splitDepth; - glm::mat4 viewProjMatrix; - void destroy(VkDevice device) { - vkDestroyImageView(device, view, nullptr); - vkDestroyFramebuffer(device, frameBuffer, nullptr); - } - }; - std::array cascades; - // Per-cascade matrices will be passed to the shaders as a linear array - vks::Buffer cascadeViewProjMatricesBuffer; - - VulkanExample() : VulkanExampleBase() - { - 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, descriptorSetLayout, nullptr); - - cascadeViewProjMatricesBuffer.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 to acommand buffer using the supplied pipeline layout and for the selected shadow cascade index - Used by the scene rendering and depth pass generation command buffer - */ - void renderScene(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, 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 | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); - models.terrain.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout); - - // Trees - const std::vector 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 | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); - // This will also bind the texture images to set 1 - 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 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(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 view 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(); - - 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 - 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, 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 | VK_SHADER_STAGE_FRAGMENT_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); - - 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 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(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 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), - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 3), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - /* - Descriptor sets - */ - - VkDescriptorImageInfo depthMapDescriptor = - vks::initializers::descriptorImageInfo(depth.sampler, depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL); - - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - - // Scene rendering / debug display - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - const std::vector 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), - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, &cascadeViewProjMatricesBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(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 | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0); - std::array setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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 | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 0); - std::array setLayouts = { descriptorSetLayout, vkglTF::descriptorSetLayoutImage }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() - { - // Cascade matrices - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &cascadeViewProjMatricesBuffer, - sizeof(glm::mat4) * SHADOW_MAP_CASCADE_COUNT)); - - // 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(cascadeViewProjMatricesBuffer.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(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, 0.0f), - glm::vec3( 1.0f, 1.0f, 0.0f), - glm::vec3( 1.0f, -1.0f, 0.0f), - glm::vec3(-1.0f, -1.0f, 0.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 j = 0; j < 8; j++) { - glm::vec4 invCorner = invCam * glm::vec4(frustumCorners[j], 1.0f); - frustumCorners[j] = invCorner / invCorner.w; - } - - for (uint32_t j = 0; j < 4; j++) { - glm::vec3 dist = frustumCorners[j + 4] - frustumCorners[j]; - frustumCorners[j + 4] = frustumCorners[j] + (dist * splitDist); - frustumCorners[j] = frustumCorners[j] + (dist * lastSplitDist); - } - - // Get frustum center - glm::vec3 frustumCenter = glm::vec3(0.0f); - for (uint32_t j = 0; j < 8; j++) { - frustumCenter += frustumCorners[j]; - } - frustumCenter /= 8.0f; - - float radius = 0.0f; - for (uint32_t j = 0; j < 8; j++) { - float distance = glm::length(frustumCorners[j] - 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 - */ - std::vector cascadeViewProjMatrices(SHADOW_MAP_CASCADE_COUNT); - for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { - cascadeViewProjMatrices[i] = cascades[i].viewProjMatrix; - } - memcpy(cascadeViewProjMatricesBuffer.mapped, cascadeViewProjMatrices.data(), sizeof(glm::mat4) * SHADOW_MAP_CASCADE_COUNT); - - /* - 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.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/examples/shadowmappingomni/shadowmappingomni.cpp b/examples/shadowmappingomni/shadowmappingomni.cpp deleted file mode 100644 index 123e5293..00000000 --- a/examples/shadowmappingomni/shadowmappingomni.cpp +++ /dev/null @@ -1,708 +0,0 @@ -/* -* Vulkan Example - Omni directional shadows using a dynamic cube map -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool displayCubeMap{ false }; - - // Defines the depth range used for the shadow maps - // This should be kept as small as possible for precision - float zNear{ 0.1f }; - float zFar{ 1024.0f }; - - struct { - vkglTF::Model scene; - vkglTF::Model debugcube; - } models; - - glm::vec4 lightPos = glm::vec4(0.0f, -2.5f, 0.0f, 1.0); - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - glm::vec4 lightPos; - }; - UniformData uniformDataScene, uniformDataOffscreen; - - struct { - vks::Buffer scene; - vks::Buffer offscreen; - } uniformBuffers; - - struct { - VkPipeline scene{ VK_NULL_HANDLE }; - VkPipeline offscreen{ VK_NULL_HANDLE }; - VkPipeline cubemapDisplay{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout scene{ VK_NULL_HANDLE }; - VkPipelineLayout offscreen{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet scene{ VK_NULL_HANDLE }; - VkDescriptorSet offscreen{ VK_NULL_HANDLE }; - } descriptorSets; - - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - vks::Texture shadowCubeMap; - std::array shadowCubeMapFaceImageViews{}; - - // Framebuffer for offscreen rendering - struct FrameBufferAttachment { - VkImage image; - VkDeviceMemory mem; - VkImageView view; - }; - struct OffscreenPass { - int32_t width, height; - std::array frameBuffers; - FrameBufferAttachment depth; - VkRenderPass renderPass; - VkSampler sampler; - VkDescriptorImageInfo descriptor; - } offscreenPass; - - // Size of the shadow map texture (per face) - const uint32_t offscreenImageSize{ 1024 }; - // We use a 32 bit float format for max. precision. Depending on the use case, lower precision may be fine and can save bandwidth - const VkFormat offscreenImageFormat{ VK_FORMAT_R32_SFLOAT }; - // The depth format is selected at runtime - VkFormat offscreenDepthFormat{ VK_FORMAT_UNDEFINED }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // Cube map - for (uint32_t i = 0; i < 6; i++) { - vkDestroyImageView(device, shadowCubeMapFaceImageViews[i], nullptr); - } - - vkDestroyImageView(device, shadowCubeMap.view, nullptr); - vkDestroyImage(device, shadowCubeMap.image, nullptr); - vkDestroySampler(device, shadowCubeMap.sampler, nullptr); - vkFreeMemory(device, shadowCubeMap.deviceMemory, nullptr); - - // Depth attachment - vkDestroyImageView(device, offscreenPass.depth.view, nullptr); - vkDestroyImage(device, offscreenPass.depth.image, nullptr); - vkFreeMemory(device, offscreenPass.depth.mem, nullptr); - - for (uint32_t i = 0; i < 6; i++) - { - vkDestroyFramebuffer(device, offscreenPass.frameBuffers[i], 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 = offscreenImageSize; - shadowCubeMap.height = offscreenImageSize; - - // Cube map image description - VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = offscreenImageFormat; - 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_COLOR_ATTACHMENT_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 = 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 = 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 = offscreenImageFormat; - 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)); - - view.viewType = VK_IMAGE_VIEW_TYPE_2D; - view.subresourceRange.layerCount = 1; - view.image = shadowCubeMap.image; - - for (uint32_t i = 0; i < 6; i++) - { - view.subresourceRange.baseArrayLayer = i; - VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &shadowCubeMapFaceImageViews[i])); - } - } - - // 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 for - VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &offscreenDepthFormat); - assert(validDepthFormat); - - osAttachments[0].format = offscreenImageFormat; - 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_SHADER_READ_ONLY_OPTIMAL; - osAttachments[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - // Depth attachment - osAttachments[1].format = offscreenDepthFormat; - 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)); - } - - // 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 = offscreenImageSize; - offscreenPass.height = offscreenImageSize; - - // Color attachment - VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = offscreenImageFormat; - 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; - - VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo(); - colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D; - colorImageView.format = offscreenImageFormat; - 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; - - VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - // Depth stencil attachment - imageCreateInfo.format = offscreenDepthFormat; - 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 = offscreenDepthFormat; - depthStencilView.flags = 0; - depthStencilView.subresourceRange = {}; - depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - if (offscreenDepthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) { - depthStencilView.subresourceRange.aspectMask |= 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)); - - VkMemoryRequirements memReqs; - vkGetImageMemoryRequirements(device, offscreenPass.depth.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, &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[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; - - for (uint32_t i = 0; i < 6; i++) - { - attachments[0] = shadowCubeMapFaceImageViews[i]; - VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffers[i])); - } - } - - // Updates a single cube map face - // Renders the scene with face's view directly to the cubemap layer `faceIndex` - // 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.frameBuffers[faceIndex]; - 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); - } - - 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); - - 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 setupDescriptors() - { - // Pool - std::vector 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, 3); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Sets - 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 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, static_cast(sceneDescriptorSets.size()), sceneDescriptorSets.data(), 0, nullptr); - - // Offscreen - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen)); - std::vector offScreenWriteDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.offscreen, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(offScreenWriteDescriptorSets.size()), offScreenWriteDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - // 3D scene pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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 - pipelineLayoutCreateInfo.pushConstantRangeCount = 1; - pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.offscreen)); - - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - // 3D scene pipeline - // Load shaders - std::array 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 = static_cast(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(UniformData))); - // 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(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffers.offscreen.map()); - VK_CHECK_RESULT(uniformBuffers.scene.map()); - } - - void updateUniformBuffers() - { - uniformDataScene.projection = camera.matrices.perspective; - uniformDataScene.view = camera.matrices.view; - uniformDataScene.model = glm::mat4(1.0f); - uniformDataScene.lightPos = lightPos; - memcpy(uniformBuffers.scene.mapped, &uniformDataScene, sizeof(UniformData)); - } - - void updateUniformBufferOffscreen() - { - lightPos.x = sin(glm::radians(timer * 360.0f)) * 0.15f; - lightPos.z = cos(glm::radians(timer * 360.0f)) * 0.15f; - uniformDataOffscreen.projection = glm::perspective((float)(M_PI / 2.0), 1.0f, zNear, zFar); - uniformDataOffscreen.view = glm::mat4(1.0f); - uniformDataOffscreen.model = glm::translate(glm::mat4(1.0f), glm::vec3(-lightPos.x, -lightPos.y, -lightPos.z)); - uniformDataOffscreen.lightPos = lightPos; - memcpy(uniformBuffers.offscreen.mapped, &uniformDataOffscreen, sizeof(UniformData)); - } - - 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(); - setupDescriptors(); - prepareOffscreenRenderpass(); - preparePipelines(); - prepareOffscreenFramebuffer(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - updateUniformBufferOffscreen(); - draw(); - } - - 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/examples/specializationconstants/specializationconstants.cpp b/examples/specializationconstants/specializationconstants.cpp deleted file mode 100644 index db357981..00000000 --- a/examples/specializationconstants/specializationconstants.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/* -* Vulkan Example - Shader specialization constants -* -* This samples uses specialization constants to define shader constants at pipeline creation -* These are used to compile shaders with different execution paths and settings -* With these constants one can create different shader configurations from a single shader file -* See uber.frag for how such a shader can look -* -* For details see https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt -* -* Copyright (C) 2016-2023 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" - -class VulkanExample: public VulkanExampleBase -{ -public: - vkglTF::Model scene; - vks::Texture2D colormap; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos{ 0.0f, -2.0f, 1.0f, 0.0f }; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - struct Pipelines{ - VkPipeline phong{ VK_NULL_HANDLE }; - VkPipeline toon{ VK_NULL_HANDLE }; - VkPipeline textured{ VK_NULL_HANDLE }; - } pipelines; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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); - - // 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 setupDescriptors() - { - // Pool - std::vector 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, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 constants data - - // Host data to take specialization constants from - struct SpecializationData { - // Sets the lighting model used in the fragment "uber" shader - uint32_t lightingModel{ 0 }; - // 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 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(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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - camera.setPerspective(60.0f, ((float)width / 3.0f) / (float)height, 0.1f, 512.0f); - - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - } - updateUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/sphericalenvmapping/sphericalenvmapping.cpp b/examples/sphericalenvmapping/sphericalenvmapping.cpp deleted file mode 100644 index a4f3b3e6..00000000 --- a/examples/sphericalenvmapping/sphericalenvmapping.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* -* Vulkan Example - Spherical Environment Mapping, using different mat caps stored in an array texture -* -* Use +/-/space toggle through different material captures -* -* Based on https://www.clicktorelease.com/blog/creating-spherical-environment-mapping-shader -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - vkglTF::Model model; - vks::Texture2DArray matCapTextureArray; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 normal; - glm::mat4 view; - // Selects the texture index of the material to display (see sem.vert) - int32_t texIndex = 0; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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 array texture 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 setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector 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, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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(UniformData))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - uniformData.normal = glm::inverseTranspose(uniformData.view * uniformData.model); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->sliderInt("Material cap", &uniformData.texIndex, 0, matCapTextureArray.layerCount); - } - } - -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/ssao/ssao.cpp b/examples/ssao/ssao.cpp deleted file mode 100644 index 76e84953..00000000 --- a/examples/ssao/ssao.cpp +++ /dev/null @@ -1,953 +0,0 @@ -/* -* 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 SSAO_KERNEL_SIZE 64 -#define SSAO_RADIUS 0.3f - -// We use a smaller noise kernel size on Android due to lower computational power -#if defined(__ANDROID__) -#define SSAO_NOISE_DIM 4 -#else -#define SSAO_NOISE_DIM 8 -#endif - -class VulkanExample : public VulkanExampleBase -{ -public: - vks::Texture2D ssaoNoise; - 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{ VK_NULL_HANDLE }; - VkPipeline composition{ VK_NULL_HANDLE }; - VkPipeline ssao{ VK_NULL_HANDLE }; - VkPipeline ssaoBlur{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkPipelineLayout gBuffer{ VK_NULL_HANDLE }; - VkPipelineLayout ssao{ VK_NULL_HANDLE }; - VkPipelineLayout ssaoBlur{ VK_NULL_HANDLE }; - VkPipelineLayout composition{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet gBuffer{ VK_NULL_HANDLE }; - VkDescriptorSet ssao{ VK_NULL_HANDLE }; - VkDescriptorSet ssaoBlur{ VK_NULL_HANDLE }; - VkDescriptorSet composition{ VK_NULL_HANDLE }; - const uint32_t count = 4; - } descriptorSets; - - struct { - VkDescriptorSetLayout gBuffer{ VK_NULL_HANDLE }; - VkDescriptorSetLayout ssao{ VK_NULL_HANDLE }; - VkDescriptorSetLayout ssaoBlur{ VK_NULL_HANDLE }; - VkDescriptorSetLayout composition{ VK_NULL_HANDLE }; - } 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() - { - 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() - { - if (device) { - 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(); - - 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; - - attachment->format = format; - - if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) - { - aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - } - if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) - { - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - if (format >= VK_FORMAT_D16_UNORM_S8_UINT) - aspectMask |=VK_IMAGE_ASPECT_STENCIL_BIT; - } - - 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 attachmentDescs = {}; - - // Init attachment properties - for (uint32_t i = 0; i < static_cast(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 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(colorReferences.size()); - subpass.pDepthStencilAttachment = &depthReference; - - // Use subpass dependencies for attachment layout transitions - std::array 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(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 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(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 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 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(); - - 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 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(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.gBuffer, 0, nullptr); - 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, nullptr); - 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, nullptr); - 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 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 setupDescriptors() - { - // Pool - std::vector 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)); - - std::vector setLayoutBindings; - VkDescriptorSetLayoutCreateInfo setLayoutCreateInfo; - VkDescriptorSetAllocateInfo descriptorAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, nullptr, 1); - std::vector writeDescriptorSets; - std::vector imageDescriptors; - - // Layouts and Sets - - // 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 - }; - setLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.gBuffer)); - - descriptorAllocInfo.pSetLayouts = &descriptorSetLayouts.gBuffer; - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSets.gBuffer)); - writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSets.gBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.sceneParams.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.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, &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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(); - - const std::vector setLayouts = { descriptorSetLayouts.gBuffer, vkglTF::descriptorSetLayoutImage }; - pipelineLayoutCreateInfo.pSetLayouts = setLayouts.data(); - pipelineLayoutCreateInfo.setLayoutCount = 2; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.gBuffer)); - - pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.ssao; - pipelineLayoutCreateInfo.setLayoutCount = 1; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.ssao)); - - pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.ssaoBlur; - pipelineLayoutCreateInfo.setLayoutCount = 1; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.ssaoBlur)); - - pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.composition; - pipelineLayoutCreateInfo.setLayoutCount = 1; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 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 blendAttachmentStates = { - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE), - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE) - }; - colorBlendState.attachmentCount = static_cast(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 rndDist(0.0f, 1.0f); - - // Sample kernel - std::vector 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 noiseValues(SSAO_NOISE_DIM * SSAO_NOISE_DIM); - for (uint32_t i = 0; i < static_cast(noiseValues.size()); i++) { - noiseValues[i] = glm::vec4(rndDist(rndEngine) * 2.0f - 1.0f, rndDist(rndEngine) * 2.0f - 1.0f, 0.0f, 0.0f); - } - // Upload as texture - ssaoNoise.fromBuffer(noiseValues.data(), noiseValues.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 prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareOffscreenFramebuffers(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - } - updateUniformBufferMatrices(); - updateUniformBufferSSAOParams(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->checkBox("Enable SSAO", &uboSSAOParams.ssao); - overlay->checkBox("SSAO blur", &uboSSAOParams.ssaoBlur); - overlay->checkBox("SSAO pass only", &uboSSAOParams.ssaoOnly); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/stencilbuffer/stencilbuffer.cpp b/examples/stencilbuffer/stencilbuffer.cpp deleted file mode 100644 index 68b6c08a..00000000 --- a/examples/stencilbuffer/stencilbuffer.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* -* Vulkan Example - Rendering outlines using the stencil buffer -* -* Copyright (C) 2016-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - vkglTF::Model model; - - struct UniformData { - 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; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline stencil{ VK_NULL_HANDLE }; - VkPipeline outline{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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)); - // This samples requires a format that supports depth AND stencil, which will be handled by the base class - requiresStencil = true; - } - - ~VulkanExample() - { - if (device) { - vkDestroyPipeline(device, pipelines.stencil, nullptr); - vkDestroyPipeline(device, pipelines.outline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - 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) - { - 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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector modelWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, &uniformBuffer, sizeof(UniformData), &uniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.model = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->inputFloat("Outline width", &uniformData.outlineWidth, 0.01f, 2)) { - updateUniformBuffers(); - } - } - } - -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/subpasses/subpasses.cpp b/examples/subpasses/subpasses.cpp deleted file mode 100644 index e28d32d9..00000000 --- a/examples/subpasses/subpasses.cpp +++ /dev/null @@ -1,832 +0,0 @@ -/* - * Vulkan Example - Using subpasses for G-Buffer compositing - * - * Copyright (C) 2016-2024 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" - -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; - }; - - std::array lights; - - struct { - vks::Buffer GBuffer; - vks::Buffer lights; - } buffers; - - 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() - { - 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); - ui.subpass = 2; - - enabledFeatures.fragmentStoresAndAtomics = VK_TRUE; - } - - ~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(); - buffers.GBuffer.destroy(); - buffers.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; - - attachment->format = format; - - if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) - { - aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - } - if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) - { - aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - } - - 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 writeDescriptorSets; - for (size_t i = 0; i < descriptorImageInfos.size(); i++) { - writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, static_cast(i), &descriptorImageInfos[i])); - } - vkUpdateDescriptorSets(device, static_cast(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(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.images.size()); - for (uint32_t i = 0; i < frameBuffers.size(); i++) - { - attachments[0] = swapChain.imageViews[i]; - 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 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 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 }; - - 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 dependencies; - - // This makes sure that writes to the depth image are done before we try to write to it again - dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;; - dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;; - dependencies[0].srcAccessMask = 0; - dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[0].dependencyFlags = 0; - - dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].dstSubpass = 0; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].srcAccessMask = 0; - dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependencies[1].dependencyFlags = 0; - - // This dependency transitions the input attachment from color attachment to input attachment read - dependencies[2].srcSubpass = 0; - dependencies[2].dstSubpass = 1; - 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_INPUT_ATTACHMENT_READ_BIT; - dependencies[2].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - dependencies[3].srcSubpass = 1; - dependencies[3].dstSubpass = 2; - dependencies[3].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[3].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - dependencies[3].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependencies[3].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; - dependencies[3].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - dependencies[4].srcSubpass = 2; - dependencies[4].dstSubpass = VK_SUBPASS_EXTERNAL; - dependencies[4].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[4].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - dependencies[4].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dependencies[4].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[4].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = static_cast(attachments.size()); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = static_cast(subpassDescriptions.size()); - renderPassInfo.pSubpasses = subpassDescriptions.data(); - renderPassInfo.dependencyCount = static_cast(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); - - // First sub pass - // Renders the components of the scene to the G-Buffer attachments - { - vks::debugutils::cmdBeginLabel(drawCmdBuffers[i], "Subpass 0: Deferred G-Buffer creation", { 1.0f, 0.78f, 0.05f, 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::debugutils::cmdEndLabel(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::debugutils::cmdBeginLabel(drawCmdBuffers[i], "Subpass 1: Deferred composition", { 0.0f, 0.5f, 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::debugutils::cmdEndLabel(drawCmdBuffers[i]); - } - - // Third subpass - // Render transparent geometry using a forward pass that compares against depth generated during G-Buffer fill - { - vks::debugutils::cmdBeginLabel(drawCmdBuffers[i], "Subpass 2: Forward transparency", { 0.5f, 0.76f, 0.34f, 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::debugutils::cmdEndLabel(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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1), - 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(poolSizes.size()), poolSizes.data(), 4); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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, &descriptorSetLayouts.scene)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene)); - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &buffers.GBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.offscreen)); - - // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 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(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 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_STORAGE_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 3), - }; - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - static_cast(setLayoutBindings.size())); - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.composition)); - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.composition, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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 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_STORAGE_BUFFER, 3, &buffers.lights.descriptor), - }; - - vkUpdateDescriptorSets(device, static_cast(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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); - - 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(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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.transparent)); - - // Pipeline layout - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.transparent, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, &buffers.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(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() - { - // Matrices - vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &buffers.GBuffer, sizeof(uboGBuffer)); - VK_CHECK_RESULT(buffers.GBuffer.map()); - - // Lights - vulkanDevice->createBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &buffers.lights, lights.size() * sizeof(Light)); - VK_CHECK_RESULT(buffers.lights.map()); - - // Update - updateUniformBufferDeferredMatrices(); - } - - void updateUniformBufferDeferredMatrices() - { - uboGBuffer.projection = camera.matrices.perspective; - uboGBuffer.view = camera.matrices.view; - uboGBuffer.model = glm::mat4(1.0f); - memcpy(buffers.GBuffer.mapped, &uboGBuffer, sizeof(uboGBuffer)); - } - - void initLights() - { - std::vector 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::random_device rndDevice; - std::default_random_engine rndGen(benchmark.active ? 0 : rndDevice()); - std::uniform_real_distribution rndDist(-1.0f, 1.0f); - std::uniform_real_distribution rndCol(0.0f, 0.5f); - - for (auto& light : lights) - { - light.position = glm::vec4(rndDist(rndGen) * 8.0f, 0.25f + std::abs(rndDist(rndGen)) * 4.0f, rndDist(rndGen) * 8.0f, 1.0f); - //light.color = colors[rndCol(rndGen)]; - light.color = glm::vec3(rndCol(rndGen), rndCol(rndGen), rndCol(rndGen)) * 2.0f; - light.radius = 1.0f + std::abs(rndDist(rndGen)); - } - - memcpy(buffers.lights.mapped, lights.data(), lights.size() * sizeof(Light)); - } - - 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(); - initLights(); - setupDescriptors(); - preparePipelines(); - prepareCompositionPass(); - buildCommandBuffers(); - prepared = true; - } - - virtual void render() - { - if (!prepared) - return; - if (camera.updated) { - updateUniformBufferDeferredMatrices(); - } - draw(); - } - - 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(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/terraintessellation/terraintessellation.cpp b/examples/terraintessellation/terraintessellation.cpp deleted file mode 100644 index adc5ba32..00000000 --- a/examples/terraintessellation/terraintessellation.cpp +++ /dev/null @@ -1,752 +0,0 @@ -/* -* Vulkan Example - Dynamic terrain tessellation -* -* This samples draw a terrain from a heightmap texture and uses tessellation to add in details based on camera distance -* The height level is generated in the vertex shader by reading from the heightmap image -* -* Copyright (C) 2016-2023 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 -#include - -class VulkanExample : public VulkanExampleBase -{ -public: - bool wireframe = false; - bool tessellation = true; - - // Holds the buffers for rendering the tessellated terrain - struct { - struct Vertices { - VkBuffer buffer{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - } vertices; - struct Indices { - int count; - VkBuffer buffer{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - } 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 UniformDataTessellation { - 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; - } uniformDataTessellation; - - // Skysphere vertex shader stage - struct UniformDataVertex { - glm::mat4 mvp; - } uniformDataVertex; - - struct Pipelines { - VkPipeline terrain{ VK_NULL_HANDLE }; - VkPipeline wireframe{ VK_NULL_HANDLE }; - VkPipeline skysphere{ VK_NULL_HANDLE }; - } pipelines; - - struct { - VkDescriptorSetLayout terrain{ VK_NULL_HANDLE }; - VkDescriptorSetLayout skysphere{ VK_NULL_HANDLE }; - } descriptorSetLayouts; - - struct { - VkPipelineLayout terrain{ VK_NULL_HANDLE }; - VkPipelineLayout skysphere{ VK_NULL_HANDLE }; - } pipelineLayouts; - - struct { - VkDescriptorSet terrain{ VK_NULL_HANDLE }; - VkDescriptorSet skysphere{ VK_NULL_HANDLE }; - } descriptorSets; - - // If supported, this sample will gather pipeline statistics to show e.g. tessellation related information - struct { - VkBuffer buffer{ VK_NULL_HANDLE }; - VkDeviceMemory memory{ VK_NULL_HANDLE }; - } 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() - { - 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 = 10.0f; - } - - ~VulkanExample() - { - if (device) { - 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; - }; - // Enable pipeline statistics if supported (to display them in the UI) - if (deviceFeatures.pipelineStatisticsQuery) { - enabledFeatures.pipelineStatisticsQuery = VK_TRUE; - }; - // Enable anisotropic filtering if supported - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - } - } - - // Setup a pool and a 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])); - } - } - - // Generate a terrain quad patch with normals based on heightmap data - void generateTerrain() - { - const uint32_t patchSize{ 64 }; - const float uvScale{ 1.0f }; - - uint16_t* heightdata; - uint32_t dim; - uint32_t scale; - - ktxResult result; - ktxTexture* ktxTexture; - - // We load the heightmap from an un-compressed ktx image with one channel that contains heights - std::string filename = getAssetPath() + "textures/terrain_heightmap_r16.ktx"; -#if defined(__ANDROID__) - // On Android we need to load the file 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); - 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); - scale = dim / patchSize; - ktxTexture_Destroy(ktxTexture); - - const uint32_t vertexCount = patchSize * patchSize; - // 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; - - // Generate a two-dimensional vertex patch - for (auto x = 0; x < patchSize; x++) { - for (auto 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; - vertices[index].pos[1] = 0.0f; - vertices[index].pos[2] = y * wy + wy / 2.0f - (float)patchSize * wy / 2.0f; - vertices[index].uv = glm::vec2((float)x / (patchSize - 1), (float)y / (patchSize - 1)) * uvScale; - } - } - - // Calculate normals from the height map using a sobel filter - for (auto x = 0; x < patchSize; x++) { - for (auto y = 0; y < patchSize; y++) { - // We get - float heights[3][3]; - for (auto sx = -1; sx <= 1; sx++) { - for (auto sy = -1; sy <= 1; sy++) { - // Get height at sampled position from heightmap - glm::ivec2 rpos = glm::ivec2(x + sx, y + sy) * 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); - heights[sx + 1][sy + 1] = *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f; - } - } - 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 * patchSize].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f)); - } - } - - delete[] heightdata; - - // Generate indices - const uint32_t w = (patchSize - 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 * patchSize); - indices[index + 1] = indices[index] + patchSize; - indices[index + 2] = indices[index + 1] + 1; - indices[index + 3] = indices[index] + 1; - } - } - terrain.indices.count = indexCount; - - // Upload vertices and indices to device - - uint32_t vertexBufferSize = vertexCount * sizeof(vkglTF::Vertex); - uint32_t indexBufferSize = indexCount * sizeof(uint32_t); - - struct { - VkBuffer buffer; - VkDeviceMemory memory; - } vertexStaging, indexStaging; - - // Stage the terrain vertex data to the device - - 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, - ©Region); - - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - terrain.indices.buffer, - 1, - ©Region); - - 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 setupDescriptors() - { - // Pool - std::vector 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, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layouts - VkDescriptorSetLayoutCreateInfo descriptorLayout; - std::vector 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 2 : 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.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(setLayoutBindings.size())); - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.skysphere)); - // Sets - VkDescriptorSetAllocateInfo allocInfo; - std::vector 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 : Height map - vks::initializers::writeDescriptorSet(descriptorSets.terrain, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.heightMap.descriptor), - // Binding 2 : Terrain texture array layers - vks::initializers::writeDescriptorSet(descriptorSets.terrain, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.terrainArray.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layouts - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo; - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.terrain, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.terrain)); - - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.skysphere, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.skysphere)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 devie supports it) - 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(UniformDataTessellation))); - - // 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(UniformDataVertex))); - - // Map persistent - VK_CHECK_RESULT(uniformBuffers.terrainTessellation.map()); - VK_CHECK_RESULT(uniformBuffers.skysphereVertex.map()); - } - - void updateUniformBuffers() - { - // Tessellation - uniformDataTessellation.projection = camera.matrices.perspective; - uniformDataTessellation.modelview = camera.matrices.view * glm::mat4(1.0f); - uniformDataTessellation.lightPos.y = -0.5f - uniformDataTessellation.displacementFactor; // todo: Not uesed yet - uniformDataTessellation.viewportDim = glm::vec2((float)width, (float)height); - - frustum.update(uniformDataTessellation.projection * uniformDataTessellation.modelview); - memcpy(uniformDataTessellation.frustumPlanes, frustum.planes.data(), sizeof(glm::vec4) * 6); - - float savedFactor = uniformDataTessellation.tessellationFactor; - if (!tessellation) - { - // Setting this to zero sets all tessellation factors to 1.0 in the shader - uniformDataTessellation.tessellationFactor = 0.0f; - } - - memcpy(uniformBuffers.terrainTessellation.mapped, &uniformDataTessellation, sizeof(UniformDataTessellation)); - - if (!tessellation) - { - uniformDataTessellation.tessellationFactor = savedFactor; - } - - // Vertex shader - uniformDataVertex.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view)); - memcpy(uniformBuffers.skysphereVertex.mapped, &uniformDataVertex, sizeof(UniformDataVertex)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - generateTerrain(); - if (deviceFeatures.pipelineStatisticsQuery) { - setupQueryResultBuffer(); - } - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; - } - - 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 (if the device supports pipeline statistics) - if (deviceFeatures.pipelineStatisticsQuery) { - getQueryResults(); - } - VulkanExampleBase::submitFrame(); - } - - virtual void render() - { - if (!prepared) - return; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - - if (overlay->checkBox("Tessellation", &tessellation)) { - updateUniformBuffers(); - } - if (overlay->inputFloat("Factor", &uniformDataTessellation.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() diff --git a/examples/tessellation/tessellation.cpp b/examples/tessellation/tessellation.cpp deleted file mode 100644 index b3aa2040..00000000 --- a/examples/tessellation/tessellation.cpp +++ /dev/null @@ -1,317 +0,0 @@ -/* -* 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-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - bool splitScreen = true; - bool wireframe = true; - - vkglTF::Model model; - - // One uniform data block is used by both tessellation shader stages - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - float tessAlpha = 1.0f; - float tessLevel = 3.0f; - } uniformData; - vks::Buffer uniformBuffer; - - struct Pipelines { - VkPipeline solid{ VK_NULL_HANDLE }; - VkPipeline wire{ VK_NULL_HANDLE }; - VkPipeline solidPassThrough{ VK_NULL_HANDLE }; - VkPipeline wirePassThrough{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - // 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); - - uniformBuffer.destroy(); - } - } - - // Enable physical device features required for this example - virtual void getEnabledFeatures() - { - // Example requires 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 setupDescriptors() - { - // Pool - const std::vector 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)); - - // Layout - const std::vector setLayoutBindings = { - // Binding 0 : Tessellation shader ubo - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 0), - }; - VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Tessellation shader ubo - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout uses set 0 for passing tessellation shader ubos and set 1 for fragment shader images (taken from glTF model) - const std::vector setLayouts = { - descriptorSetLayout, - vkglTF::descriptorSetLayoutImage, - }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 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 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 = static_cast(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, &uniformBuffer, sizeof(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - // Adjust camera perspective if split screen is enabled - camera.setPerspective(45.0f, (float)(width * ((splitScreen) ? 0.5f : 1.0f)) / (float)height, 0.1f, 256.0f); - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - // Tessellation evaluation uniform block - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->inputFloat("Tessellation level", &uniformData.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/examples/textoverlay/textoverlay.cpp b/examples/textoverlay/textoverlay.cpp deleted file mode 100644 index 648f3058..00000000 --- a/examples/textoverlay/textoverlay.cpp +++ /dev/null @@ -1,719 +0,0 @@ -/* -* Vulkan Example - Text overlay rendering on-top of an existing scene using a separate render pass -* -* This sample renders a basic text overlay on top of a 3D scene that can be used e.g. for debug purposes -* For a more complete GUI sample see the ImGui sample -* -* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include -#include -#include "vulkanexamplebase.h" -#include "VulkanglTFModel.h" -#include "../external/stb/stb_font_consolas_24_latin1.inl" - -// Max. number of chars the text overlay buffer can hold -#define TEXTOVERLAY_MAX_CHAR_COUNT 2048 - -/* - Mostly self-contained text overlay class - This class contains all Vulkan resources for drawing the text overlay - It can be plugged into an existing renderpass/command buffer -*/ -class TextOverlay -{ -private: - // Created by this class - // Font image - VkSampler sampler; - VkImage image; - VkImageView view; - VkDeviceMemory imageMemory; - // Character vertex buffer - VkBuffer buffer; - VkDeviceMemory memory; - VkDescriptorPool descriptorPool; - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - VkPipelineCache pipelineCache; - VkPipeline pipeline; - - // Passed from the sample - VkRenderPass renderPass; - VkQueue queue; - vks::VulkanDevice* vulkanDevice; - uint32_t* frameBufferWidth; - uint32_t* frameBufferHeight; - std::vector shaderStages; - float scale; - - // Pointer to mapped vertex buffer - glm::vec4 *mapped = nullptr; - - stb_fontchar stbFontData[STB_FONT_consolas_24_latin1_NUM_CHARS]; -public: - enum TextAlign { alignLeft, alignCenter, alignRight }; - - uint32_t numLetters; - bool visible = true; - - TextOverlay( - vks::VulkanDevice *vulkanDevice, - VkQueue queue, - VkRenderPass renderPass, - uint32_t *framebufferwidth, - uint32_t *framebufferheight, - float scale, - std::vector shaderstages) - { - this->vulkanDevice = vulkanDevice; - this->queue = queue; - this->shaderStages = shaderstages; - this->frameBufferWidth = framebufferwidth; - this->frameBufferHeight = framebufferheight; - this->scale = scale; - this->renderPass = renderPass; - - prepareResources(); - 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); - } - - // 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_HEIGHT; - - static unsigned char font24pixels[fontHeight][fontWidth]; - stb_font_consolas_24_latin1(stbFontData, font24pixels, fontHeight); - - // 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 = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - // 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); - - vulkanDevice->flushCommandBuffer(copyCmd, queue); - - 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 poolSizes; - poolSizes[0] = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - static_cast(poolSizes.size()), - poolSizes.data(), - 1); - - VK_CHECK_RESULT(vkCreateDescriptorPool(vulkanDevice->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor set layout - std::array 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(setLayoutBindings.size())); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout)); - - // Descriptor set - VkDescriptorSetAllocateInfo descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &descriptorSetAllocInfo, &descriptorSet)); - - // Descriptor for the font image - VkDescriptorImageInfo texDescriptor = vks::initializers::descriptorImageInfo(sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - std::array writeDescriptorSets; - writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor); - vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - } - - // Prepare a separate pipeline for the font rendering decoupled from the main application - void preparePipeline() - { - // Pipeline cache - VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; - pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - VK_CHECK_RESULT(vkCreatePipelineCache(vulkanDevice->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); - - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(vulkanDevice->logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout)); - - // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - std::array 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 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(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(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(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(vulkanDevice->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); - } - - // 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 - 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 * scale / *frameBufferWidth; - const float charH = 1.5f * scale / *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; - case alignLeft: - 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(); - } - - // Issue the draw commands for the characters of the overlay - void draw(VkCommandBuffer cmdBuffer) - { - vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); - - VkDeviceSize offsets = 0; - vkCmdBindVertexBuffers(cmdBuffer, 0, 1, &buffer, &offsets); - vkCmdBindVertexBuffers(cmdBuffer, 1, 1, &buffer, &offsets); - // One draw command for every character. This is okay for a debug overlay, but not optimal - // In a real-world application one would try to batch draw commands - for (uint32_t j = 0; j < numLetters; j++) { - vkCmdDraw(cmdBuffer, 4, 1, j * 4, 0); - } - } -}; - -/* - Vulkan example main class -*/ -class VulkanExample : public VulkanExampleBase -{ -public: - TextOverlay* textOverlay{ nullptr }; - - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - } uniformData; - vks::Buffer uniformBuffer; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - title = "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() - { - if (device) { - 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]); - - if (textOverlay->visible) { - textOverlay->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) - { - uint32_t lastNumLetters = textOverlay->numLetters; - - textOverlay->beginTextUpdate(); - - textOverlay->addText(title, 5.0f * ui.scale, 5.0f * ui.scale, TextOverlay::alignLeft); - - std::stringstream ss; - ss << std::fixed << std::setprecision(2) << (frameTimer * 1000.0f) << "ms (" << lastFPS << " fps)"; - textOverlay->addText(ss.str(), 5.0f * ui.scale, 25.0f * ui.scale, TextOverlay::alignLeft); - - textOverlay->addText(deviceProperties.deviceName, 5.0f * ui.scale, 45.0f * ui.scale, TextOverlay::alignLeft); - - // Display current model view matrix - textOverlay->addText("model view matrix", (float)width - 5.0f * ui.scale, 5.0f * ui.scale, TextOverlay::alignRight); - - for (uint32_t i = 0; i < 4; i++) - { - ss.str(""); - ss << std::fixed << std::setprecision(2) << std::showpos; - ss << uniformData.modelView[0][i] << " " << uniformData.modelView[1][i] << " " << uniformData.modelView[2][i] << " " << uniformData.modelView[3][i]; - textOverlay->addText(ss.str(), (float)width - 5.0f * ui.scale, (25.0f + (float)i * 20.0f) * ui.scale, TextOverlay::alignRight); - } - - glm::vec3 projected = glm::project(glm::vec3(0.0f), uniformData.modelView, uniformData.projection, glm::vec4(0, 0, (float)width, (float)height)); - textOverlay->addText("A torus knot", projected.x, projected.y, TextOverlay::alignCenter); - -#if defined(__ANDROID__) -#else - textOverlay->addText("Press \"space\" to toggle text overlay", 5.0f * ui.scale, 65.0f * ui.scale, TextOverlay::alignLeft); - textOverlay->addText("Hold middle mouse button and drag to move", 5.0f * ui.scale, 85.0f * ui.scale, TextOverlay::alignLeft); -#endif - textOverlay->endTextUpdate(); - - // If the no. of letters changed, the no. of draw commands also changes which requires a rebuild of the command buffers - if (lastNumLetters != textOverlay->numLetters) { - std::cout << "rebuild cb\n"; - buildCommandBuffers(); - } - } - - void loadAssets() - { - const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; - model.loadFromFile(getAssetPath() + "models/torusknot.gltf", vulkanDevice, queue, glTFLoadingFlags); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() - { - 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))); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepareTextOverlay() - { - // Load the text rendering shaders - std::vector 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, - renderPass, - &width, - &height, - ui.scale, - shaderStages - ); - updateTextOverlay(); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - prepareTextOverlay(); - 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; - updateUniformBuffers(); - if (camera.updated) { - updateTextOverlay(); - } - draw(); - } - - virtual void keyPressed(uint32_t keyCode) - { - switch (keyCode) - { - case KEY_KPADD: - case KEY_SPACE: - textOverlay->visible = !textOverlay->visible; - buildCommandBuffers(); - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texture/texture.cpp b/examples/texture/texture.cpp deleted file mode 100644 index d8dee7cb..00000000 --- a/examples/texture/texture.cpp +++ /dev/null @@ -1,683 +0,0 @@ -/* -* Vulkan Example - Texture loading (and display) example (including mip maps) -* -* This sample shows how to upload a 2D texture to the device and how to display it. In Vulkan this is done using images, views and samplers. -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" -#include -#include - -// 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{ VK_NULL_HANDLE }; - VkImage image{ VK_NULL_HANDLE }; - VkImageLayout imageLayout; - VkDeviceMemory deviceMemory{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - uint32_t width{ 0 }; - uint32_t height{ 0 }; - uint32_t mipLevels{ 0 }; - } texture; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 viewPos; - // This is used to change the bias for the level-of-detail (mips) in the fragment shader - float lodBias = 0.0f; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - destroyTextureImage(texture); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vertexBuffer.destroy(); - indexBuffer.destroy(); - uniformBuffer.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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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 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(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; - // 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, nullptr); - 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])); - } - } - - // Creates a vertex and index buffer for a quad made of two triangles - // This is used to display the texture on - void generateQuad() - { - // Setup vertices for a single uv-mapped quad made from two triangles - std::vector 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 indices = { 0,1,2, 2,3,0 }; - indexCount = static_cast(indices.size()); - - // Create buffers and upload data to the GPU - 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, vertices.size() * sizeof(Vertex), vertices.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, indices.size() * sizeof(uint32_t), indices.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, &vertexBuffer, vertices.size() * sizeof(Vertex))); - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t))); - - // Copy from host do device - vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue); - vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue); - - // Clean up - stagingBuffers.vertices.destroy(); - stagingBuffers.indices.destroy(); - } - - void setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - // The sample uses a combined image + sampler descriptor to sample the texture in the fragment shader - 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - 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; - // The image's view (images are never directly accessed by the shader, but rather through views defining subresources) - textureDescriptor.imageView = texture.view; - // The sampler (Telling the pipeline how to sample the texture, including repeat, border, etc.) - textureDescriptor.sampler = texture.sampler; - // The current layout of the image(Note: Should always fit the actual use, e.g.shader read) - textureDescriptor.imageLayout = texture.imageLayout; - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.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 (as opposed to splitting image and sampler) - 1, // Shader binding point 1 - &textureDescriptor) // Pointer to the descriptor image for our texture - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Shaders - 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); - - // Vertex input state - std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)), - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(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(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() - { - // 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(uniformData), &uniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - 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(); - loadTexture(); - generateQuad(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->sliderFloat("LOD bias", &uniformData.lodBias, 0.0f, (float)texture.mipLevels)) { - updateUniformBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texture3d/texture3d.cpp b/examples/texture3d/texture3d.cpp deleted file mode 100644 index b42a037e..00000000 --- a/examples/texture3d/texture3d.cpp +++ /dev/null @@ -1,651 +0,0 @@ -/* -* Vulkan Example - 3D texture loading (and generation using perlin noise) example -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" - -// 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 -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(bool applyRandomSeed) - { - // Generate random lookup for permutations containing all numbers from 0..255 - std::vector plookup; - plookup.resize(256); - std::iota(plookup.begin(), plookup.end(), 0); - std::default_random_engine rndEngine(applyRandomSeed ? std::random_device{}() : 0); - 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 -class FractalNoise -{ -private: - PerlinNoise perlinNoise; - uint32_t octaves; - T frequency; - T amplitude; - T persistence; -public: - FractalNoise(const PerlinNoise &perlinNoiseIn) : - perlinNoise(perlinNoiseIn) - { - 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{ 0 }; - uint32_t height{ 0 }; - uint32_t depth{ 0 }; - uint32_t mipLevels{ 0 }; - } texture; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::vec4 viewPos; - // The current depth level of the texture to display - // This is animated - float depth = 0.0f; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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(benchmark.active ? 0 : (unsigned int)time(NULL)); - } - - ~VulkanExample() - { - if (device) { - destroyTextureImage(texture); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vertexBuffer.destroy(); - indexBuffer.destroy(); - uniformBuffer.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.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 perlinNoise(!benchmark.active); - FractalNoise fractalNoise(perlinNoise); - - const float noiseScale = static_cast(rand() % 10) + 4.0f; - -#pragma omp parallel for - for (int32_t z = 0; z < static_cast(texture.depth); z++) - { - for (int32_t y = 0; y < static_cast(texture.height); y++) - { - for (int32_t x = 0; x < static_cast(texture.width); x++) - { - float nx = (float)x / (float)texture.width; - float ny = (float)y / (float)texture.height; - float nz = (float)z / (float)texture.depth; - float n = fractalNoise.noise(nx * noiseScale, ny * noiseScale, nz * noiseScale); - n = n - floor(n); - data[x + y * texture.width + z * texture.width * texture.height] = static_cast(floor(n * 255)); - } - } - } - - auto tEnd = std::chrono::high_resolution_clock::now(); - auto tDiff = std::chrono::duration(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, 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])); - } - } - - // Creates a vertex and index buffer for a quad made of two triangles - // This is used to display the texture on - void generateQuad() - { - // Setup vertices for a single uv-mapped quad made from two triangles - std::vector 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 indices = { 0,1,2, 2,3,0 }; - indexCount = static_cast(indices.size()); - - // Create buffers and upload data to the GPU - 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, vertices.size() * sizeof(Vertex), vertices.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, indices.size() * sizeof(uint32_t), indices.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, &vertexBuffer, vertices.size() * sizeof(Vertex))); - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t))); - - // Copy from host do device - vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue); - vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue); - - // Clean up - stagingBuffers.vertices.destroy(); - stagingBuffers.indices.destroy(); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - // Image descriptor for the 3D texture - VkDescriptorImageInfo textureDescriptor = - vks::initializers::descriptorImageInfo( - texture.sampler, - texture.view, - texture.imageLayout); - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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, 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Shaders - 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); - - // Vertex input state - std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)), - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); - vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(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(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() - { - // 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(UniformData), &uniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - uniformData.viewPos = camera.viewPos; - if (!paused) { - // Animate depth - uniformData.depth += frameTimer * 0.15f; - if (uniformData.depth > 1.0f) { - uniformData.depth = uniformData.depth - 1.0f; - } - } - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - generateQuad(); - prepareUniformBuffers(); - prepareNoiseTexture(128, 128, 128); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->button("Generate new texture")) { - updateNoiseTexture(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturearray/texturearray.cpp b/examples/texturearray/texturearray.cpp deleted file mode 100644 index ca7e2c59..00000000 --- a/examples/texturearray/texturearray.cpp +++ /dev/null @@ -1,544 +0,0 @@ -/* -* Vulkan Example - Texture arrays and instanced rendering -* -* This sample shows how to load and render a texture array. This is a single layered texture where each layer contains different image data. -* The different layers are displayed on cubes using instancing, where each instance selects a different layer from the texture -* -* Copyright (C) 2016-2023 Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include "vulkanexamplebase.h" -#include -#include - -#define MAX_LAYERS 8 - -// 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{ 0 }; - vks::Texture textureArray; - - vks::Buffer vertexBuffer; - vks::Buffer indexBuffer; - uint32_t indexCount{ 0 }; - - // Values passed to the shader per drawn instance - struct alignas(16) PerInstanceData { - // Model matrix - glm::mat4 model; - // Layer index from which this instance will sample in the fragment shader - float arrayIndex{ 0 }; - }; - - struct UniformData { - // Global matrices - struct { - glm::mat4 projection; - glm::mat4 view; - } matrices; - // Separate data for each instance - PerInstanceData* instance{ nullptr }; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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(); - uniformBuffer.destroy(); - delete[] uniformData.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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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; - assert(layerCount <= MAX_LAYERS); - 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 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, - static_cast(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.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])); - } - } - - // Creates a vertex and index buffer for a cube - // This is used to display the texture on - void generateCube() - { - std::vector 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 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(indices.size()); - - // Create buffers and upload data to the GPU - 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, vertices.size() * sizeof(Vertex), vertices.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, indices.size() * sizeof(uint32_t), indices.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, &vertexBuffer, vertices.size() * sizeof(Vertex))); - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t))); - - // Copy from host do device - vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue); - vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue); - - // Clean up - stagingBuffers.vertices.destroy(); - stagingBuffers.indices.destroy(); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - 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 writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0); - - // Vertex bindings and attributes - VkVertexInputBindingDescription vertexInputBinding = { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }; - std::vector 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(vertexInputAttributes.size()); - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - - // Instancing pipeline - std::array 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 = static_cast(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); - } - - void prepareUniformBuffers() - { - uniformData.instance = new PerInstanceData[layerCount]; - - uint32_t uboSize = sizeof(uniformData.matrices) + (MAX_LAYERS * sizeof(PerInstanceData)); - - // 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, 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 - uniformData.instance[i].model = glm::translate(glm::mat4(1.0f), glm::vec3(i * offset - center, 0.0f, 0.0f)); - uniformData.instance[i].model = glm::scale(uniformData.instance[i].model, glm::vec3(0.5f)); - // Instance texture array index - uniformData.instance[i].arrayIndex = (float)i; - } - - // Update instanced part of the uniform buffer - uint8_t *pData; - uint32_t dataOffset = sizeof(uniformData.matrices); - uint32_t dataSize = layerCount * sizeof(PerInstanceData); - VK_CHECK_RESULT(vkMapMemory(device, uniformBuffer.memory, dataOffset, dataSize, 0, (void **)&pData)); - memcpy(pData, uniformData.instance, dataSize); - vkUnmapMemory(device, uniformBuffer.memory); - - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffersCamera() - { - uniformData.matrices.projection = camera.matrices.perspective; - uniformData.matrices.view = camera.matrices.view; - memcpy(uniformBuffer.mapped, &uniformData.matrices, sizeof(uniformData.matrices)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - generateCube(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffersCamera(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturecubemap/texturecubemap.cpp b/examples/texturecubemap/texturecubemap.cpp deleted file mode 100644 index c426916d..00000000 --- a/examples/texturecubemap/texturecubemap.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/* -* Vulkan Example - Cube map texture loading and displaying -* -* This sample shows how to load and render a cubemap. A cubemap is a textures that contains 6 images, one per cube face. -* The sample displays the cubemap as a skybox (background) and as a reflection on a selectable object -* -* Copyright (C) 2016-2023 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 -#include - -class VulkanExample : public VulkanExampleBase -{ -public: - bool displaySkybox = true; - - vks::Texture cubeMap; - - struct Models { - vkglTF::Model skybox; - // The sample lets you select different models to apply the cubemap to - std::vector objects; - int32_t objectIndex = 0; - } models; - - struct UBOVS { - glm::mat4 projection; - glm::mat4 modelView; - glm::mat4 inverseModelview; - float lodBias = 0.0f; - } uboVS; - vks::Buffer uniformBuffer; - - struct { - VkPipeline skybox{ VK_NULL_HANDLE }; - VkPipeline reflect{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - std::vector objectNames; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - 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); - uniformBuffer.destroy(); - } - } - - // Enable physical device features required for this example - virtual void getEnabledFeatures() - { - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - } - } - - // Loads a cubemap from a file, uploads it to the device and create all Vulkan resources required to display it - void loadCubemap(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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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 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(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 = static_cast(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.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); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - - // Skybox - if (displaySkybox) - { - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox); - models.skybox.draw(drawCmdBuffers[i]); - } - - // 3D object - 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 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 - loadCubemap(getAssetPath() + "textures/cubemap_yokohama_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : Vertex shader 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - // Image descriptor for the cube map texture - VkDescriptorImageInfo textureDescriptor = vks::initializers::descriptorImageInfo(cubeMap.sampler, cubeMap.view, cubeMap.imageLayout); - - std::vector writeDescriptorSets = - { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader cubemap sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - const 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() - { - 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()); - } - - void updateUniformBuffers() - { - uboVS.projection = camera.matrices.perspective; - // Note: Both the object and skybox use the same uniform data, the translation part of the skybox is removed in the shader (see skybox.vert) - uboVS.modelView = camera.matrices.view; - uboVS.inverseModelview = glm::inverse(camera.matrices.view); - memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/texturecubemaparray/texturecubemaparray.cpp b/examples/texturecubemaparray/texturecubemaparray.cpp deleted file mode 100644 index a8826d09..00000000 --- a/examples/texturecubemaparray/texturecubemaparray.cpp +++ /dev/null @@ -1,498 +0,0 @@ -/* -* Vulkan Example - Cube map array texture loading and displaying -* -* This sample shows how load and render an cubemap array texture. A single image contains multiple cube maps. -* The cubemap to be displayed is selected in the fragment shader -* -* Copyright (C) 2020-2025 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 -#include - -class VulkanExample : public VulkanExampleBase -{ -public: - bool displaySkybox = true; - - vks::Texture cubeMapArray; - - struct Meshes { - vkglTF::Model skybox; - std::vector objects; - int32_t objectIndex = 0; - } models; - - struct UniformData { - glm::mat4 projection; - glm::mat4 modelView; - glm::mat4 inverseModelview; - float lodBias = 0.0f; - // Used by the fragment shader to select the cubemap from the array cubemap - int cubeMapIndex = 1; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline skybox{ VK_NULL_HANDLE }; - VkPipeline reflect{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - std::vector objectNames; - - VulkanExample() : VulkanExampleBase() - { - title = "Cube map texture arrays"; - 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() - { - if (device) { - 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); - uniformBuffer.destroy(); - } - } - - // Enable physical device features required for this example - virtual void getEnabledFeatures() - { - // This sample requires support for cube map arrays - 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; - } - }; - - // Loads a cubemap array from a file, uploads it to the device and create all Vulkan resources required to display it - void loadCubemapArray(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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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 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(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(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.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); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - - // Skybox - if (displaySkybox) - { - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox); - models.skybox.draw(drawCmdBuffers[i]); - } - - // 3D object - 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 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); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0 : Vertex shader 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - // Image descriptor for the cube map array texture - VkDescriptorImageInfo textureDescriptor = vks::initializers::descriptorImageInfo(cubeMapArray.sampler, cubeMapArray.view, cubeMapArray.imageLayout); - - std::vector writeDescriptorSets = - { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader cubemap sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - const VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, &uniformBuffer, sizeof(UniformData))); - // Map persistent - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.modelView = camera.matrices.view; - uniformData.inverseModelview = glm::inverse(camera.matrices.view); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - overlay->sliderInt("Cube map", &uniformData.cubeMapIndex, 0, cubeMapArray.layerCount - 1); - overlay->sliderFloat("LOD bias", &uniformData.lodBias, 0.0f, (float)cubeMapArray.mipLevels); - if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) { - buildCommandBuffers(); - } - if (overlay->checkBox("Skybox", &displaySkybox)) { - buildCommandBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturemipmapgen/README.md b/examples/texturemipmapgen/README.md deleted file mode 100644 index da34b4ee..00000000 --- a/examples/texturemipmapgen/README.md +++ /dev/null @@ -1,201 +0,0 @@ -# Run-time mip-map generation - - - -## 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.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - view.subresourceRange.baseMipLevel = 0; - view.subresourceRange.baseArrayLayer = 0; - view.subresourceRange.layerCount = 1; - view.subresourceRange.levelCount = texture.mipLevels; -``` diff --git a/examples/texturemipmapgen/texturemipmapgen.cpp b/examples/texturemipmapgen/texturemipmapgen.cpp deleted file mode 100644 index 734d3a8e..00000000 --- a/examples/texturemipmapgen/texturemipmapgen.cpp +++ /dev/null @@ -1,555 +0,0 @@ -/* -* Vulkan Example - Runtime mip map generation -* -* This samples shows how to generate a full mip-chain from a top-level image and how different sampling modes compare -* -* Copyright (C) 2016-2023 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 -#include - -class VulkanExample : public VulkanExampleBase -{ -public: - struct Texture { - VkImage image{ VK_NULL_HANDLE }; - VkDeviceMemory deviceMemory{ VK_NULL_HANDLE }; - VkImageView view{ VK_NULL_HANDLE }; - uint32_t width{ 0 }; - uint32_t height{ 0 }; - uint32_t mipLevels{ 0 }; - } texture; - - // To demonstrate mip mapping and filtering this example uses separate samplers - std::vector samplerNames{ "No mip maps" , "Mip maps (bilinear)" , "Mip maps (anisotropic)" }; - std::vector samplers{}; - - vkglTF::Model model; - - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::mat4 model; - glm::vec4 viewPos; - float lodBias = 0.0f; - int32_t samplerIndex = 2; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - VulkanExample() : VulkanExampleBase() - { - 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() - { - if (device) { - destroyTextureImage(texture); - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffer.destroy(); - for (auto sampler : samplers) { - vkDestroySampler(device, sampler, nullptr); - } - } - } - - virtual void getEnabledFeatures() - { - if (deviceFeatures.samplerAnisotropy) { - enabledFeatures.samplerAnisotropy = VK_TRUE; - } - } - - // Loads a full sized image from disk, generates a Vulkan image (texture) from it and creates a full mip chain using blits - void loadTextureAndGenerateMips(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\nMake sure the assets submodule has been checked out and is up-to-date.", -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\nMake sure the assets submodule has been checked out and is up-to-date.", -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 = static_cast(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 (uint32_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 some samplers with different settings that can be selected via the UI - 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.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) - { - 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 loadAssets() - { - model.loadFromFile(getAssetPath() + "models/tunnel_cylinder.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY); - loadTextureAndGenerateMips(getAssetPath() + "textures/metalplate_nomips_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM); - } - - void setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLER, 3), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector setLayoutBindings = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0), - // Binding 1: Sampled image - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT, 1), - // Binding 2: Array with 3 samplers - 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)); - - // Sets - 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 writeDescriptorSets = { - // Binding 0: Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1: Sampled image - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, &textureDescriptor) - }; - - // Binding 2: Contains an array of samplers that can be switched from the UI to demonstrate different filteirng modes - std::vector 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(samplerDescriptors.size()); - samplerDescriptorWrite.pImageInfo = samplerDescriptors.data(); - samplerDescriptorWrite.dstBinding = 2; - samplerDescriptorWrite.dstArrayElement = 0; - writeDescriptorSets.push_back(samplerDescriptorWrite); - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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() - { - 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()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::rotate(glm::mat4(1.0f), glm::radians(timer * 360.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - uniformData.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - 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; - updateUniformBuffers(); - draw(); - } - - virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) - { - if (overlay->header("Settings")) { - if (overlay->sliderFloat("LOD bias", &uniformData.lodBias, 0.0f, (float)texture.mipLevels)) { - updateUniformBuffers(); - } - if (overlay->comboBox("Sampler type", &uniformData.samplerIndex, samplerNames)) { - updateUniformBuffers(); - } - } - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturesparseresidency/texturesparseresidency.cpp b/examples/texturesparseresidency/texturesparseresidency.cpp deleted file mode 100644 index fad0d98a..00000000 --- a/examples/texturesparseresidency/texturesparseresidency.cpp +++ /dev/null @@ -1,871 +0,0 @@ -/* -* Vulkan Example - Sparse texture residency example -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -/* -* Important note : This sample is work-in-progress and works basically, but it's not 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(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 &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(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(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() -{ - 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); - uniformBuffer.destroy(); -} - -void VulkanExample::getEnabledFeatures() -{ - if (deviceFeatures.sparseBinding && deviceFeatures.sparseResidencyImage2D) { - enabledFeatures.shaderResourceResidency = 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(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 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 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); - 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(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.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::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::setupDescriptors() -{ - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Sets - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), - // Binding 1 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texture.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); -} - -void VulkanExample::preparePipelines() -{ - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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, &uniformBuffer, sizeof(UniformData), &uniformData)); - updateUniformBuffers(); -} - -void VulkanExample::updateUniformBuffers() -{ - uniformData.projection = camera.matrices.perspective; - uniformData.model = camera.matrices.view; - uniformData.viewPos = camera.viewPos; - - VK_CHECK_RESULT(uniformBuffer.map()); - memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); - uniformBuffer.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); - setupDescriptors(); - preparePipelines(); - buildCommandBuffers(); - prepared = true; -} - -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::render() -{ - if (!prepared) - return; - updateUniformBuffers(); - draw(); -} - -// 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 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, ®ion); - 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(benchmark.active ? 0 : std::random_device{}()); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - std::vector updatedPages; - std::vector 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 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, ®ion); - 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(benchmark.active ? 0 : std::random_device{}()); - std::uniform_real_distribution rndDist(0.0f, 1.0f); - - std::vector updatedPages; - std::vector 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", &uniformData.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(texture.pages.size())); - overlay->text("Mip tail starts at: %d", texture.mipTailStart); - } - -} - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturesparseresidency/texturesparseresidency.h b/examples/texturesparseresidency/texturesparseresidency.h deleted file mode 100644 index 9964b83e..00000000 --- a/examples/texturesparseresidency/texturesparseresidency.h +++ /dev/null @@ -1,120 +0,0 @@ -/* -* Vulkan Example - Sparse texture residency example -* -* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -/* -* Important note : This sample is work-in-progress and works basically, but it's not finished -*/ - -#include "vulkanexamplebase.h" -#include "VulkanglTFModel.h" - -// 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 pages; // Contains all virtual pages of the texture - std::vector sparseImageMemoryBinds; // Sparse image memory bindings of all memory-backed virtual tables - std::vector 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 &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 UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::vec4 viewPos; - float lodBias = 0.0f; - } uniformData; - vks::Buffer uniformBuffer; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - //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 setupDescriptors(); - 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/examples/timelinesemaphore/timelinesemaphore.cpp b/examples/timelinesemaphore/timelinesemaphore.cpp deleted file mode 100644 index 6b9a0c30..00000000 --- a/examples/timelinesemaphore/timelinesemaphore.cpp +++ /dev/null @@ -1,674 +0,0 @@ -/* -* Vulkan Example - Using timeline semaphores -* -* Based on the compute n-nbody sample, this sample replaces multiple semaphores with a single timeline semaphore -* -* Copyright (C) 2024 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(__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: - struct Textures { - vks::Texture2D particle; - vks::Texture2D gradient; - } textures{}; - - // Particle Definition - struct Particle { - glm::vec4 pos; - glm::vec4 vel; - }; - uint32_t numParticles{ 0 }; - vks::Buffer storageBuffer; - - // Resources for the graphics part of the example - struct Graphics { - uint32_t queueFamilyIndex; - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - VkPipeline pipeline; - struct UniformData { - glm::mat4 projection; - glm::mat4 view; - glm::vec2 screenDim; - } uniformData; - vks::Buffer uniformBuffer; - } graphics{}; - - // Resources for the compute part of the example - struct Compute { - uint32_t queueFamilyIndex; - VkQueue queue; - VkCommandPool commandPool; - VkCommandBuffer commandBuffer; - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - VkPipeline pipelineCalculate; - VkPipeline pipelineIntegrate; - struct UniformData { - float deltaT{ 0.0f }; - int32_t particleCount{ 0 }; - float gravity{ 0.002f }; - float power{ 0.75f }; - float soften{ 0.05f }; - } uniformData; - vks::Buffer uniformBuffer; - } compute{}; - - // Along with the actual semaphore we also need to track the increasing value of the timeline, - // so we store both in a single struct - struct TimeLineSemaphore { - VkSemaphore handle{ VK_NULL_HANDLE }; - uint64_t value{ 0 }; - } timeLineSemaphore; - - VkPhysicalDeviceTimelineSemaphoreFeaturesKHR enabledTimelineSemaphoreFeaturesKHR{}; - - VulkanExample() : VulkanExampleBase() - { - title = "Timeline semaphores"; - 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; - - enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); - - enabledTimelineSemaphoreFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR; - enabledTimelineSemaphoreFeaturesKHR.timelineSemaphore = VK_TRUE; - - deviceCreatepNextChain = &enabledTimelineSemaphoreFeaturesKHR; - } - - ~VulkanExample() - { - if (device) { - vkDestroySemaphore(device, timeLineSemaphore.handle, nullptr); - - // Graphics - graphics.uniformBuffer.destroy(); - vkDestroyPipeline(device, graphics.pipeline, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - - // Compute - compute.uniformBuffer.destroy(); - vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); - vkDestroyPipeline(device, compute.pipelineCalculate, nullptr); - vkDestroyPipeline(device, compute.pipelineIntegrate, nullptr); - vkDestroyCommandPool(device, compute.commandPool, nullptr); - - storageBuffer.destroy(); - - 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_TOP_OF_PIPE_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], 0, 1, &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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_TOP_OF_PIPE_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 = storageBuffer.buffer; - bufferBarrier.size = 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - compute.commandBuffer, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_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() - { - // We mark a few particles as attractors that move along a given path, these will pull in the other particles - std::vector 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), - }; - - numParticles = static_cast(attractors.size()) * PARTICLES_PER_ATTRACTOR; - - // Initial particle positions - std::vector particleBuffer(numParticles); - - std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr)); - std::normal_distribution rndDist(0.0f, 1.0f); - - for (uint32_t i = 0; i < static_cast(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(attractors.size()); - } - } - - compute.uniformData.particleCount = numParticles; - - VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle); - - // Staging - 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, &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, storageBuffer.buffer, 1, ©Region); - // 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, - storageBuffer.buffer, - 0, - storageBuffer.size - }; - - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, - 0, nullptr, - 1, &buffer_barrier, - 0, nullptr); - } - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - } - - void prepareGraphics() - { - // 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::UniformData)); - VK_CHECK_RESULT(graphics.uniformBuffer.map()); - - // Descriptor pool - std::vector 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(poolSizes, 2); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); - - // Descriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - - std::vector 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(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - - // Pipeline - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array shaderStages; - - // Vertex Input state - std::vector inputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX) - }; - std::vector inputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, pos)), - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, vel)), - }; - VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertexInputState.vertexBindingDescriptionCount = static_cast(inputBindings.size()); - vertexInputState.pVertexBindingDescriptions = inputBindings.data(); - vertexInputState.vertexAttributeDescriptionCount = static_cast(inputAttributes.size()); - vertexInputState.pVertexAttributeDescriptions = inputAttributes.data(); - - // Shaders - 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 = &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(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)); - - buildCommandBuffers(); - } - - void prepareCompute() - { - vkGetDeviceQueue(device, compute.queueFamilyIndex, 0, &compute.queue); - vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &compute.uniformBuffer, sizeof(Compute::UniformData)); - VK_CHECK_RESULT(compute.uniformBuffer.map()); - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - std::vector computeWriteDescriptorSets = { - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &storageBuffer.descriptor), - vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,1,&compute.uniformBuffer.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); - computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computenbody/particle_calculate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); - uint32_t sharedDataSize = std::min((uint32_t)1024, (uint32_t)(vulkanDevice->properties.limits.maxComputeSharedMemorySize / sizeof(glm::vec4))); - VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t)); - VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(int32_t), &sharedDataSize); - computePipelineCreateInfo.stage.pSpecializationInfo = &specializationInfo; - VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipelineCalculate)); - 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)); - 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)); - compute.commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool); - buildComputeCommandBuffer(); - } - - void updateComputeUniformBuffers() - { - compute.uniformData.deltaT = paused ? 0.0f : frameTimer * 0.05f; - memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformData)); - } - - void updateGraphicsUniformBuffers() - { - graphics.uniformData.projection = camera.matrices.perspective; - graphics.uniformData.view = camera.matrices.view; - graphics.uniformData.screenDim = glm::vec2((float)width, (float)height); - memcpy(graphics.uniformBuffer.mapped, &graphics.uniformData, sizeof(Graphics::UniformData)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - graphics.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - compute.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - - // Setup the timeline semaphore - VkSemaphoreCreateInfo semaphoreCI{}; - semaphoreCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - // It's a variation of the core semaphore type, creation is handled via an extension struture - VkSemaphoreTypeCreateInfoKHR semaphoreTypeCI{}; - semaphoreTypeCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR; - semaphoreTypeCI.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR; - semaphoreTypeCI.initialValue = timeLineSemaphore.value; - - semaphoreCI.pNext = &semaphoreTypeCI; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &timeLineSemaphore.handle)); - - loadAssets(); - prepareStorageBuffers(); - prepareGraphics(); - prepareCompute(); - prepared = true; - } - - void draw() - { - // Wait for rendering finished - VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - - // Submit compute commands - - // Define incremental timeline sempahore states - const uint64_t graphics_finished = timeLineSemaphore.value; - const uint64_t compute_finished = timeLineSemaphore.value + 1; - const uint64_t all_finished = timeLineSemaphore.value + 2; - - // With timeline semaphores, we can state on what value we want to wait on / signal on - VkTimelineSemaphoreSubmitInfoKHR timeLineSubmitInfo{ VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR }; - timeLineSubmitInfo.waitSemaphoreValueCount = 1; - timeLineSubmitInfo.pWaitSemaphoreValues = &graphics_finished; - timeLineSubmitInfo.signalSemaphoreValueCount = 1; - timeLineSubmitInfo.pSignalSemaphoreValues = &compute_finished; - - VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo(); - computeSubmitInfo.commandBufferCount = 1; - computeSubmitInfo.pCommandBuffers = &compute.commandBuffer; - computeSubmitInfo.waitSemaphoreCount = 1; - computeSubmitInfo.pWaitSemaphores = &timeLineSemaphore.handle; - computeSubmitInfo.pWaitDstStageMask = &waitStageMask; - computeSubmitInfo.signalSemaphoreCount = 1; - computeSubmitInfo.pSignalSemaphores = &timeLineSemaphore.handle; - - computeSubmitInfo.pNext = &timeLineSubmitInfo; - - 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[] = { timeLineSemaphore.handle, semaphores.presentComplete }; - VkSemaphore graphicsSignalSemaphores[] = { timeLineSemaphore.handle, 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; - - uint64_t wait_values[2] = { compute_finished, compute_finished }; - uint64_t signal_values[2] = { all_finished, all_finished }; - - timeLineSubmitInfo.waitSemaphoreValueCount = 2; - timeLineSubmitInfo.pWaitSemaphoreValues = &wait_values[0]; - timeLineSubmitInfo.signalSemaphoreValueCount = 2; - timeLineSubmitInfo.pSignalSemaphoreValues = &signal_values[0]; - - submitInfo.pNext = &timeLineSubmitInfo; - - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - // Increase timeline value base for next frame - timeLineSemaphore.value = all_finished; - - VulkanExampleBase::submitFrame(); - } - - virtual void render() - { - if (!prepared) - return; - updateComputeUniformBuffers(); - updateGraphicsUniformBuffers(); - draw(); - } -}; - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/triangle/triangle.cpp b/examples/triangle/triangle.cpp deleted file mode 100644 index 22bb8f3f..00000000 --- a/examples/triangle/triangle.cpp +++ /dev/null @@ -1,1186 +0,0 @@ -/* -* 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-2025 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include -#include -#include -#include -#include -#include -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include - -#include -#include "vulkanexamplebase.h" - -// We want to keep GPU and CPU busy. To do that we may start building a new command buffer while the previous one is still being executed -// This number defines how many frames may be worked on simultaneously at once -// Increasing this number may improve performance but will also introduce additional latency -#define MAX_CONCURRENT_FRAMES 2 - -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{ VK_NULL_HANDLE }; // Handle to the device memory for this buffer - VkBuffer buffer{ VK_NULL_HANDLE }; // Handle to the Vulkan buffer object that the memory is bound to - } vertices; - - // Index buffer - struct { - VkDeviceMemory memory{ VK_NULL_HANDLE }; - VkBuffer buffer{ VK_NULL_HANDLE }; - uint32_t count{ 0 }; - } indices; - - // Uniform buffer block object - struct UniformBuffer { - VkDeviceMemory memory{ VK_NULL_HANDLE }; - VkBuffer buffer{ VK_NULL_HANDLE }; - // 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{ VK_NULL_HANDLE }; - // We keep a pointer to the mapped buffer, so we can easily update it's contents via a memcpy - uint8_t* mapped{ nullptr }; - }; - // We use one UBO per frame, so we can have a frame overlap and make sure that uniforms aren't updated while still in use - std::array uniformBuffers; - - // 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 ShaderData { - glm::mat4 projectionMatrix; - glm::mat4 modelMatrix; - glm::mat4 viewMatrix; - }; - - // 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{ VK_NULL_HANDLE }; - - // 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{ VK_NULL_HANDLE }; - - // 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{ VK_NULL_HANDLE }; - - // Synchronization primitives - // Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan. - - // Semaphores are used to coordinate operations within the graphics queue and ensure correct command ordering - std::vector presentCompleteSemaphores{}; - std::vector renderCompleteSemaphores{}; - - VkCommandPool commandPool{ VK_NULL_HANDLE }; - std::array commandBuffers{}; - std::array waitFences{}; - - // To select the correct sync and command objects, we need to keep track of the current frame - uint32_t currentFrame{ 0 }; - - VulkanExample() : VulkanExampleBase() - { - title = "Basic indexed triangle"; - // To keep things simple, we don't use the UI overlay from the framework - 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() override - { - // Clean up used Vulkan resources - // Note: Inherited destructor cleans up resources stored in base class - if (device) { - 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); - vkDestroyCommandPool(device, commandPool, nullptr); - for (size_t i = 0; i < presentCompleteSemaphores.size(); i++) { - vkDestroySemaphore(device, presentCompleteSemaphores[i], nullptr); - } - for (size_t i = 0; i < renderCompleteSemaphores.size(); i++) { - vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr); - } - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - vkDestroyFence(device, waitFences[i], nullptr); - vkDestroyBuffer(device, uniformBuffers[i].buffer, nullptr); - vkFreeMemory(device, uniformBuffers[i].memory, 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 https://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 per-frame (in flight) Vulkan synchronization primitives used in this example - void createSynchronizationPrimitives() - { - // Fences are used to check draw command buffer completion on the host - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - VkFenceCreateInfo fenceCI{}; - fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - // Create the fences in signaled state (so we don't wait on first render of each command buffer) - fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT; - // Fence used to ensure that command buffer has completed exection before using it again - VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFences[i])); - } - // Semaphores are used for correct command ordering within a queue - // Used to ensure that image presentation is complete before starting to submit again - presentCompleteSemaphores.resize(MAX_CONCURRENT_FRAMES); - for (auto& semaphore : presentCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } - // Render completion - // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue - renderCompleteSemaphores.resize(swapChain.images.size()); - for (auto& semaphore : renderCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } - } - - void createCommandBuffers() - { - // All command buffers are allocated from a command pool - VkCommandPoolCreateInfo commandPoolCI{}; - commandPoolCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - commandPoolCI.queueFamilyIndex = swapChain.queueNodeIndex; - commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - VK_CHECK_RESULT(vkCreateCommandPool(device, &commandPoolCI, nullptr, &commandPool)); - - // Allocate one command buffer per max. concurrent frame from above pool - VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, MAX_CONCURRENT_FRAMES); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, commandBuffers.data())); - } - - // 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 createVertexBuffer() - { - // 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 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(vertexBuffer.size()) * sizeof(Vertex); - - // Setup indices - std::vector indexBuffer{ 0, 1, 2 }; - indices.count = static_cast(indexBuffer.size()); - uint32_t indexBufferSize = indices.count * sizeof(uint32_t); - - VkMemoryAllocateInfo memAlloc{}; - memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - VkMemoryRequirements memReqs; - - // 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 - // - // Note: On unified memory architectures where host (CPU) and GPU share the same memory, staging is not necessary - // To keep this sample easy to follow, there is no check for that in place - - struct StagingBuffer { - VkDeviceMemory memory; - VkBuffer buffer; - }; - - struct { - StagingBuffer vertices; - StagingBuffer indices; - } stagingBuffers{}; - - void* data; - - // Vertex buffer - VkBufferCreateInfo vertexBufferInfoCI{}; - vertexBufferInfoCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - vertexBufferInfoCI.size = vertexBufferSize; - // Buffer is used as the copy source - vertexBufferInfoCI.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, &vertexBufferInfoCI, 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 to - // 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 - vertexBufferInfoCI.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfoCI, 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 indexbufferCI{}; - indexbufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - indexbufferCI.size = indexBufferSize; - indexbufferCI.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - // Copy index data to a buffer visible to the host (staging buffer) - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferCI, 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 - indexbufferCI.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferCI, 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; - - VkCommandBufferAllocateInfo cmdBufAllocateInfo{}; - cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - cmdBufAllocateInfo.commandPool = commandPool; - cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - cmdBufAllocateInfo.commandBufferCount = 1; - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); - - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); - // Put buffer region copies into command buffer - VkBufferCopy copyRegion{}; - // Vertex buffer - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffers.vertices.buffer, vertices.buffer, 1, ©Region); - // Index buffer - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffers.indices.buffer, indices.buffer, 1, ©Region); - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - - // Submit the command buffer to the queue to finish the copy - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ©Cmd; - - // Create fence to ensure that the command buffer has finished executing - VkFenceCreateInfo fenceCI{}; - fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceCI.flags = 0; - VkFence fence; - VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, 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, commandPool, 1, ©Cmd); - - // 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); - } - - // Descriptors are allocated from a pool, that tells the implementation how many and what types of descriptors we are going to use (at maximum) - void createDescriptorPool() - { - // We need to tell the API the number of max. requested descriptors per type - VkDescriptorPoolSize descriptorTypeCounts[1]{}; - // This example only one descriptor type (uniform buffer) - descriptorTypeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - // We have one buffer (and as such descriptor) per frame - descriptorTypeCounts[0].descriptorCount = MAX_CONCURRENT_FRAMES; - // 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 descriptorPoolCI{}; - descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - descriptorPoolCI.pNext = nullptr; - descriptorPoolCI.poolSizeCount = 1; - descriptorPoolCI.pPoolSizes = descriptorTypeCounts; - // Set the max. number of descriptor sets that can be requested from this pool (requesting beyond this limit will result in an error) - // Our sample will create one set per uniform buffer per frame - descriptorPoolCI.maxSets = MAX_CONCURRENT_FRAMES; - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); - } - - // Descriptor set layouts define the interface between our application and the shader - // 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 - void createDescriptorSetLayout() - { - // 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 descriptorLayoutCI{}; - descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorLayoutCI.pNext = nullptr; - descriptorLayoutCI.bindingCount = 1; - descriptorLayoutCI.pBindings = &layoutBinding; - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); - } - - // Shaders access data using descriptor sets that "point" at our uniform buffers - // The descriptor sets make use of the descriptor set layouts created above - void createDescriptorSets() - { - // Allocate one descriptor set per frame from the global descriptor pool - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - 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, &uniformBuffers[i].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{}; - - // The buffer's information is passed using a descriptor info structure - VkDescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = uniformBuffers[i].buffer; - bufferInfo.range = sizeof(ShaderData); - - // Binding 0 : Uniform buffer - writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writeDescriptorSet.dstSet = uniformBuffers[i].descriptorSet; - writeDescriptorSet.descriptorCount = 1; - writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - writeDescriptorSet.pBufferInfo = &bufferInfo; - 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() override - { - // Create an optimal image used as the depth stencil attachment - VkImageCreateInfo imageCI{}; - imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageCI.imageType = VK_IMAGE_TYPE_2D; - imageCI.format = depthFormat; - // Use example's height and width - 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; - imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VK_CHECK_RESULT(vkCreateImage(device, &imageCI, 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.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.memory, 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 depthStencilViewCI{}; - depthStencilViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - depthStencilViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthStencilViewCI.format = depthFormat; - depthStencilViewCI.subresourceRange = {}; - depthStencilViewCI.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) { - depthStencilViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - depthStencilViewCI.subresourceRange.baseMipLevel = 0; - depthStencilViewCI.subresourceRange.levelCount = 1; - depthStencilViewCI.subresourceRange.baseArrayLayer = 0; - depthStencilViewCI.subresourceRange.layerCount = 1; - depthStencilViewCI.image = depthStencil.image; - VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilViewCI, 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() override - { - // Create a frame buffer for every image in the swapchain - frameBuffers.resize(swapChain.images.size()); - for (size_t i = 0; i < frameBuffers.size(); i++) - { - std::array attachments{}; - // Color attachment is the view of the swapchain image - attachments[0] = swapChain.imageViews[i]; - // Depth/Stencil attachment is the same for all frame buffers due to how depth works with current GPUs - attachments[1] = depthStencil.view; - - VkFramebufferCreateInfo frameBufferCI{}; - frameBufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - // All frame buffers use the same renderpass setup - frameBufferCI.renderPass = renderPass; - frameBufferCI.attachmentCount = static_cast(attachments.size()); - frameBufferCI.pAttachments = attachments.data(); - frameBufferCI.width = width; - frameBufferCI.height = height; - frameBufferCI.layers = 1; - // Create the framebuffer - VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCI, 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() override - { - // This example will use a single render pass with one subpass - - // Descriptors for the attachments used by this renderpass - std::array 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 dependencies{}; - - // Does the transition from final to initial layout for the depth an color attachments - // Depth attachment - dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; - dependencies[0].dependencyFlags = 0; - // Color attachment - dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].dstSubpass = 0; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependencies[1].srcAccessMask = 0; - dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - dependencies[1].dependencyFlags = 0; - - // Create the actual renderpass - VkRenderPassCreateInfo renderPassCI{}; - renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassCI.attachmentCount = static_cast(attachments.size()); // Number of attachments used by this render pass - renderPassCI.pAttachments = attachments.data(); // Descriptions of the attachments used by the render pass - renderPassCI.subpassCount = 1; // We only use one subpass in this example - renderPassCI.pSubpasses = &subpassDescription; // Description of that subpass - renderPassCI.dependencyCount = static_cast(dependencies.size()); // Number of subpass dependencies - renderPassCI.pDependencies = dependencies.data(); // Subpass dependencies used by the render pass - VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, 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(const std::string& filename) - { - size_t shaderSize; - char* shaderCode{ nullptr }; - -#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 shaderModuleCI{}; - shaderModuleCI.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - shaderModuleCI.codeSize = shaderSize; - shaderModuleCI.pCode = (uint32_t*)shaderCode; - - VkShaderModule shaderModule; - VK_CHECK_RESULT(vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule)); - - delete[] shaderCode; - - return shaderModule; - } - else - { - std::cerr << "Error: Could not open shader file \"" << filename << "\"" << std::endl; - return VK_NULL_HANDLE; - } - } - - void createPipelines() - { - // 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 pipelineLayoutCI{}; - pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pipelineLayoutCI.pNext = nullptr; - pipelineLayoutCI.setLayoutCount = 1; - pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // 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 pipelineCI{}; - pipelineCI.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - // The layout used for this pipeline (can be shared among multiple pipelines using the same layout) - pipelineCI.layout = pipelineLayout; - // Renderpass this pipeline is attached to - pipelineCI.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 inputAssemblyStateCI{}; - inputAssemblyStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - - // Rasterization state - VkPipelineRasterizationStateCreateInfo rasterizationStateCI{}; - rasterizationStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL; - rasterizationStateCI.cullMode = VK_CULL_MODE_NONE; - rasterizationStateCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - rasterizationStateCI.depthClampEnable = VK_FALSE; - rasterizationStateCI.rasterizerDiscardEnable = VK_FALSE; - rasterizationStateCI.depthBiasEnable = VK_FALSE; - rasterizationStateCI.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{}; - blendAttachmentState.colorWriteMask = 0xf; - blendAttachmentState.blendEnable = VK_FALSE; - VkPipelineColorBlendStateCreateInfo colorBlendStateCI{}; - colorBlendStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlendStateCI.attachmentCount = 1; - colorBlendStateCI.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 viewportStateCI{}; - viewportStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewportStateCI.viewportCount = 1; - viewportStateCI.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 dynamicStateEnables; - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR); - VkPipelineDynamicStateCreateInfo dynamicStateCI{}; - dynamicStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamicStateCI.pDynamicStates = dynamicStateEnables.data(); - dynamicStateCI.dynamicStateCount = static_cast(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 depthStencilStateCI{}; - depthStencilStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - depthStencilStateCI.depthTestEnable = VK_TRUE; - depthStencilStateCI.depthWriteEnable = VK_TRUE; - depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; - depthStencilStateCI.depthBoundsTestEnable = VK_FALSE; - depthStencilStateCI.back.failOp = VK_STENCIL_OP_KEEP; - depthStencilStateCI.back.passOp = VK_STENCIL_OP_KEEP; - depthStencilStateCI.back.compareOp = VK_COMPARE_OP_ALWAYS; - depthStencilStateCI.stencilTestEnable = VK_FALSE; - depthStencilStateCI.front = depthStencilStateCI.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 multisampleStateCI{}; - multisampleStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - multisampleStateCI.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 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 vertexInputStateCI{}; - vertexInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertexInputStateCI.vertexBindingDescriptionCount = 1; - vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding; - vertexInputStateCI.vertexAttributeDescriptionCount = 2; - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributs.data(); - - // Shaders - std::array 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 - pipelineCI.stageCount = static_cast(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - - // Assign the pipeline states to the pipeline creation info structure - pipelineCI.pVertexInputState = &vertexInputStateCI; - pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; - pipelineCI.pRasterizationState = &rasterizationStateCI; - pipelineCI.pColorBlendState = &colorBlendStateCI; - pipelineCI.pMultisampleState = &multisampleStateCI; - pipelineCI.pViewportState = &viewportStateCI; - pipelineCI.pDepthStencilState = &depthStencilStateCI; - pipelineCI.pDynamicState = &dynamicStateCI; - - // Create rendering pipeline using the specified states - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, 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 createUniformBuffers() - { - // Prepare and initialize the per-frame uniform buffer blocks containing shader uniforms - // Single uniforms like in OpenGL are no longer present in Vulkan. All hader 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(ShaderData); - // This buffer will be used as a uniform buffer - bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - - // Create the buffers - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBuffers[i].buffer)); - // Get memory requirements including size, alignment and memory type - vkGetBufferMemoryRequirements(device, uniformBuffers[i].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, &(uniformBuffers[i].memory))); - // Bind memory to buffer - VK_CHECK_RESULT(vkBindBufferMemory(device, uniformBuffers[i].buffer, uniformBuffers[i].memory, 0)); - // We map the buffer once, so we can update it without having to map it again - VK_CHECK_RESULT(vkMapMemory(device, uniformBuffers[i].memory, 0, sizeof(ShaderData), 0, (void**)&uniformBuffers[i].mapped)); - } - - } - - void prepare() override - { - VulkanExampleBase::prepare(); - createSynchronizationPrimitives(); - createCommandBuffers(); - createVertexBuffer(); - createUniformBuffers(); - createDescriptorSetLayout(); - createDescriptorPool(); - createDescriptorSets(); - createPipelines(); - prepared = true; - } - - void render() override - { - if (!prepared) - return; - - // Use a fence to wait until the command buffer has finished execution before using it again - vkWaitForFences(device, 1, &waitFences[currentFrame], VK_TRUE, UINT64_MAX); - VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentFrame])); - - // Get the next swap chain image from the implementation - // Note that the implementation is free to return the images in any order, so we must use the acquire function and can't just cycle through the images/imageIndex on our own - uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain.swapChain, UINT64_MAX, presentCompleteSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); - if (result == VK_ERROR_OUT_OF_DATE_KHR) { - windowResize(); - return; - } - else if ((result != VK_SUCCESS) && (result != VK_SUBOPTIMAL_KHR)) { - throw "Could not acquire the next swap chain image!"; - } - - // Update the uniform buffer for the next frame - ShaderData shaderData{}; - shaderData.projectionMatrix = camera.matrices.perspective; - shaderData.viewMatrix = camera.matrices.view; - shaderData.modelMatrix = glm::mat4(1.0f); - - // Copy the current matrices to the current frame's uniform buffer - // Note: Since we requested a host coherent memory type for the uniform buffer, the write is instantly visible to the GPU - memcpy(uniformBuffers[currentFrame].mapped, &shaderData, sizeof(ShaderData)); - - // Build the command buffer - // Unlike in OpenGL all rendering commands are recorded into command buffers that are then submitted to the queue - // This allows to generate work upfront in a separate thread - // For basic command buffers (like in this sample), recording is so fast that there is no need to offload this - - vkResetCommandBuffer(commandBuffers[currentFrame], 0); - - VkCommandBufferBeginInfo cmdBufInfo{}; - cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - - // 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; - renderPassBeginInfo.framebuffer = frameBuffers[imageIndex]; - - const VkCommandBuffer commandBuffer = commandBuffers[currentFrame]; - VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &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(commandBuffer, &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(commandBuffer, 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(commandBuffer, 0, 1, &scissor); - // Bind descriptor set for the current frame's uniform buffer, so the shader uses the data from that buffer for this draw - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &uniformBuffers[currentFrame].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(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - // Bind triangle vertex buffer (contains position and colors) - VkDeviceSize offsets[1]{ 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); - // Bind triangle index buffer - vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); - // Draw indexed triangle - vkCmdDrawIndexed(commandBuffer, indices.count, 1, 0, 0, 0); - vkCmdEndRenderPass(commandBuffer); - // 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(commandBuffer)); - - // Submit the command buffer to the graphics queue - - // 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.pCommandBuffers = &commandBuffer; // Command buffers(s) to execute in this batch (submission) - submitInfo.commandBufferCount = 1; // We submit a single command buffer - - // Semaphore to wait upon before the submitted command buffer starts executing - submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentFrame]; - submitInfo.waitSemaphoreCount = 1; - // Semaphore to be signaled when command buffers have completed - submitInfo.pSignalSemaphores = &renderCompleteSemaphores[imageIndex]; - submitInfo.signalSemaphoreCount = 1; - - // Submit to the graphics queue passing a wait fence - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentFrame])); - - // Present the current frame 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 - - VkPresentInfoKHR presentInfo{}; - presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderCompleteSemaphores[imageIndex]; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapChain.swapChain; - presentInfo.pImageIndices = &imageIndex; - result = vkQueuePresentKHR(queue, &presentInfo); - - if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { - windowResize(); - } - else if (result != VK_SUCCESS) { - throw "Could not present the image to the swap chain!"; - } - - // Select the next frame to render to, based on the max. no. of concurrent frames - currentFrame = (currentFrame + 1) % MAX_CONCURRENT_FRAMES; - } -}; - -// OS specific 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(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR, _In_ int) -{ - 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_USE_PLATFORM_METAL_EXT)) && 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; -} -#elif defined(VK_USE_PLATFORM_SCREEN_QNX) -VULKAN_EXAMPLE_MAIN() -#endif diff --git a/examples/trianglevulkan13/trianglevulkan13.cpp b/examples/trianglevulkan13/trianglevulkan13.cpp deleted file mode 100644 index 542d608a..00000000 --- a/examples/trianglevulkan13/trianglevulkan13.cpp +++ /dev/null @@ -1,963 +0,0 @@ -/* -* Vulkan Example - Basic indexed triangle rendering using Vulkan 1.3 -* -* Note: -* This is a variation of the the triangle sample that makes use of Vulkan 1.3 features -* This simplifies the api a bit, esp. with dynamic rendering replacing render passes (and with that framebuffers) -* -* Copyright (C) 2024-2025 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include -#include -#include -#include -#include -#include -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include - -#include -#include "vulkanexamplebase.h" - -// We want to keep GPU and CPU busy. To do that we may start building a new command buffer while the previous one is still being executed -// This number defines how many frames may be worked on simultaneously at once -// Increasing this number may improve performance but will also introduce additional latency -#define MAX_CONCURRENT_FRAMES 2 - -class VulkanExample : public VulkanExampleBase -{ -public: - // Vertex layout used in this example - struct Vertex { - float position[3]; - float color[3]; - }; - - struct VulkanBuffer { - VkDeviceMemory memory{ VK_NULL_HANDLE }; - VkBuffer handle{ VK_NULL_HANDLE }; - }; - - VulkanBuffer vertexBuffer; - VulkanBuffer indexBuffer; - uint32_t indexCount{ 0 }; - - // Uniform buffer block object - struct UniformBuffer : VulkanBuffer { - // 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{ VK_NULL_HANDLE }; - // We keep a pointer to the mapped buffer, so we can easily update it's contents via a memcpy - uint8_t* mapped{ nullptr }; - }; - // We use one UBO per frame, so we can have a frame overlap and make sure that uniforms aren't updated while still in use - std::array uniformBuffers; - - // For simplicity we use the same uniform block layout as in the shader - // This way we can just memcpy the data to the ubo - // Note: You should use data types that align with the GPU in order to avoid manual padding (vec4, mat4) - struct ShaderData { - glm::mat4 projectionMatrix; - glm::mat4 modelMatrix; - glm::mat4 viewMatrix; - }; - - // 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{ VK_NULL_HANDLE }; - - // 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{ VK_NULL_HANDLE }; - - // 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{ VK_NULL_HANDLE }; - - // Synchronization primitives - // Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan. - // Semaphores are used to coordinate operations within the graphics queue and ensure correct command ordering - std::vector presentCompleteSemaphores{}; - std::vector renderCompleteSemaphores{}; - // Fences are used to make sure command buffers aren't rerecorded until they've finished executing - std::array waitFences{}; - - VkCommandPool commandPool{ VK_NULL_HANDLE }; - std::array commandBuffers{}; - - // To select the correct sync and command objects, we need to keep track of the current frame - uint32_t currentFrame{ 0 }; - - VkPhysicalDeviceVulkan13Features enabledFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES }; - - VulkanExample() : VulkanExampleBase() - { - title = "Basic indexed triangle using Vulkan 1.3"; - // To keep things simple, we don't use the UI overlay from the framework - 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); - // We want to use Vulkan 1.3 with the dynamic rendering and sync 2 features - apiVersion = VK_API_VERSION_1_3; - enabledFeatures.dynamicRendering = VK_TRUE; - enabledFeatures.synchronization2 = VK_TRUE; - deviceCreatepNextChain = &enabledFeatures; - } - - ~VulkanExample() override - { - // Clean up used Vulkan resources - // Note: Inherited destructor cleans up resources stored in base class - if (device) { - vkDestroyPipeline(device, pipeline, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vkDestroyBuffer(device, vertexBuffer.handle, nullptr); - vkFreeMemory(device, vertexBuffer.memory, nullptr); - vkDestroyBuffer(device, indexBuffer.handle, nullptr); - vkFreeMemory(device, indexBuffer.memory, nullptr); - vkDestroyCommandPool(device, commandPool, nullptr); - for (size_t i = 0; i < presentCompleteSemaphores.size(); i++) { - vkDestroySemaphore(device, presentCompleteSemaphores[i], nullptr); - } - for (size_t i = 0; i < renderCompleteSemaphores.size(); i++) { - vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr); - } - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - vkDestroyFence(device, waitFences[i], nullptr); - vkDestroyBuffer(device, uniformBuffers[i].handle, nullptr); - vkFreeMemory(device, uniformBuffers[i].memory, nullptr); - } - } - } - - virtual void getEnabledFeatures() override - { - // Vulkan 1.3 device support is required for this example - if (deviceProperties.apiVersion < VK_API_VERSION_1_3) { - vks::tools::exitFatal("Selected GPU does not support support Vulkan 1.3", VK_ERROR_INCOMPATIBLE_DRIVER); - } - } - - // 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 https://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 per-frame (in flight) and per (swapchain image) Vulkan synchronization primitives used in this example - void createSynchronizationPrimitives() - { - // Fences are per frame in flight - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - // Fence used to ensure that command buffer has completed exection before using it again - VkFenceCreateInfo fenceCI{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; - // Create the fences in signaled state (so we don't wait on first render of each command buffer) - fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT; - VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFences[i])); - } - // Semaphores are used for correct command ordering within a queue - // Used to ensure that image presentation is complete before starting to submit again - presentCompleteSemaphores.resize(MAX_CONCURRENT_FRAMES); - for (auto& semaphore : presentCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } - // Render completion - // Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue - renderCompleteSemaphores.resize(swapChain.images.size()); - for (auto& semaphore : renderCompleteSemaphores) { - VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; - VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &semaphore)); - } - } - - // Command buffers are used to record commands to and are submitted to a queue for execution ("rendering") - void createCommandBuffers() - { - // All command buffers are allocated from the same command pool - VkCommandPoolCreateInfo commandPoolCI{ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; - commandPoolCI.queueFamilyIndex = swapChain.queueNodeIndex; - commandPoolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - VK_CHECK_RESULT(vkCreateCommandPool(device, &commandPoolCI, nullptr, &commandPool)); - // Allocate one command buffer per max. concurrent frame from above pool - VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, MAX_CONCURRENT_FRAMES); - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, commandBuffers.data())); - } - - // 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 createVertexBuffer() - { - // A note on memory management in Vulkan in general: - // This is a 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 - const std::vector 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 } } - }; - uint32_t vertexBufferSize = static_cast(vertices.size()) * sizeof(Vertex); - - // Setup indices - // We do this for demonstration purposes, a triangle doesn't require indices to be rendered (because of the 1:1 mapping), but more complex shapes usually make use of indices - std::vector indices{ 0, 1, 2 }; - indexCount = static_cast(indices.size()); - uint32_t indexBufferSize = indexCount * sizeof(uint32_t); - - VkMemoryAllocateInfo memAlloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - VkMemoryRequirements memReqs; - - // 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 - // - // Note: On unified memory architectures where host (CPU) and GPU share the same memory, staging is not necessary - // To keep this sample easy to follow, there is no check for that in place - - // Create the host visible staging buffer that we copy vertices and indices too, and from which we copy to the device - VulkanBuffer stagingBuffer; - VkBufferCreateInfo stagingBufferCI{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - stagingBufferCI.size = vertexBufferSize + indexBufferSize; - // Buffer is used as the copy source - stagingBufferCI.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, &stagingBufferCI, nullptr, &stagingBuffer.handle)); - vkGetBufferMemoryRequirements(device, stagingBuffer.handle, &memReqs); - memAlloc.allocationSize = memReqs.size; - // Request a host visible memory type that can be used to copy our data to - // 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, &stagingBuffer.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer.handle, stagingBuffer.memory, 0)); - // Map the buffer and copy vertices and indices into it, this way we can use a single buffer as the source for both vertex and index GPU buffers - uint8_t* data{ nullptr }; - VK_CHECK_RESULT(vkMapMemory(device, stagingBuffer.memory, 0, memAlloc.allocationSize, 0, (void**)&data)); - memcpy(data, vertices.data(), vertexBufferSize); - memcpy(((char*)data) + vertexBufferSize, indices.data(), indexBufferSize); - - // Create a device local buffer to which the (host local) vertex data will be copied and which will be used for rendering - VkBufferCreateInfo vertexbufferCI{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - vertexbufferCI.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - vertexbufferCI.size = vertexBufferSize; - VK_CHECK_RESULT(vkCreateBuffer(device, &vertexbufferCI, nullptr, &vertexBuffer.handle)); - vkGetBufferMemoryRequirements(device, vertexBuffer.handle, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertexBuffer.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(device, vertexBuffer.handle, vertexBuffer.memory, 0)); - - // Create a device local buffer to which the (host local) index data will be copied and which will be used for rendering - VkBufferCreateInfo indexbufferCI{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - indexbufferCI.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - indexbufferCI.size = indexBufferSize; - VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferCI, nullptr, &indexBuffer.handle)); - vkGetBufferMemoryRequirements(device, indexBuffer.handle, &memReqs); - memAlloc.allocationSize = memReqs.size; - memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indexBuffer.memory)); - VK_CHECK_RESULT(vkBindBufferMemory(device, indexBuffer.handle, indexBuffer.memory, 0)); - - // Buffer copies have to be submitted to a queue, so we need a command buffer for them - VkCommandBuffer copyCmd; - - VkCommandBufferAllocateInfo cmdBufAllocateInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; - cmdBufAllocateInfo.commandPool = commandPool; - cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - cmdBufAllocateInfo.commandBufferCount = 1; - VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); - - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); - // Copy vertex and index buffers to the device - VkBufferCopy copyRegion{}; - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.handle, vertexBuffer.handle, 1, ©Region); - copyRegion.size = indexBufferSize; - // Indices are stored after the vertices in the source buffer, so we need to add an offset - copyRegion.srcOffset = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.handle, indexBuffer.handle, 1, ©Region); - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - - // Submit the command buffer to the queue to finish the copy - VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ©Cmd; - - // Create fence to ensure that the command buffer has finished executing - VkFenceCreateInfo fenceCI{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; - VkFence fence; - VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &fence)); - // Submit copies 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, commandPool, 1, ©Cmd); - - // The fence made sure copies are finished, so we can safely delete the staging buffer - vkDestroyBuffer(device, stagingBuffer.handle, nullptr); - vkFreeMemory(device, stagingBuffer.memory, nullptr); - } - - // Decriptors are used to pass data to shaders, for our sample we use a descriptor to pass parameters like matrices to the shader - void createDescriptors() - { - // Descriptors are allocated from a pool, that tells the implementation how many and what types of descriptors we are going to use (at maximum) - VkDescriptorPoolSize descriptorTypeCounts[1]{}; - // This example only one descriptor type (uniform buffer) - descriptorTypeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - // We have one buffer (and as such descriptor) per frame - descriptorTypeCounts[0].descriptorCount = MAX_CONCURRENT_FRAMES; - // 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 descriptorPoolCI{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; - descriptorPoolCI.poolSizeCount = 1; - descriptorPoolCI.pPoolSizes = descriptorTypeCounts; - // Set the max. number of descriptor sets that can be requested from this pool (requesting beyond this limit will result in an error) - // Our sample will create one set per uniform buffer per frame - descriptorPoolCI.maxSets = MAX_CONCURRENT_FRAMES; - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); - - // Descriptor set layouts define the interface between our application and the shader - // 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; - - VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; - descriptorLayoutCI.bindingCount = 1; - descriptorLayoutCI.pBindings = &layoutBinding; - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); - - // Where the descriptor set layout is the interface, the descriptor set points to actual data - // Descriptors that are changed per frame need to be multiplied, so we can update descriptor n+1 while n is still used by the GPU, so we create one per max frame in flight - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - VkDescriptorSetAllocateInfo allocInfo{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; - allocInfo.descriptorPool = descriptorPool; - allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = &descriptorSetLayout; - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &uniformBuffers[i].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{ VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; - - // The buffer's information is passed using a descriptor info structure - VkDescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = uniformBuffers[i].handle; - bufferInfo.range = sizeof(ShaderData); - - // Binding 0 : Uniform buffer - writeDescriptorSet.dstSet = uniformBuffers[i].descriptorSet; - writeDescriptorSet.descriptorCount = 1; - writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - writeDescriptorSet.pBufferInfo = &bufferInfo; - writeDescriptorSet.dstBinding = 0; - vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); - } - } - - // Create the depth (and stencil) buffer attachments - // While we don't do any depth testing in this sample, having depth testing is very common so it's a good idea to learn it from the very start - void setupDepthStencil() override - { - // Create an optimal tiled image used as the depth stencil attachment - VkImageCreateInfo imageCI{ 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; - imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image)); - - // Allocate memory for the image (device local) and bind it to our image - VkMemoryAllocateInfo memAlloc{ 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.memory)); - VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.memory, 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 depthStencilViewCI{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; - depthStencilViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthStencilViewCI.format = depthFormat; - depthStencilViewCI.subresourceRange = {}; - depthStencilViewCI.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) { - depthStencilViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; - } - depthStencilViewCI.subresourceRange.baseMipLevel = 0; - depthStencilViewCI.subresourceRange.levelCount = 1; - depthStencilViewCI.subresourceRange.baseArrayLayer = 0; - depthStencilViewCI.subresourceRange.layerCount = 1; - depthStencilViewCI.image = depthStencil.image; - VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilViewCI, nullptr, &depthStencil.view)); - } - - // 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(const std::string& filename) - { - size_t shaderSize; - char* shaderCode{ nullptr }; - -#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 shaderModuleCI{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; - shaderModuleCI.codeSize = shaderSize; - shaderModuleCI.pCode = (uint32_t*)shaderCode; - - VkShaderModule shaderModule; - VK_CHECK_RESULT(vkCreateShaderModule(device, &shaderModuleCI, nullptr, &shaderModule)); - - delete[] shaderCode; - - return shaderModule; - } else { - std::cerr << "Error: Could not open shader file \"" << filename << "\"" << std::endl; - return VK_NULL_HANDLE; - } - } - - void createPipeline() - { - // The pipeline layout is the interface telling the pipeline what type of descriptors will later be bound - VkPipelineLayoutCreateInfo pipelineLayoutCI{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; - pipelineLayoutCI.setLayoutCount = 1; - pipelineLayoutCI.pSetLayouts = &descriptorSetLayout; - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - - // 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 - - VkGraphicsPipelineCreateInfo pipelineCI{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; - // The layout used for this pipeline (can be shared among multiple pipelines using the same layout) - pipelineCI.layout = pipelineLayout; - - // 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 inputAssemblyStateCI{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; - inputAssemblyStateCI.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - - // Rasterization state - VkPipelineRasterizationStateCreateInfo rasterizationStateCI{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; - rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL; - rasterizationStateCI.cullMode = VK_CULL_MODE_NONE; - rasterizationStateCI.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - rasterizationStateCI.depthClampEnable = VK_FALSE; - rasterizationStateCI.rasterizerDiscardEnable = VK_FALSE; - rasterizationStateCI.depthBiasEnable = VK_FALSE; - rasterizationStateCI.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{}; - blendAttachmentState.colorWriteMask = 0xf; - blendAttachmentState.blendEnable = VK_FALSE; - VkPipelineColorBlendStateCreateInfo colorBlendStateCI{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; - colorBlendStateCI.attachmentCount = 1; - colorBlendStateCI.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 viewportStateCI{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; - viewportStateCI.viewportCount = 1; - viewportStateCI.scissorCount = 1; - - // Enable dynamic states - // Most states are baked into the pipeline, but there is somee state that can be dynamically changed within the command buffer to mak e things easuer - // 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 - std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI{ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; - dynamicStateCI.pDynamicStates = dynamicStateEnables.data(); - dynamicStateCI.dynamicStateCount = static_cast(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 depthStencilStateCI{ VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; - depthStencilStateCI.depthTestEnable = VK_TRUE; - depthStencilStateCI.depthWriteEnable = VK_TRUE; - depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; - depthStencilStateCI.depthBoundsTestEnable = VK_FALSE; - depthStencilStateCI.back.failOp = VK_STENCIL_OP_KEEP; - depthStencilStateCI.back.passOp = VK_STENCIL_OP_KEEP; - depthStencilStateCI.back.compareOp = VK_COMPARE_OP_ALWAYS; - depthStencilStateCI.stencilTestEnable = VK_FALSE; - depthStencilStateCI.front = depthStencilStateCI.back; - - // This example does not make use of multi sampling (for anti-aliasing), the state must still be set and passed to the pipeline - VkPipelineMultisampleStateCreateInfo multisampleStateCI{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; - multisampleStateCI.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - // 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 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 vertexInputStateCI{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; - vertexInputStateCI.vertexBindingDescriptionCount = 1; - vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding; - vertexInputStateCI.vertexAttributeDescriptionCount = 2; - vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributs.data(); - - // Shaders - std::array shaderStages{}; - - // Vertex shader - shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shaderStages[0].module = loadSPIRVShader(getShadersPath() + "triangle/triangle.vert.spv"); - shaderStages[0].pName = "main"; - assert(shaderStages[0].module != VK_NULL_HANDLE); - - // Fragment shader - shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shaderStages[1].module = loadSPIRVShader(getShadersPath() + "triangle/triangle.frag.spv"); - shaderStages[1].pName = "main"; - assert(shaderStages[1].module != VK_NULL_HANDLE); - - // Set pipeline shader stage info - pipelineCI.stageCount = static_cast(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - - // Attachment information for dynamic rendering - VkPipelineRenderingCreateInfoKHR pipelineRenderingCI{ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR }; - pipelineRenderingCI.colorAttachmentCount = 1; - pipelineRenderingCI.pColorAttachmentFormats = &swapChain.colorFormat; - pipelineRenderingCI.depthAttachmentFormat = depthFormat; - pipelineRenderingCI.stencilAttachmentFormat = depthFormat; - - // Assign the pipeline states to the pipeline creation info structure - 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.pNext = &pipelineRenderingCI; - - // Create rendering pipeline using the specified states - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline)); - - // Shader modules can safely be destroyed when the pipeline has been created - vkDestroyShaderModule(device, shaderStages[0].module, nullptr); - vkDestroyShaderModule(device, shaderStages[1].module, nullptr); - } - - void createUniformBuffers() - { - // Prepare and initialize the per-frame uniform buffer blocks containing shader uniforms - // Single uniforms like in OpenGL are no longer present in Vulkan. All shader uniforms are passed via uniform buffer blocks - VkBufferCreateInfo bufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; - bufferInfo.size = sizeof(ShaderData); - // This buffer will be used as a uniform buffer - bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - - // Create the buffers - for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) { - VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBuffers[i].handle)); - // Get memory requirements including size, alignment and memory type based on the buffer type we request (uniform buffer) - VkMemoryRequirements memReqs; - vkGetBufferMemoryRequirements(device, uniformBuffers[i].handle, &memReqs); - VkMemoryAllocateInfo allocInfo{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - // Note that we use the size we got from the memory requirements and not the actual buffer size, as the former may be larger due to alignment requirements of the device - 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). - 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, &(uniformBuffers[i].memory))); - // Bind memory to buffer - VK_CHECK_RESULT(vkBindBufferMemory(device, uniformBuffers[i].handle, uniformBuffers[i].memory, 0)); - // We map the buffer once, so we can update it without having to map it again - VK_CHECK_RESULT(vkMapMemory(device, uniformBuffers[i].memory, 0, sizeof(ShaderData), 0, (void**)&uniformBuffers[i].mapped)); - } - - } - - void prepare() override - { - VulkanExampleBase::prepare(); - createSynchronizationPrimitives(); - createCommandBuffers(); - createVertexBuffer(); - createUniformBuffers(); - createDescriptors(); - createPipeline(); - prepared = true; - } - - void render() override - { - // Use a fence to wait until the command buffer has finished execution before using it again - vkWaitForFences(device, 1, &waitFences[currentFrame], VK_TRUE, UINT64_MAX); - VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentFrame])); - - // Get the next swap chain image from the implementation - // Note that the implementation is free to return the images in any order, so we must use the acquire function and can't just cycle through the images/imageIndex on our own - uint32_t imageIndex{ 0 }; - VkResult result = vkAcquireNextImageKHR(device, swapChain.swapChain, UINT64_MAX, presentCompleteSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); - if (result == VK_ERROR_OUT_OF_DATE_KHR) { - windowResize(); - return; - } else if ((result != VK_SUCCESS) && (result != VK_SUBOPTIMAL_KHR)) { - throw "Could not acquire the next swap chain image!"; - } - - // Update the uniform buffer for the next frame - ShaderData shaderData{}; - shaderData.projectionMatrix = camera.matrices.perspective; - shaderData.viewMatrix = camera.matrices.view; - shaderData.modelMatrix = glm::mat4(1.0f); - // Copy the current matrices to the current frame's uniform buffer. As we requested a host coherent memory type for the uniform buffer, the write is instantly visible to the GPU. - memcpy(uniformBuffers[currentFrame].mapped, &shaderData, sizeof(ShaderData)); - - // Build the command buffer for the next frame to render - vkResetCommandBuffer(commandBuffers[currentFrame], 0); - VkCommandBufferBeginInfo cmdBufInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; - const VkCommandBuffer commandBuffer = commandBuffers[currentFrame]; - VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo)); - - // With dynamic rendering we need to explicitly add layout transitions by using barriers, this set of barriers prepares the color and depth images for output - vks::tools::insertImageMemoryBarrier(commandBuffer, swapChain.images[imageIndex], 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); - vks::tools::insertImageMemoryBarrier(commandBuffer, depthStencil.image, 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_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 - // Color attachment - VkRenderingAttachmentInfo colorAttachment{ VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO }; - colorAttachment.imageView = swapChain.imageViews[imageIndex]; - colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.clearValue.color = { 0.0f, 0.0f, 0.2f, 0.0f }; - // Depth/stencil attachment - VkRenderingAttachmentInfo depthStencilAttachment{ VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO }; - depthStencilAttachment.imageView = depthStencil.view; - depthStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthStencilAttachment.clearValue.depthStencil = { 1.0f, 0 }; - - VkRenderingInfo renderingInfo{ 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; - - // Start a dynamic rendering section - vkCmdBeginRendering(commandBuffer, &renderingInfo); - // Update dynamic viewport state - VkViewport viewport{ 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f }; - vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - // Update dynamic scissor state - VkRect2D scissor{ 0, 0, width, height }; - vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - // Bind descriptor set for the current frame's uniform buffer, so the shader uses the data from that buffer for this draw - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &uniformBuffers[currentFrame].descriptorSet, 0, nullptr); - // The pipeline (state object) contains all states of the rendering pipeline, binding it will set all the states specified at pipeline creation time - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - // Bind triangle vertex buffer (contains position and colors) - VkDeviceSize offsets[1]{ 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.handle, offsets); - // Bind triangle index buffer - vkCmdBindIndexBuffer(commandBuffer, indexBuffer.handle, 0, VK_INDEX_TYPE_UINT32); - // Draw indexed triangle - vkCmdDrawIndexed(commandBuffer, indexCount, 1, 0, 0, 0); - // Finish the current dynamic rendering section - vkCmdEndRendering(commandBuffer); - - // This barrier prepares the color image for presentation, we don't need to care for the depth image - vks::tools::insertImageMemoryBarrier(commandBuffer, swapChain.images[imageIndex], VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_NONE, VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); - VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); - - // Submit the command buffer to the graphics queue - - // 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{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; - submitInfo.pWaitDstStageMask = &waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at - submitInfo.pCommandBuffers = &commandBuffer; // Command buffers(s) to execute in this batch (submission) - submitInfo.commandBufferCount = 1; // We submit a single command buffer - - // Semaphore to wait upon before the submitted command buffer starts executing - submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentFrame]; - submitInfo.waitSemaphoreCount = 1; - // Semaphore to be signaled when command buffers have completed - submitInfo.pSignalSemaphores = &renderCompleteSemaphores[imageIndex]; - submitInfo.signalSemaphoreCount = 1; - - // Submit to the graphics queue passing a wait fence - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentFrame])); - - // Present the current frame 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 - VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &renderCompleteSemaphores[imageIndex]; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &swapChain.swapChain; - presentInfo.pImageIndices = &imageIndex; - result = vkQueuePresentKHR(queue, &presentInfo); - if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) { - windowResize(); - } else if (result != VK_SUCCESS) { - throw "Could not present the image to the swap chain!"; - } - - // Select the next frame to render to, based on the max. no. of concurrent frames - currentFrame = (currentFrame + 1) % MAX_CONCURRENT_FRAMES; - } - - // Override these as otherwise the base class would generate frame buffers and render passes - void setupFrameBuffer() override {} - void setupRenderPass() override {} -}; - -// OS specific 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(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR, _In_ int) -{ - 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_USE_PLATFORM_METAL_EXT)) && 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; -} -#elif defined(VK_USE_PLATFORM_SCREEN_QNX) -VULKAN_EXAMPLE_MAIN() -#endif diff --git a/examples/validate_all.py b/examples/validate_all.py deleted file mode 100644 index aaf3daa7..00000000 --- a/examples/validate_all.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (C) 2025 by Sascha Willems - www.saschawillems.de -# This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) - -# Runs all samples in benchmark mode and stores all validation messages into a single text file - -# Note: Needs to be copied to where the binary files have been compiled (e.g. build/windows/bin/debug) - -import glob -import subprocess -import os -import platform - -if os.path.exists("validation_output.txt"): - os.remove("validation_output.txt") -if platform.system() == 'Linux' or platform.system() == 'Darwin': - binaries = "./*" -else: - binaries = "*.exe" -for sample in glob.glob(binaries): - # Skip headless samples, as they require a manual keypress - if "headless" in sample: - continue - subprocess.call("%s -v -vl -b -bfs %s" % (sample, 50), shell=True) \ No newline at end of file diff --git a/examples/variablerateshading/variablerateshading.cpp b/examples/variablerateshading/variablerateshading.cpp deleted file mode 100644 index 6c07987c..00000000 --- a/examples/variablerateshading/variablerateshading.cpp +++ /dev/null @@ -1,604 +0,0 @@ -/* -* Vulkan Example - Variable rate shading -* -* Copyright (C) 2020-2024 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() -{ - 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_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - enabledDeviceExtensions.push_back(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME); -} - -VulkanExample::~VulkanExample() -{ - vkDestroyPipeline(device, pipelines.masked, nullptr); - vkDestroyPipeline(device, pipelines.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 - enabledPhysicalDeviceShadingRateImageFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR; - enabledPhysicalDeviceShadingRateImageFeaturesKHR.attachmentFragmentShadingRate = VK_TRUE; - enabledPhysicalDeviceShadingRateImageFeaturesKHR.pipelineFragmentShadingRate = VK_FALSE; - enabledPhysicalDeviceShadingRateImageFeaturesKHR.primitiveFragmentShadingRate = VK_FALSE; - deviceCreatepNextChain = &enabledPhysicalDeviceShadingRateImageFeaturesKHR; -} - -/* - If the window has been resized, we need to recreate the shading rate image and the render pass. That's because the render pass holds information on the fragment shading rate image resolution - -*/ -void VulkanExample::handleResize() -{ - vkDeviceWaitIdle(device); - // Invalidate the shading rate image, will be recreated in the renderpass setup - vkDestroyImageView(device, shadingRateImage.view, nullptr); - vkDestroyImage(device, shadingRateImage.image, nullptr); - vkFreeMemory(device, shadingRateImage.memory, nullptr); - prepareShadingRateImage(); - // Recreate the render pass and update it with the new fragment shading rate image resolution - vkDestroyRenderPass(device, renderPass, nullptr); - setupRenderPass(); - resized = false; -} - -void VulkanExample::setupFrameBuffer() -{ - if (resized) { - handleResize(); - } - - if (shadingRateImage.image == VK_NULL_HANDLE) { - prepareShadingRateImage(); - } - - VkImageView attachments[3]; - - // Depth/Stencil attachment is the same for all frame buffers - attachments[1] = depthStencil.view; - // Fragment shading rate attachment - attachments[2] = shadingRateImage.view; - - VkFramebufferCreateInfo frameBufferCreateInfo{}; - frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - frameBufferCreateInfo.renderPass = renderPass; - frameBufferCreateInfo.attachmentCount = 3; - frameBufferCreateInfo.pAttachments = attachments; - frameBufferCreateInfo.width = width; - frameBufferCreateInfo.height = height; - frameBufferCreateInfo.layers = 1; - - // Create frame buffers for every swap chain image - frameBuffers.resize(swapChain.images.size()); - for (uint32_t i = 0; i < frameBuffers.size(); i++) { - attachments[0] = swapChain.imageViews[i]; - VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i])); - } -} - -void VulkanExample::setupRenderPass() -{ - // Note that we need to use ...2KHR types in here, as fragment shading rate requires additional properties and structs to be passed at renderpass creation - if (!vkCreateRenderPass2KHR) { - vkCreateRenderPass2KHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateRenderPass2KHR")); - } - - if (shadingRateImage.image == VK_NULL_HANDLE) { - prepareShadingRateImage(); - } - - std::array attachments = {}; - // Color attachment - attachments[0].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; - 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].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; - 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; - // Fragment shading rate attachment - attachments[2].sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; - attachments[2].format = VK_FORMAT_R8_UINT; - attachments[2].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[2].initialLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; - attachments[2].finalLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; - - VkAttachmentReference2KHR colorReference = {}; - colorReference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; - colorReference.attachment = 0; - colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorReference.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - - VkAttachmentReference2KHR depthReference = {}; - depthReference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; - depthReference.attachment = 1; - depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - depthReference.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - - // Setup the attachment reference for the shading rate image attachment in slot 2 - VkAttachmentReference2 fragmentShadingRateReference{}; - fragmentShadingRateReference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; - fragmentShadingRateReference.attachment = 2; - fragmentShadingRateReference.layout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; - - // Setup the attachment info for the shading rate image, which will be added to the sub pass via structure chaining (in pNext) - VkFragmentShadingRateAttachmentInfoKHR fragmentShadingRateAttachmentInfo{}; - fragmentShadingRateAttachmentInfo.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR; - fragmentShadingRateAttachmentInfo.pFragmentShadingRateAttachment = &fragmentShadingRateReference; - fragmentShadingRateAttachmentInfo.shadingRateAttachmentTexelSize.width = physicalDeviceShadingRateImageProperties.maxFragmentShadingRateAttachmentTexelSize.width; - fragmentShadingRateAttachmentInfo.shadingRateAttachmentTexelSize.height = physicalDeviceShadingRateImageProperties.maxFragmentShadingRateAttachmentTexelSize.height; - - VkSubpassDescription2KHR subpassDescription = {}; - subpassDescription.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2; - 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; - subpassDescription.pNext = &fragmentShadingRateAttachmentInfo; - - // Subpass dependencies for layout transitions - std::array dependencies = {}; - - dependencies[0].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2; - 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 | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - dependencies[1].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2; - dependencies[1].srcSubpass = 0; - dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; - dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_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 | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; - - VkRenderPassCreateInfo2KHR renderPassCI = {}; - renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2; - renderPassCI.attachmentCount = static_cast(attachments.size()); - renderPassCI.pAttachments = attachments.data(); - renderPassCI.subpassCount = 1; - renderPassCI.pSubpasses = &subpassDescription; - renderPassCI.dependencyCount = static_cast(dependencies.size()); - renderPassCI.pDependencies = dependencies.data(); - - VK_CHECK_RESULT(vkCreateRenderPass2KHR(device, &renderPassCI, nullptr, &renderPass)); -} - -void VulkanExample::buildCommandBuffers() -{ - // As this is an extension, we need to manually load the extension pointers - if (!vkCmdSetFragmentShadingRateKHR) { - vkCmdSetFragmentShadingRateKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetFragmentShadingRateKHR")); - } - - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - - VkClearValue clearValues[3]; - clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 1.0f } };; - clearValues[1].depthStencil = { 1.0f, 0 }; - clearValues[2].color = { {0.0f, 0.0f, 0.0f, 0.0f} }; - - 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; - - 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); - - // Set the fragment shading rate state for the current pipeline - VkExtent2D fragmentSize = { 1, 1 }; - VkFragmentShadingRateCombinerOpKHR combinerOps[2]; - // The combiners determine how the different shading rate values for the pipeline, primitives and attachment are combined - if (enableShadingRate) - { - // If shading rate from attachment is enabled, we set the combiner, so that the values from the attachment are used - // Combiner for pipeline (A) and primitive (B) - Not used in this sample - combinerOps[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; - // Combiner for pipeline (A) and attachment (B), replace the pipeline default value (fragment_size) with the fragment sizes stored in the attachment - combinerOps[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR; - } - else - { - // If shading rate from attachment is disabled, we keep the value set via the dynamic state - combinerOps[0] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; - combinerOps[1] = VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; - } - vkCmdSetFragmentShadingRateKHR(drawCmdBuffers[i], &fragmentSize, combinerOps); - - // Render the scene - 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 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 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 setLayouts = { - descriptorSetLayout, - vkglTF::descriptorSetLayoutImage, - }; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Descriptor set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); -} - -// [POI] -void VulkanExample::prepareShadingRateImage() -{ - // As this is an extension, we need to manually load the extension pointers - if (!vkGetPhysicalDeviceFragmentShadingRatesKHR) { - vkGetPhysicalDeviceFragmentShadingRatesKHR = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFragmentShadingRatesKHR")); - } - - // Get properties of this extensions, which also contains texel sizes required to setup the image - physicalDeviceShadingRateImageProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR; - VkPhysicalDeviceProperties2 deviceProperties2{}; - deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; - deviceProperties2.pNext = &physicalDeviceShadingRateImageProperties; - vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2); - - // We need to check if the requested format for the shading rate attachment supports the required flag - const VkFormat imageFormat = VK_FORMAT_R8_UINT; - VkFormatProperties formatProperties; - vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); - if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR)) - { - throw std::runtime_error("Selected shading rate attachment image format does not fragment shading rate"); - } - - // 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(ceil(width / (float)physicalDeviceShadingRateImageProperties.maxFragmentShadingRateAttachmentTexelSize.width)); - imageExtent.height = static_cast(ceil(height / (float)physicalDeviceShadingRateImageProperties.maxFragmentShadingRateAttachmentTexelSize.height)); - imageExtent.depth = 1; - - VkImageCreateInfo imageCI{}; - imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageCI.imageType = VK_IMAGE_TYPE_2D; - imageCI.format = imageFormat; - 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_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &shadingRateImage.image)); - VkMemoryRequirements memReqs{}; - vkGetImageMemoryRequirements(device, shadingRateImage.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, &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)); - - // The shading rates are stored in a buffer that'll be copied to the shading rate image - VkDeviceSize bufferSize = imageExtent.width * imageExtent.height * sizeof(uint8_t); - - // Fragment sizes are encoded in a single texel as follows: - // size(w) = 2^((texel/4) & 3) - // size(h)h = 2^(texel & 3) - - // Populate it with the lowest possible shading rate - uint8_t val = (4 >> 1) | (4 << 1); - uint8_t* shadingRatePatternData = new uint8_t[bufferSize]; - memset(shadingRatePatternData, val, bufferSize); - - // Get a list of available shading rate patterns - std::vector fragmentShadingRates{}; - uint32_t fragmentShadingRatesCount = 0; - vkGetPhysicalDeviceFragmentShadingRatesKHR(physicalDevice, &fragmentShadingRatesCount, nullptr); - if (fragmentShadingRatesCount > 0) { - fragmentShadingRates.resize(fragmentShadingRatesCount); - for (VkPhysicalDeviceFragmentShadingRateKHR& fragmentShadingRate : fragmentShadingRates) { - // In addition to the value, we also need to set the sType for each rate to comply with the spec or else the call to vkGetPhysicalDeviceFragmentShadingRatesKHR will result in undefined behaviour - fragmentShadingRate.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR; - } - vkGetPhysicalDeviceFragmentShadingRatesKHR(physicalDevice, &fragmentShadingRatesCount, fragmentShadingRates.data()); - } - // Create a circular pattern from the available list of fragment shading rates with decreasing sampling rates outwards (max. range, pattern) - // Shading rates returned by vkGetPhysicalDeviceFragmentShadingRatesKHR are ordered from largest to smallest - std::map patternLookup{}; - float range = 25.0f / static_cast(fragmentShadingRates.size()); - float currentRange = 8.0f; - for (size_t i = fragmentShadingRates.size() - 1; i > 0; i--) { - uint32_t rate_v = fragmentShadingRates[i].fragmentSize.width == 1 ? 0 : (fragmentShadingRates[i].fragmentSize.width >> 1); - uint32_t rate_h = fragmentShadingRates[i].fragmentSize.height == 1 ? 0 : (fragmentShadingRates[i].fragmentSize.height << 1); - patternLookup[currentRange] = rate_v | rate_h; - currentRange += range; - } - - 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 = (static_cast(imageExtent.width) / 2.0f - static_cast(x)) / imageExtent.width * 100.0f; - const float deltaY = (static_cast(imageExtent.height) / 2.0f - static_cast(y)) / imageExtent.height * 100.0f; - const float dist = std::sqrt(deltaX * deltaX + deltaY * deltaY); - for (auto pattern : patternLookup) { - if (dist < pattern.first) { - *ptrData = pattern.second; - break; - } - } - ptrData++; - } - } - - // Copy the shading rate pattern to the shading rate image - - 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_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); - std::array 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(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 { - VkBool32 alphaMask; - float alphaMaskCutoff; - } specializationData; - specializationData.alphaMask = false; - specializationData.alphaMaskCutoff = 0.5f; - const std::vector 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; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.opaque)); - - specializationData.alphaMask = true; - rasterizationStateCI.cullMode = VK_CULL_MODE_NONE; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.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(); - 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/examples/variablerateshading/variablerateshading.h b/examples/variablerateshading/variablerateshading.h deleted file mode 100644 index 91afe1b2..00000000 --- a/examples/variablerateshading/variablerateshading.h +++ /dev/null @@ -1,70 +0,0 @@ -/* -* Vulkan Example - Variable rate shading -* -* Copyright (C) 2020-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - vkglTF::Model scene; - - struct ShadingRateImage { - VkImage image{ VK_NULL_HANDLE }; - 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; - - VkPipelineLayout pipelineLayout; - VkDescriptorSet descriptorSet; - VkDescriptorSetLayout descriptorSetLayout; - - VkPhysicalDeviceFragmentShadingRatePropertiesKHR physicalDeviceShadingRateImageProperties{}; - VkPhysicalDeviceFragmentShadingRateFeaturesKHR enabledPhysicalDeviceShadingRateImageFeaturesKHR{}; - - PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR vkGetPhysicalDeviceFragmentShadingRatesKHR{ nullptr }; - PFN_vkCmdSetFragmentShadingRateKHR vkCmdSetFragmentShadingRateKHR{ nullptr }; - PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR{ nullptr }; - - VulkanExample(); - ~VulkanExample(); - virtual void getEnabledFeatures() override; - void handleResize(); - void buildCommandBuffers() override; - void loadAssets(); - void prepareShadingRateImage(); - void setupDescriptors(); - void preparePipelines(); - void prepareUniformBuffers(); - void updateUniformBuffers(); - void prepare() override; - void setupFrameBuffer() override; - void setupRenderPass() override; - virtual void render() override; - virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay) override; -}; diff --git a/examples/vertexattributes/README.md b/examples/vertexattributes/README.md deleted file mode 100644 index 16969750..00000000 --- a/examples/vertexattributes/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Vertex attributes - -## Synopsis - -This sample demonstrates two different ways of providing vertex data to the GPU using either interleaved or separate buffers for vertex attributes. - -## Shader interface - -The shader interface for passing the vertex attributes is the same, no matter if the data provided is coming from a single interleaved or multiple separate buffers. - -```glsl -layout (location = 0) in vec3 inPos; -layout (location = 1) in vec3 inNormal; -layout (location = 2) in vec2 inUV; -layout (location = 3) in vec4 inTangent; -``` - -## Interleaved vertex attributes - -In an interleaved vertex buffer, the components that make up a single vertex are stored after each other, so the stride of a single vertex is the sum of it's component's sizes. - -![Interleaved buffer layout](interleavedbuffer.png) - -```cpp -// Binding -const std::vector vertexInputBindingsInterleaved = { - { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }, -}; - -// Attribute -const std::vector vertexInputAttributesInterleaved = { - { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) }, - { 1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) }, - { 2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) }, - { 3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tangent) }, -}; -``` - -## Separate vertex attributes - -When using separate buffers, each component is stored in it's own buffer. So e.g. the position buffer only contains vertex positions stored consecutively. - -![Interleaved buffer layout](separatebuffers.png) - -```cpp -// Bindings -const std::vector vertexInputBindingsSeparate = { - { 0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX }, - { 1, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX }, - { 2, sizeof(glm::vec2), VK_VERTEX_INPUT_RATE_VERTEX }, - { 3, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX }, -}; - -// Attributes -const std::vector vertexInputAttributesSeparate = { - { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 }, - { 1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0 }, - { 2, 2, VK_FORMAT_R32G32_SFLOAT, 0 }, - { 3, 3, VK_FORMAT_R32G32B32A32_SFLOAT, 0 }, -}; -``` \ No newline at end of file diff --git a/examples/vertexattributes/interleavedbuffer.png b/examples/vertexattributes/interleavedbuffer.png deleted file mode 100644 index 109de3a015ebbe9d710df2a08f105888db62e59b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9565 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 diff --git a/examples/vertexattributes/separatebuffers.png b/examples/vertexattributes/separatebuffers.png deleted file mode 100644 index abbeec2b70b718c86b6fb4c5c1345409cf439de0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18867 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 0) { - for (size_t i = 0; i < inputNode.children.size(); i++) { - loadSceneNode(input.nodes[inputNode.children[i]], input, &node); - } - } - - // 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(indexBuffer.size()); - uint32_t vertexStart = static_cast(vertexBuffer.size()); - uint32_t indexCount = 0; - - // Vertex attributes - const float* positionBuffer = nullptr; - const float* normalsBuffer = nullptr; - const float* texCoordsBuffer = nullptr; - const float* tangentsBuffer = nullptr; - size_t vertexCount = 0; - - // Anonymous functions to simplify buffer view access - auto getBuffer = [glTFPrimitive, input, &vertexCount](const std::string attributeName, const float* &bufferTarget) { - if (glTFPrimitive.attributes.find(attributeName) != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find(attributeName)->second]; - const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - bufferTarget = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - if (attributeName == "POSITION") { - vertexCount = accessor.count; - } - } - }; - - // Get buffer pointers to the vertex attributes used in this sample - getBuffer("POSITION", positionBuffer); - getBuffer("NORMAL", normalsBuffer); - getBuffer("TEXCOORD_0", texCoordsBuffer); - getBuffer("TANGENT", tangentsBuffer); - - // Append attributes to the vertex buffers - for (size_t v = 0; v < vertexCount; v++) { - - // Append interleaved attributes - 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.tangent = tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f); - vertexBuffer.push_back(vert); - - // Append separate attributes - vertexAttributeBuffers.pos.push_back(glm::make_vec3(&positionBuffer[v * 3])); - vertexAttributeBuffers.normal.push_back(glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f)))); - vertexAttributeBuffers.tangent.push_back(tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f)); - vertexAttributeBuffers.uv.push_back(texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f)); - } - - // 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(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&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(&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(&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); - } -} - -VulkanExample::VulkanExample() : VulkanExampleBase() -{ - title = "Separate/interleaved vertex attribute buffers"; - 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() -{ - if (device) { - vkDestroyPipeline(device, pipelines.vertexAttributesInterleaved, nullptr); - vkDestroyPipeline(device, pipelines.vertexAttributesSeparate, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); - indices.destroy(); - shaderData.buffer.destroy(); - separateVertexBuffers.normal.destroy(); - separateVertexBuffers.pos.destroy(); - separateVertexBuffers.tangent.destroy(); - separateVertexBuffers.uv.destroy(); - interleavedVertexBuffer.destroy(); - for (Image image : scene.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); - } - } -} - -void VulkanExample::getEnabledFeatures() -{ - enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy; -} - -void VulkanExample::drawSceneNode(VkCommandBuffer commandBuffer, Node node) -{ - if (node.mesh.primitives.size() > 0) { - PushConstBlock pushConstBlock; - glm::mat4 nodeMatrix = node.matrix; - Node* currentParent = node.parent; - while (currentParent) { - nodeMatrix = currentParent->matrix * nodeMatrix; - currentParent = currentParent->parent; - } - for (Primitive& primitive : node.mesh.primitives) { - if (primitive.indexCount > 0) { - Material& material = scene.materials[primitive.materialIndex]; - pushConstBlock.nodeMatrix = nodeMatrix; - pushConstBlock.alphaMask = (material.alphaMode == "MASK"); - pushConstBlock.alphaMaskCutoff = material.alphaCutOff; - vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); - 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) { - drawSceneNode(commandBuffer, child); - } -} - -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); - - // Select the separate or interleaved vertex binding pipeline - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, vertexAttributeSettings == VertexAttributeSettings::separate ? pipelines.vertexAttributesSeparate : pipelines.vertexAttributesInterleaved); - - // Bind scene matrices descriptor to set 0 - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); - - // Use the same index buffer, no matter how vertex attributes are passed - vkCmdBindIndexBuffer(drawCmdBuffers[i], indices.buffer, 0, VK_INDEX_TYPE_UINT32); - - if (vertexAttributeSettings == VertexAttributeSettings::separate) { - // Using separate vertex attribute bindings requires binding multiple attribute buffers - VkDeviceSize offsets[4] = { 0, 0, 0, 0 }; - std::array buffers = { separateVertexBuffers.pos.buffer, separateVertexBuffers.normal.buffer, separateVertexBuffers.uv.buffer, separateVertexBuffers.tangent.buffer }; - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, static_cast(buffers.size()), buffers.data(), offsets); - } - else { - // Using interleaved attribute bindings only requires one buffer to be bound - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &interleavedVertexBuffer.buffer, offsets); - } - // Render all nodes starting at top-level - for (auto& node : nodes) { - drawSceneNode(drawCmdBuffers[i], node); - } - - 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); - - size_t pos = filename.find_last_of('/'); - std::string path = filename.substr(0, pos); - - if (!fileLoaded) { - vks::tools::exitFatal("Could not open the glTF file.\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1); - return; - } - - // Load images - scene.images.resize(glTFInput.images.size()); - for (size_t i = 0; i < glTFInput.images.size(); i++) { - tinygltf::Image& glTFImage = glTFInput.images[i]; - scene.images[i].texture.loadFromFile(path + "/" + glTFImage.uri, VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - // Load textures - scene.textures.resize(glTFInput.textures.size()); - for (size_t i = 0; i < glTFInput.textures.size(); i++) { - scene.textures[i].imageIndex = glTFInput.textures[i].source; - } - // Load materials - scene.materials.resize(glTFInput.materials.size()); - for (size_t i = 0; i < glTFInput.materials.size(); i++) { - // We only read the most basic properties required for our sample - tinygltf::Material glTFMaterial = glTFInput.materials[i]; - // Get the base color factor - if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { - scene.materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); - } - // Get base color texture index - if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { - scene.materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); - } - // Get the normal map texture index - if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) { - scene.materials[i].normalTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex(); - } - // Get some additional material parameters that are used in this sample - scene.materials[i].alphaMode = glTFMaterial.alphaMode; - scene.materials[i].alphaCutOff = (float)glTFMaterial.alphaCutoff; - } - // Load nodes - 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]]; - loadSceneNode(node, glTFInput, nullptr); - } - - uploadVertexData(); -} - -void VulkanExample::uploadVertexData() -{ - // Upload vertex and index buffers - - // Anonymous functions to simplify buffer creation - // Create a staging buffer used as a source for copies - auto createStagingBuffer = [this](vks::Buffer& buffer, void* data, VkDeviceSize size) { - VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &buffer, size, data)); - }; - // Create a device local buffer used as a target for copies - auto createDeviceBuffer = [this](vks::Buffer& buffer, VkDeviceSize size, VkBufferUsageFlags usageFlags = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) { - VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &buffer, size)); - }; - - VkCommandBuffer copyCmd; - VkBufferCopy copyRegion{}; - - /* - Interleaved vertex attributes - We create one single buffer containing the interleaved vertex attributes - */ - size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); - vks::Buffer vertexStaging; - createStagingBuffer(vertexStaging, vertexBuffer.data(), vertexBufferSize); - createDeviceBuffer(interleavedVertexBuffer, vertexStaging.size); - - // Copy data from staging buffer (host) do device local buffer (gpu) - copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, interleavedVertexBuffer.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - vertexStaging.destroy(); - - /* - Separate vertex attributes - We create multiple separate buffers for each of the vertex attributes (position, normals, etc.) - */ - std::array stagingBuffers; - createStagingBuffer(stagingBuffers[0], vertexAttributeBuffers.pos.data(), vertexAttributeBuffers.pos.size() * sizeof(vertexAttributeBuffers.pos[0])); - createStagingBuffer(stagingBuffers[1], vertexAttributeBuffers.normal.data(), vertexAttributeBuffers.normal.size() * sizeof(vertexAttributeBuffers.normal[0])); - createStagingBuffer(stagingBuffers[2], vertexAttributeBuffers.uv.data(), vertexAttributeBuffers.uv.size() * sizeof(vertexAttributeBuffers.uv[0])); - createStagingBuffer(stagingBuffers[3], vertexAttributeBuffers.tangent.data(), vertexAttributeBuffers.tangent.size() * sizeof(vertexAttributeBuffers.tangent[0])); - - createDeviceBuffer(separateVertexBuffers.pos, stagingBuffers[0].size); - createDeviceBuffer(separateVertexBuffers.normal, stagingBuffers[1].size); - createDeviceBuffer(separateVertexBuffers.uv, stagingBuffers[2].size); - createDeviceBuffer(separateVertexBuffers.tangent, stagingBuffers[3].size); - - // Stage - std::vector attributeBuffers = { - separateVertexBuffers.pos, - separateVertexBuffers.normal, - separateVertexBuffers.uv, - separateVertexBuffers.tangent, - }; - - // Copy data from staging buffer (host) do device local buffer (gpu) - copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - for (size_t i = 0; i < attributeBuffers.size(); i++) { - copyRegion.size = attributeBuffers[i].size; - vkCmdCopyBuffer(copyCmd, stagingBuffers[i].buffer, attributeBuffers[i].buffer, 1, ©Region); - } - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - for (size_t i = 0; i < 4; i++) { - stagingBuffers[i].destroy(); - } - - /* - Index buffer - The index buffer is always the same, no matter how we pass the vertex attributes - */ - size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); - vks::Buffer indexStaging; - createStagingBuffer(indexStaging, indexBuffer.data(), indexBufferSize); - createDeviceBuffer(indices, indexStaging.size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); - // Copy data from staging buffer (host) do device local buffer (gpu) - copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - // Free staging resources - indexStaging.destroy(); -} - -void VulkanExample::loadAssets() -{ - loadglTFFile(getAssetPath() + "models/sponza/sponza.gltf"); -} - -void VulkanExample::setupDescriptors() -{ - // 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 poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(scene.materials.size()) * 2), - }; - // One set for matrices and one per model image/texture - const uint32_t maxSetCount = static_cast(scene.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 setLayoutBindings = { - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0) - }; - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(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 setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; - VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(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 | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushConstBlock), 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 the materials - for (auto& material : scene.materials) { - const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &material.descriptorSet)); - VkDescriptorImageInfo colorMap = scene.images[material.baseColorTextureIndex].texture.descriptor; - VkDescriptorImageInfo normalMap = scene.images[material.normalTextureIndex].texture.descriptor; - std::vector 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(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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); - VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); - std::array shaderStages; - - 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(shaderStages.size()); - pipelineCI.pStages = shaderStages.data(); - - shaderStages[0] = loadShader(getShadersPath() + "vertexattributes/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getShadersPath() + "vertexattributes/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - - // Interleaved vertex attributes - // One Binding (one buffer) and multiple attributes - const std::vector vertexInputBindingsInterleaved = { - { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }, - }; - const std::vector vertexInputAttributesInterleaved = { - { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) }, - { 1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) }, - { 2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) }, - { 3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tangent) }, - }; - - vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(vertexInputBindingsInterleaved, vertexInputAttributesInterleaved); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.vertexAttributesInterleaved)); - - // Separate vertex attribute - // Multiple bindings (for each attribute buffer) and multiple attribues - const std::vector vertexInputBindingsSeparate = { - { 0, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX }, - { 1, sizeof(glm::vec3), VK_VERTEX_INPUT_RATE_VERTEX }, - { 2, sizeof(glm::vec2), VK_VERTEX_INPUT_RATE_VERTEX }, - { 3, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX }, - }; - const std::vector vertexInputAttributesSeparate = { - { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 }, - { 1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0 }, - { 2, 2, VK_FORMAT_R32G32_SFLOAT, 0 }, - { 3, 3, VK_FORMAT_R32G32B32A32_SFLOAT, 0 }, - }; - - vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(vertexInputBindingsSeparate, vertexInputAttributesSeparate); - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.vertexAttributesSeparate)); -} - -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() -{ - updateUniformBuffers(); - renderFrame(); -} - -void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay) -{ - if (overlay->header("Vertex buffer attributes")) { - bool interleaved = (vertexAttributeSettings == VertexAttributeSettings::interleaved); - bool separate = (vertexAttributeSettings == VertexAttributeSettings::separate); - if (overlay->radioButton("Interleaved", interleaved)) { - vertexAttributeSettings = VertexAttributeSettings::interleaved; - buildCommandBuffers(); - } - if (overlay->radioButton("Separate", separate)) { - vertexAttributeSettings = VertexAttributeSettings::separate; - buildCommandBuffers(); - } - } -} - -VULKAN_EXAMPLE_MAIN() diff --git a/examples/vertexattributes/vertexattributes.h b/examples/vertexattributes/vertexattributes.h deleted file mode 100644 index 7e16dafb..00000000 --- a/examples/vertexattributes/vertexattributes.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Vulkan Example - Passing vertex attributes using interleaved and separate buffers - * - * Copyright (C) 2022-2023 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 -#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" - -struct PushConstBlock { - glm::mat4 nodeMatrix; - uint32_t alphaMask; - float alphaMaskCutoff; -}; - -struct Material { - glm::vec4 baseColorFactor = glm::vec4(1.0f); - uint32_t baseColorTextureIndex; - uint32_t normalTextureIndex; - std::string alphaMode = "OPAQUE"; - float alphaCutOff; - VkDescriptorSet descriptorSet; -}; - -struct Image { - vks::Texture2D texture; -}; - -struct Texture { - int32_t imageIndex; -}; - -// Layout for the interleaved vertex attributes -struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv; - glm::vec4 tangent; -}; - -struct Primitive { - uint32_t firstIndex; - uint32_t indexCount; - int32_t materialIndex; -}; -struct Mesh { - std::vector primitives; -}; -struct Node; -struct Node { - Node* parent; - std::vector children; - Mesh mesh; - glm::mat4 matrix; -}; - -std::vector nodes; - -class VulkanExample : public VulkanExampleBase -{ -public: - enum VertexAttributeSettings { interleaved, separate }; - VertexAttributeSettings vertexAttributeSettings = separate; - - // Used to store indices and vertices from glTF to be uploaded to the GPU - std::vector indexBuffer; - std::vector vertexBuffer; - struct VertexAttributes { - std::vector uv; - std::vector pos, normal; - std::vector tangent; - } vertexAttributeBuffers; - - // Buffers for the separate vertex attributes - struct SeparateVertexBuffers { - vks::Buffer pos, normal, uv, tangent; - } separateVertexBuffers; - - // Single vertex buffer for all primitives - vks::Buffer interleavedVertexBuffer; - - // Index buffer for all primitives of the scene - vks::Buffer indices; - - 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; - - struct Pipelines { - VkPipeline vertexAttributesInterleaved; - VkPipeline vertexAttributesSeparate; - } pipelines; - VkPipelineLayout pipelineLayout; - - struct DescriptorSetLayouts { - VkDescriptorSetLayout matrices; - VkDescriptorSetLayout textures; - } descriptorSetLayouts; - VkDescriptorSet descriptorSet; - - struct Scene { - std::vector images; - std::vector textures; - std::vector materials; - } scene; - - VulkanExample(); - ~VulkanExample(); - virtual void getEnabledFeatures(); - void buildCommandBuffers(); - void uploadVertexData(); - void loadglTFFile(std::string filename); - void loadAssets(); - void setupDescriptors(); - void preparePipelines(); - void prepareUniformBuffers(); - void updateUniformBuffers(); - void prepare(); - void loadSceneNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, Node* parent); - void drawSceneNode(VkCommandBuffer commandBuffer, Node node); - virtual void render(); - virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay); -}; diff --git a/examples/viewportarray/viewportarray.cpp b/examples/viewportarray/viewportarray.cpp deleted file mode 100644 index 7c022ed1..00000000 --- a/examples/viewportarray/viewportarray.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/* -* Vulkan Example - Viewport array with single pass rendering using geometry shaders -* -* Copyright (C) 2017-2023 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - vkglTF::Model scene; - - struct UniformDataGS { - glm::mat4 projection[2]; - glm::mat4 modelview[2]; - glm::vec4 lightPos = glm::vec4(-2.5f, -3.5f, 0.0f, 1.0f); - } uniformDataGS; - vks::Buffer uniformBufferGS; - - VkPipeline pipeline{ VK_NULL_HANDLE }; - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - // 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() - { - 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() - { - if (device) { - 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); - - // We render to two viewports simultaneously, so we need to viewports and two scissor rectangles - // 0 = right, 1 = left - VkViewport viewports[2] = { - { (float)width / 2.0f, 0, (float)width / 2.0f, (float)height, 0.0, 1.0f }, - { 0, 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, width / 2, 0), - vks::initializers::rect2D(width/2, height, 0, 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 setupDescriptors() - { - // Pool - std::vector poolSizes = { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - }; - VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 1); - VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Layout - std::vector 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)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferGS.descriptor), // Binding 0 :Geometry shader ubo - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, 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_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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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(UniformDataGS))); - // Map persistent - VK_CHECK_RESULT(uniformBufferGS.map()); - } - - 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)); - - uniformDataGS.projection[0] = glm::frustum(left, right, bottom, top, zNear, zFar); - uniformDataGS.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)); - - uniformDataGS.projection[1] = glm::frustum(left, right, bottom, top, zNear, zFar); - uniformDataGS.modelview[1] = rotM * transM; - - memcpy(uniformBufferGS.mapped, &uniformDataGS, sizeof(uniformDataGS)); - } - - void prepare() - { - VulkanExampleBase::prepare(); - loadAssets(); - prepareUniformBuffers(); - setupDescriptors(); - preparePipelines(); - 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; - updateUniformBuffers(); - draw(); - } - - 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/examples/vulkanscene/vulkanscene.cpp b/examples/vulkanscene/vulkanscene.cpp deleted file mode 100644 index a900f3c0..00000000 --- a/examples/vulkanscene/vulkanscene.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* -* Vulkan Demo Scene -* -* Don't take this a an example, it's more of a playground -* -* Copyright (C) 2016-2025 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" - -class VulkanExample : public VulkanExampleBase -{ -public: - struct DemoModel { - vkglTF::Model* glTF; - VkPipeline *pipeline; - }; - std::vector demoModels; - vks::TextureCubeMap skybox; - - struct UniformData { - glm::mat4 projection; - glm::mat4 model; - glm::mat4 normal; - glm::mat4 view; - glm::vec4 lightPos; - } uniformData; - vks::Buffer uniformBuffer; - - struct { - VkPipeline logos{ VK_NULL_HANDLE }; - VkPipeline models{ VK_NULL_HANDLE }; - VkPipeline skybox{ VK_NULL_HANDLE }; - } pipelines; - - VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; - VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; - VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; - - glm::vec4 lightPos = glm::vec4(1.0f, 4.0f, 0.0f, 0.0f); - - VulkanExample() : VulkanExampleBase() - { - title = "Vulkan Demo Scene"; - 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() - { - if (device) { - 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; - } - uniformBuffer.destroy(); - skybox.destroy(); - } - } - - void loadAssets() - { - // Models - std::vector modelFiles = { "cube.gltf", "vulkanscenelogos.gltf", "vulkanscenebackground.gltf", "vulkanscenemodels.gltf" }; - std::vector modelPipelines = { &pipelines.skybox, &pipelines.logos, &pipelines.models, &pipelines.models }; - 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 - skybox.loadFromFile(getAssetPath() + "textures/cubemap_vulkan.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); - } - - void setupDescriptors() - { - // Pool - std::vector 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)); - - // Layout - std::vector 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); - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - // Set - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector 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, &skybox.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - // Layout - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - - // Pipelines - 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 dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - std::array 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(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 skybox - 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,&uniformBuffer, sizeof(uniformData)); - VK_CHECK_RESULT(uniformBuffer.map()); - } - - void updateUniformBuffers() - { - uniformData.projection = camera.matrices.perspective; - uniformData.view = camera.matrices.view; - uniformData.model = glm::mat4(1.0f); - uniformData.normal = glm::inverseTranspose(uniformData.view * uniformData.model); - uniformData.lightPos = lightPos; - memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData)); - } - - 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, nullptr); - - 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 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; - updateUniformBuffers(); - draw(); - } - -}; - -VULKAN_EXAMPLE_MAIN() \ No newline at end of file diff --git a/external/ktx/other_include/other_include/KHR/khrplatform.h b/external/ktx/other_include/other_include/KHR/khrplatform.h new file mode 100644 index 00000000..c9e6f17d --- /dev/null +++ b/external/ktx/other_include/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 + * 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 + */ +#include +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 + */ +#include +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 +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/stb/stb_font_consolas_24_latin1.inl b/external/stb/stb_font_consolas_24_latin1.inl deleted file mode 100644 index 12eedd2f..00000000 --- a/external/stb/stb_font_consolas_24_latin1.inl +++ /dev/null @@ -1,734 +0,0 @@ -// 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/images/androidlogo.png b/images/androidlogo.png deleted file mode 100644 index 40bf934bb5867e7d1ffa8e8cc705c4b7f1ba5c25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8288 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 LZIvn|>+t^oPe^CY diff --git a/images/applelogo.png b/images/applelogo.png deleted file mode 100644 index 31f83b0458f2b0d1e82ad95bb878bc4347a9f41c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16068 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 diff --git a/images/linuxlogo.png b/images/linuxlogo.png deleted file mode 100644 index bdc5b1d73e1f860c669819fba0a4c949c93e61b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11913 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 z 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 diff --git a/images/vulkanlogoscene.png b/images/vulkanlogoscene.png deleted file mode 100644 index c44bba44192ba32abb66b0f631edb5338cede528..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168127 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

#4#z(1-QvSg9^=2!ZtWuJP74#}g<^TA zq}z25FZ`{ag*|NIa$!XF(K<1Y;j+z5#TL-aM`d7`18nD8Zg-5IxKO+nb+zyd zNj6#vp1H_a{ZBdH)kF3p9&fr%3ZE|32FbJs5w8~XF24Bz?~cz`v!RiJfwkFLVM{Jh zGm2i^UCb33f+@`2#336|l_YZ%^sIO!1qMs0vnb!-@Zmrge;0--P&pW|c%B<%Gg5cS z$mV&*OcQjag78v?Z7gb#ft9*H3;Q@Yl)%o~Hf1f>Ku$tXUs8PnA>g zmF4ZumMvYz6{|@io7l6ko97TzX^kevitjk!M?WW>uYt0e2gvj!!_AS4w z{Th13O(+pjmFxSmp&?0pT&a~+N#Br(JCc!vlU8bafs$3A69M{R@!%2xHj<$0U0rD5MZo*>d3Xq`}GzGy4 z3Jc#I>`MT|02W`KUs_yNAlKvl%g2qUMRyQ%P^Qn+^)w-c)Nnt}+Z_*#J!;I%ho$;1 zaqN3pW@-nD@>?CCB?UECjE%#>UJ?o0RM?K$tMW)=}~%qxRk~a|NXh1Z*f#Wkg)TY1P;6jyg5xFYX_dCu#g%B`n7(x zfE9wqpNnfH&DM#D%3MgqxLUYW)?^W~gU{%20cQV94P zXTXF+v2cH>_Mgw@QenfFfSmet&gMhayB~rc8{rq<>ol70g`q#JZg{OsbFJ#E&w6X{ zJU>ThILmLzH11=)SY2k-(_ZnLkP@i2>8>(-uHMH9f?7Wv_D2!)lSv%U(6DfnKc~$@ zi)*QnCvUDPw+@KFzIdZH;?(sWMHN?Ot_*$}G12nAEBNy(@ArzQ ztjShdax&4<62RJ`r65tvy$(Syckq)-F4bW_zPtobWW7Gm?h6#TX%Ix7*z(7m{g8ySF0KN;zXT9Cmy?@sWpo zoc-P=ocRcdeaH$sablBP!ARe+O6DAQmK4IE$=zg&n}6B_Q*lkYyRrM-2@{<$$f2Lh z6ObZmbL)RH+6qA2rLJVnq7C{11NAGRKux6F?2S` zzp?*(M}Va-S3D~wdSU!r9bL&6QxJLo^QhL-?7s1tNIJ)ypKt z%5JYQmS#-o4VY%{J4~QWNSagHBgRyob=-fDzZiS=>w?m7_NE=ZQmQES`c|$%p>HjH zW6$n%0`E6oRuZGUn}tKpVT8HnQm4h7{cJvhpl!PBmo~vrQ`N8$B$}3T$8A;^D_G(P zw`_?RdN>aP`w}2`pdDZxP3Av`B@%l`!}P~6U6D~~WMG2AeV-n&%EwK6TD=y51Bpt4 zlb^_n!kOjwI!B(!fbT`|!-ObChV6Z~{q^yemNtL=25YC4rk%^nmHr_%lEN>=zE<{f zvR+LJdv-$5LXxO9^Y6tl1SEVd^bq?vlFBS%t8P*G^zg11`3mn7mrIfWi<_8(o?h0b z&zH_t!M(g}NsO1@UC-Ow;b7w8eCGo2Txx;u);?eE*NoSl#a-2xR15r5BO@>ATv3wL zTA*#w)nBdb=%C!&1MRYu2fWhzG;@B!6mqGzYjA7cjLc)@LawYl(jn%V)PIP;76 zn70I5I(K;_p|yVAYlIm6`0zkfy4AOy>Tq%4JbJWu!CZli26yf1L4;~bCl=Q8?gTOw zTF_&S8PVf5zDTpgZ;C=tS68n?-N%!Wd}5B${=u5z2}>)7c~$|%gF}sdKt)Y)a$X8l zjS_gguY&A%Tju%wx%_JJmzA|NEe-qo@EO|s)r>R&8d37oq)+HBY?Bhhx743Rf&+-nYVIUa3hfS4=@+81M!gUQSjAOBba#g znk5jH4`=o&9M?9B0QeeU0KuR>3AnlsSF^xHpNmVxKWANyfnBq22)y6402S_1C+7Fx z>Rane2YwIH7MU01q?;!LnkNP8x6DUD2^Nc@x-j=cf_ zuXxP@?|0Rg%3m#FEvfTjg?A51!F>Mod&n@ehFT8LJ!yHx z^Jc%rNkJDTi@qcx7U1M}f1@!S)a0;qI8q=RD`3n{AvXLltA^q5aE^M6()S-fqZQ6o zLk*}9`MCe{&U^{zT<(%AZz%2X)z#6lu(Z@NND*EJ(t;N+^(F(}D_*0Vx_Eo~qx(bH`d`G3fC?&)+0kVoCpWt9NX78ZTAqRI&d z+Qr4#hm}S>&}Iwq)&u3Lw89_|?z;na%-H{^4}d1-WwFN_7;BYl;g`UvRKCy17JGy^(vdZ#%iT?M>P=_a{-XJ`CkJ{fjRN5G#`|pU&gQMc{ z-p&K&dybrSdinSFXjt)4mXp8sS0u4}^RicyfX58HPLSUQ*?ri~gD`1nwy$4L3J1xa zv$lydDc(&KmwCoT_c9cJ4Qs?Z;ZMjm&rkugOd%{=Q6E95bs=7eHFnaXC?Wasu?gF|S|mGcdkNh&N15{wl#_B|nx17SXu+P6rO+s>@QOgL!_1d-c(XtuAD!qJfLsw_Cv1riS^H~!am@ePs)$EBg zaLvelXSVx{G$Az(T?F<{I5nlXAcKXO$yV-#>mP*ah0pf6urt#k?50`=a=rDAPj$%F z&}Wc|Zp50Bn{_ia)s#A{Y;o7n@0VUjECMB0z&jSBcX**LD(3(9*Iy33rY73~lucmd zm~Ok?-QKP!DFJnom6w-|mnZdQ>#MNya~jEflpQQ4btqL{-UcsQ6*Db_NvRPi{J3QL z$iR_$NkaqBF7TEE%Rc~23*Ty~UR*C(IR<#gC&4@3plOtjq6~rLMd})&iU{(!YHKeR zj;pMSQte){#(Vr4U0(?EcxpY+Zhx9qJo!c-9n2WIjNw^Trnd8Vy(oNO@37{h;o+hB zV)P`9141Uc+_1Fr?O#wlWde=reb0IGhGmy<{EGfc(%;`*Pd@#Tjl#lq_4QiCWs`9U zdFhF@&xE}-TrUVZkQJRN2aEWFPXpUN`1pR+DS<#PI&Z#ue#C}1JZH1>H>KMakw%a8 zb8^1jMT5J9Iuu`)Kg-ek!3067^ZP6m>PGR*g00`aAHAOy_)?&>X`&hULDh(i>e^E% zG|)V>UtDL1W6ao}dj$A0f-Ae9=Od9e23cebzH>fw)f&96qkr9=LXDWGb@mFr7oe;> zK#Wq_&+cr zci%5roxp(&RDq~vuogy&jL_y>p#LU>_jb)JVdQeZuBW*kd0jSTr!Uwg2{<%U0Drf^7jE*YvQE<4)KN2Ic z_ouBZy3Hth?BBoVdhLy-3Z z{vdhD2r)HDd20?!P1JFkioFO@V>c5ILW#o>QoyHLNIBn*v4>B^Q`+w^yj(=j^+au#8?FrPC>TC*OuYA0b9Hh?kz3^)Hkb> zP6BzAJZyGqVQNCP4nrtWZGz9a;aPN6C)}hGcn0uC4)989+oq#5Dq`p_5;VRs{1{9m zaMUYo2DTR+X&gU@h)*4UoQ}j(<{i!KH6DLScPWZmQH6^`D5m6KM_@AOiLj9Bi=h7I zar`;vz3YBy8Rzi0zNUI@d*L?dTGpw!0>#g7N7+Va*_3ww-H=&rzPf7ONF_Wu`eC$W z{IuHI(zBMD)eb&d8SO>(7~~!z!Ss9#xp)2GUn%9eRbN!N-m#1SXBDH{bosx?#^d+z z^E$6y6F&(!n&;g82PdWT_$BidwcQQk@fI0b9FO!iu_sXO1Y0CO%JV%}Ar5tTOS~0+ zE3jB15Y);p^(q81$?=E2_YJ01H}CH6%zmY=vK_1F)rEazBBL}cA-8SPx41cg$yw_d z@^s3x5rI9UC19&cStJYCGFUG|0`)m7jo7r8BqFtuk#q9KEP^C0|?!V5>g()k)-`=*=WxpgKNG~p}86DQn zOQ{!@d}QEPKdq$LyuC)nBM1CQ+w zxjr(0(;ICbdjVe_PA{Gp5ZhYJhmej#@~r9-Kgzb1DQsbBgD<@ceCa0qizOx;B?T9y zksQ_X*+En^1<|qgmc|F+)`UBPGpcxutR##;!wIJAa!}T zoypFwh>8LLwEq_!{<+R_vV@@AeC4~4seejDyw1y$_;om#iuAmPgY(pl+RIqy#pvzS z6o3KPBqfEyWxWaI9==6ID1`Q=1LAv_4g&6gI==)|@3__VhONN<&sW8sDkky`^=Ms?1Z9f2;v*8U8mk(U>PyC*;Vsr{8e9nhqf$9D4ZoLa0|h$0@bR3k2?IA>%o{E3IcHIk0T9|r?N zckb(6=ISa)I<=oOTVM|lm+B0|)EPWO@0HzyYJ2{+3}M+!uo%-uMR~-0-ZRK+IczqF zz_S|!{b6n%$OTxr-_?{dD9J^|S#4j-^&kFE3=Ak?K%!bRI^O@uVvQUmA-*ZC&COb> z*ExWpp$s6I1Tq3qQ73(Z7~Rcr#EWl9^mt!&XG_M837BlH`-u4!7ADCu74?3(qKsq$ zv1b*B$DRjxla;&(gzZ>iw03Fqy zCFX8xpfL?tDoK`?Eg2c}cXvb1IC0w86Zs}2tF{ET#*lzsB_tLh|EOmIQV&wjKDS#S zg(ao8FM_5Y1&L01n?(P9aU&G!pd)G{ZIey;GgfH~)?kt5LM4*GHhQ z#Mf~@>_LN&U4No1KKR-Fxs;>lvK+}{kzM*32`>%b`j{FlnBoyBpQg=BQ5~}uirXz@ zwyQ+ks}Tp^dVGwaCLP}*V2}@k^4Cf=`rThm`mXPd_V(sjSoF=zyqFvp*uv&PXrkG< zkPB!2-Xc6H2{qu4%z3i1m4uKRZ!cev_79yw}d--@Exd(yn>Ny4({hl_n(1oKx8w9`G*8nS(sel?94mBl(7(0zI?gXH| z><$mNqN9VZ4sai8XG7-(6Vj>u2(^%3;*Ln%GvlW0L2Ukx@82bL>Ip{5&1b~JydrhIeUlBPYIoRZZ>?#V9>2} zt$5j|3;rLJc;-@JE-q+P))2h63P6RFk__S*>^az0R+=~p23_(|P6KhMEt_K_Z(1Z3 zvfn{TiO-^hZvOrbj4Co{UwFn47ZnYsBohW1JgU+?Z*oL=RlF}%QJ7o$80q)7)QT_9 zhfdT2$J8Y22<@%xjbSp3YT4gOK5?1YxLy?3a^<6(ZF9%NuL9A(N6HDAPu~Qg;!X-2 z96In8M7=YnykDADu<7i&0^WT9a7Q6K*$Y#myd9OX#+P5U;iiBHB zE7>(@s*zVF_2?4$0TF6rh!#5ro&Uh5i0flas`KmnH0m9atqNBbNoleD_?X!&iZq{92p30FD7$4_TU2LwLOqygd9 z89fMdeTb?*{V2*rnXO%HkN#|L|F)?)ER;7|bwN!w$W4B224LjTy2*(&8y@7(1Jj?k z7`ymB_{Qv+6eZctEa$^e%S}XyQn4nc#9X(>MFXTkbiCI`0b^CS8|p7aplHZ}qEQ@A zElEW}a$uVt&RX*MB0iCtFEx_FTH9qPHlIgA#OKuu4B0q<^L1FEvfuayA}oH?w1&I; zNZviewBhXRr{gX_aX|@Q72K$$LQEWr;dEqU%fcmqhUgAvd_Ts(Fg%%!2s;Cg5R<sw{z5%XbLTjoG->w6uw z#}=1Me1fgJs3^csUDOZax6^)tU5rGKYCkqRSqZtGWOUg5*p0?G8oxzVgvnV%i&WbG z5Mp_zQlNDHvw0BIYaO5(dY;2I0aV~{^E??=kaGSKa7KgKu<-jg zqB*YBl=^VGk?9y9A-;-_LpH~f6#|kDZS7%GVXm-!TNsFtkl{d!auFZ#sL!=y=|*2u&>UClBVZt-j1h2p<8lB8MN3xLB#nu&f}Bah;L0#_W9!Sd#r}%y`el^`T}AO-6~CqlRnJ z@57zv@6l8t$fmIcR~IAxsfLH06f4 zp%NC^kXa#$G(OP#ShTq-HO? zvrt{V^8Wot&g9vzATBpHCZX%7p7H4K`Y|kAZD30IKm1846A}MaT&{I7_`2VK|0e*!JRQU9)!3za)iH5LN9FjF7E{isIKjA;h4;9{NuB?J6#PB?C4Yt4IvjU;#&NOjFjy0wtl7ZXs0dVOCHg9$*-H+RyP=g{dAnbp0*9R+OCQMtyVWQ~Xz;yURRJo-hnX@};|j!VfR zP{#1^nH{lm*?w0F%4`U0;VE&Y?Hp+(<_{IoEGsK@m0+f$Sn0>lE>4D>p?NPMz6&Rk8@Bt!$xCyb8$w|-yQ2zaC6J{(oqr#ej0`2Iu4;iaQ9 zL_8@hPLO=eA1K_y^72W4&KGMM13x1$F=@)Kt1FLGhravv^3_XTo4$~QE)g>rs@F;| zZze$I@o|GapmnZTBq;FKtz``jeCAjt)i_QIcq_OM;Q(qDA7_yHDJsnq)Oe`~`Wz8% zY$4~sYgw9I`br3=wHuT}lvs-DOGmE=tF7HllMU~Ly#kUvv;44J@T{q+03H5h&e-~L zE084#4nF=Hr-?Oa*PfHx*6#8bCnu8*?M9hH4(=2G%N|1N8_XNXxahSdBjE2cDSpYG zh9|~Fpg{9(Yk^?v$qQ8b0&+b9GT}9Q z!=%)=4qReAya`Gue68h=%A{*?3Yoo8dcOTisf41>Rn;~!lIn!T{cLoXxpE^0G&CYp zg_u=qr5x)Rybd)aQl;#JC^gbRO*Qs5)h0cC)WDE|orH~t1mT(jtD92L`6u?g^6J&; zreA*q92P%lxe!wvg60HW#5>u9g4N?YTwuq1=9vJM+tWV~(#iJb4GF`u@%Z>$e5!aK z*`_4!BWjse^*Ux66(lL&!0rV6idlZXUY|lF`w+^yKuH_VkF>_w{HAEW$u^vCML*rY zOWKQK))C zY%VbVG+$wq7d#fSEc-1~nQ&pjuygUr+u7ML9S%Va`DG{U4Pag6f&}F%AnRU;I{_=b5>@0t)#>hF}8!Kf511xl-dRO>uR|Xiz&T zTDDuv91w)-SiRBlDQvJW0?^z}2k?xNQ0;~NL{3%rvYZsG<@Dv$i*=CEY$8n}zqbUH zirVyZOd-%w2O?OUnDWuto4xG+2n>Z)i6zLGi#g)kr*&p0rB9MiCYPkO&R5LjzTOY# zQ1K6^%&hR957xMD(j1xGB>j@BR`kqtQia%@TyVT6XQTK`xf-gf<8mp+B+rTp!PwVZ=vIN2TYUk@^WQB z=to4H`$;GTqrr0xwnfqdej7wN@=!Q%Hc6sF3oH>#h6Uz@J(vvZ1m5+^5j8-zkbgxI zb`!X5r^-$G4v5Cb&&K&Way+*Taa>4P%-;mv7Q^#aEhCE`u__-~vo^{6vY`HsKOP0LF(pQBXG(}>7)T;UxOnMm|+&+a#Gj2ce)#=l3Ms_Um zf_7g~8H6LD{&@DRdVFLF1lN2q5M7Y&v>~8}_W!lYvoIqAxEMj(hi)UBu)o6~3h}+X z)|YjCbuJ;UM9a&b$n?mT%amj=w$~5wSWTF(;iK&aqCblfweKF<9rQg{z1M zzAuah|3?Bgo9r;eKhOt)Eu_Y}WmB@=KB^1J@8+2kv}Uf><-Xtf>0#j&TU+#c4N~_? z1Y5*)#0uRgrNfhwPqXhQe*q_py#Hg*tDIMWrUq~R4HC0TS!nvM8n9nsOkmgIga(c8 zR73U|Rb^T}I(2^hp4RzW;?JLPkeo&?JUg4t$T0nElshPhFri1Pd|uHjS6Yl2F*^J! z0dhh|HQy> zC?QYUKRzWGkjn0Jix#!(bkpY2Wa>MNPgcu0^OtKBkq0r3yIs z(mztZRaA47wL`mc(Hyo-FoxzN6~}M4u)mANLyB7{MZSque21HP+uhAj zQNi93M?Q4=`Cvk`P4HcHzM}BrXvLc)a;hMD?OchR&KvZb4P!jH7hXFGmffg@kNMIO zx{am;q{oJzgScXEL$#5Y^Rc}{W&3GEpR>}SKgo0$>8aO(AT&Xz_Fw5Cs5+07$KY!} z|9d&PbjcZkQ*W=ORWIm)MKLVl)M8@xVwAZEuO&n(EE~Zs5q7Q6(LNn%zdHI9BhoXi z!L;b5ZM>;Y^#=r6ZemRVkV262vnPJtIZ_#r?+NQed#DbVyfs#nAFz(uEmbkG=Y;ulpacPMpJyJ3Rll(u!<%Tja!p}VwjeN?;Oo-!?a>LxU0bKJ102IyotX-`}!zwszE6Ll**rv#1=oCmx<4P+rvZ7-kzGrVRxYMUk?GFV7*;XPnyTT{B_Uu?LM0S z7zlJls$9(Ch9g!EIrIef_h6V9GAf!j&=EQ})#ylLGh(z!dWG5z?urT%N>aeD*G8-D z&P<|T>N*ql;me8(QsY9ELK6Dw%3y}QiEv(2t(ZQU`bMZ}I?>s^EpvXzXpNfks^<>J zmLE)GVwwaCIso$I%5ou8A6O*j_%9!eh0m>W1Zg6J7!M$LA|3D1`4a{8(S<^WxmaPs zOmq1r*o825D8IS_Yd$oHR-Ix_(iS>6Pzqt z?)Zxn2tHf=Oin%w4Z8mQv?qMp#}06;aopHW@oGX;)nb7 zTQ)SIaTS4lBTLJ|q&0wU`+R>g0+7Kz`yix!?)H#=ArsuOW4>(!OlClLLAvzGR_a>2 z;QoOYGV-)n&~jan1yU_oSb&wR4yr^iZ)ansKMUZF8FSl{emKH!-n`{BefHQdaLWm+ z2;}Of;5sy4ZD}MUOHpOaRFC@Wv`ax-+c**$yh%(kh8E{`pi0kz<{lZdXb+B;y|J;K zL`3$Py9ar^a|p`iUk#gxzl%Kl^IX6T{#t~uPHr3z5{9_eh(%gs(YBhVX<+&MMcF0XV*iU=kM^D z8F2PaR?9Y)9F1P_%*+f_LztS#v1i4?n*>-ZjpWCFdv;$wa(4L!4IoZrfYmHGB)eKO zdv{C&&Ibn?=Nz(mU?m}0(QHI*#43JsfrLh#9C<@vr3VWCTZ7>qnZ~aR4nbPZX)d3{ z8I=io`_c;s)t{tlc817Qw`KhPIskFMETxD_T)bgk*4QEOf!zW-2%=W+GjDsRu^!<1 z+v}|7$56aG5x=GAjm6t<&F-&lLV&o#j%#jNDvjS3CxvQbnE)p3z8Px=p9)e5|>8rdN5>DncJEVPtoQDfcHX#H|&7iMcf8gZxLaw1O z-h#G;Get45cHIq{EO}|vdBU^%KJZzN7AghMW^9vPD2P)qqlI~|W){`p{=n;XDU;w8 z$)r?D%lozT9%vNGO{NAxuBr_K`KtGgfyWfcFRA*H_TgF=Kmet1?_c4clWfjR5x=-kb=tFUK$)=q&NTVAu&e8AEm3Q0d=9w6H)K` z&=!2txaqbr)^3jAY!(~>LOQ>`w4R_CtU%~#|D8)lz$+m3(0~2IiH$u~t48c$GQe=a zs6GhuV@tG9h*;~JGBY41Ifa#J^nKzc+#~-`I;TwjC=2>EdiWLb1eqDK6`vm%+rP!I;|z1QtCYVSY&VS_z1R{`>3*2S707R_P^2&!&FDughCk$P#pwoN6N5DMVZ zcm?nvf&xO55JnZ6=#ZC{GP3EG<`lLYx$ZB0?^&|!E1wf+WuaqBqT-kP&CexaGxfIVPyiFwaGDfqcevA`<$SokGrpI_z5DMo$hw zwGPSsE2ZOeHbSl4hn7Hng^I9nz602yC&v--21ytN1iR)I+TM z0@Qb8B-TUgWA!!4@LpY+b?B$a$QrS8rhn;h1st#B;$m?0UJ-Hc4HKagqtL>Bjy@;Z zq=hbPR`Q__g=csHr=S~$|LOztsONlcq`=#`GlJznele3Dda!W+Wb>qA7ciBFfvKDf z&+69rs$ciThy8}Pb0GcP@MpX;@oCiTtk&MHJsXF(wnh(?_*z$y}(->j0pV?>pKtb*+887 z^QT&qErXF~j_<$galzGY|J%P!E4wRBR$UzaPyec$Ll~Vm*Zq2t1gLPpE}NCZLB^W- zyFGyB7t-|7Zo^`6Bl(B>Kt3iow-EZ{3ZU_aBco2LzOtNToLl%hUc_#6o|lZuxA<#> zhPL799CZaH5P4ljvz!-K`WfE=wEgKo@8KXf#FbZ6o!R!7{j?WR4K`Uxnpw3djrRos7%GL=K3|&fC(3c5Y*0i zj<-+VN#eHz~xiC8vD+@=pBqxAJS|h^K`P+p}5tQ$A?7i7T7QutKm<^szRvxFw60 zz(Hj;F+oucY}{-nl;vUlSaL!Q9W6OIaB@*m78`E>8S3rr{H#~+X|Q=pVS+DqzHGyATA#xB8W`^4}{9SO$l2VfO0vX5g zQ!PT=8rRAF{UyrEReDY4<1RaK01V8%*>Ynjkip}|Odd$h9o+feXO4M+Tf$gM^(~=e zKVI>CnO+ZB`sGN$jS@cHY4e*hy28F;l(5UcF2AV=`2<;PKG?9Zk)WYB1O+1qeY=(M zt`7GfTZeeiJeWcAQKAztAg=%Jt7ed8dU-lacEClQfX2lhT=~b#`vmWPPfvd0+~DImK|)GnLoU9?cG8#>g_GAmZ-#a^vR?UwOwXmX_10wkNyqB!F=hGv%=z&Fu>ELX2W5SwruJ7*Kus<|7LWSQ$}JC*$MOP;nk@hjH(^g95@Mii+jPnZKBA zwhY>Td&Y`Eg-hjcWY6xcZ@xR;FXv@r$#`<>t+mH?qz+$35c_;h4nE&R$j(Ki;z0C7 z>^=k7rpWU7;8RoQrKOef(MqJw)C$uQ@#At$J*QuW@DG0aKD)C`+-TqWoYTolhb)UW zD6z?jx?L1iv*VsMt1ieZN)721oL$H*^1F(DSFZa$4uud%$rT*3-bUv?{H2)>nL>G# z+reJ$MzQ{fw_7SNH!fuz8=W~4rxZwj^|7MjKi;ER4n_0YV7M-ocPOkB3*MixQ#V8! zuno-Qy_IqV8L9Q! zj_s-pv7fTJ0J9f?r}V5$moY3f_FurC2}H4YdYeVvp6GWoFW4+C#}MnWT(Mefcev{N ztO4@V(sD2Z|IM)UO4Ki|BH8qp&s(4V1bndN6EqC6cyZ#ZrzTl7eR86r=IK7aGA}M} zXsGbEZ=CqqS)pm6;j&$3;QNn>C~c5?{Tfw1ye2OEr(O>dLR->c#=wkJ*6RsM_vsf> zoj$PW91%^JQ^Cy3G%Og5Xl~O#YuVmHAghOX-)*2G_a_Gi4rf&4)QYA>q2L5OQkq;? z7g*my8s3Fq>FP8LlF6=RpEnZ-+Uir#(ZZzTQeCHEBYAXf-l89O(>SjqokF0`!Z*fj zDe>SU3<4g~x|zBWF5{#`i`t#K1004v%I>8Mofpyf#h&r=hRv_O%V&sc!JNfzH&f4? zMTRD=nfixbDuq09Q>qrgve{H*jq0=91jWTsbv}nQ@`=$PTx7uU3W)sy>oPGhfAHOD zr&cbbX1!pp?`ww!yMvBLpQ+G1&$H`aWK`ck6ayjM!$UoMKY#YA2Q@l$-T!Y1F^31A zeM^a9Cy8T=3D|v{%+1ky{GD4|^yAPix1q))sl4uK!3+!$4KC@%Mi-+HE#1ZlrWEqh zkqf=-gKZ)sn1pgK0y~@U-!;!8uEXLPl9n-(TEwA5k$2>D%=}K zPEt}<~su2siesbhWO(_X1^m8Qdsw~RZ7yG-h9 zGKG=|xydi&hFf8I$_{S4T`hOP8wPY!N^I0}+9Dm)B z4~aT`(+Lwi?RdkWKk)BH^mm(`qrE+9mAjPoViO9C5O=ey3V(B_^X6^%!S@f}4`~A`J!*j!4X;uq!4ouK8($t{niTB*l%quQZyzZgbe=N(Z)E)?U zEFulYdblk@MEi4wPUrjgGlu1vraa`$EzywOC0ClI#WgzG<~l>6l(6qgXZ!n~Ge1{F z^XvWpJzTjF&E!Aa-CY0B%>7&Cxe^cRP6^)N@CB+4Y)-uaj}*nN^ zAB!s_W+Ye;`q?(?FX`>l<;t%=Y$4C0XwRKAF;kxQUYph@9eP9CFRloHfAWZRpn9#X zB5-PZpmJtwx=7K;es-UlX(JME{q|B=_Sx9vq!dh9_VM*H0*|G2B3_k?!pc`3L92(j zfjBrxkiOsbd_Iu=gvRC`rfNJlRh&rSt6On#4G9UsEG)7}a6GEST~kTx`b^k&A8MI? zF4*bks{n{*k&zi66Kh#T^}eH%3cnUtgXr0>RSxC-u=`>*@aub4nI!2<*HgNdkvM}` zM_w5d)0TZUK~uNkdw+eBQjFutF1O5|{qF-26iL35Z61M?sVxM~FL)5h&aXdU|A-+} zSOme`V^VTh7IJFo@gC#oM1+mr+UQGTU#4 zAo>Tkj_aA17!uzebrU=iQJY#%qX?pfmGIHHp{U*|a@Ya)%F~0GoE=_wdqouS^Dy#W zL$=07YL8}XZU#on!0$~@%XWgqM5?Mv>WL7P&-xYjdpHtGTY-2oMDzKB)>(0J1>e4f zbaYk~z+U7gCn-iSQyfKR21~D46Yygdw{-aabe#S+Al#`&NZ=0_Y-pCIsyh@{xt%BQIuTFVQ#u@xcsqu!SDh6w_ z_sD2M8#Xx#q{S_+6Oqj;ihZ_@cYmYF`JW>O@g^=VYF{2k#T+*6X1_2srK8*qNjDIf zvTR?N{8I6)U1WKRqoj!);=gCe;p#Z0ptYKuFnN}rzl4tda&~6!HIms7>zCLnC}$eG zMkvHL(Q@+LvElFw{Tvb1n8-k){D`tqROIn&ml%OBiQ@XNO4ZSvH+OHNuqwbWVZ`nI zHVac&_$wEo(y$1%gvbmsGIZ~=J^<|gvhJ30l(8MS6$R2%v3EA5_x>Vd3pl(k!V>== z;?(|M$DJE4ifHF7w#wM5tNX6>Ovw;Y(DT1C@}Z!f?KpodCz!^bU9a@?W|v8eDBT%7 z()`Ae2$>d2?N{7O8i*WvzLy>?;QyfU3lix6WH22LA$#`7P)EIdd%}BQ_eETF=#KYv%soH>>;fKDBGtu5!YNHTy2~usCF{0mWkxlJQ|V4|Wz_d=`^;_9CM3 z&ZDSa!d59EoY)ZS-Y$q|2g}?@WBljrT#8AcX)}IOr*)#HN~}N;1baNZ-AQQn@3b{W ztW#H4U4DM%f>CkWZE5lB2^B8XN+!2lH0-DFftb1Is9CkV@Zp3x&)_)}k2pp9#t{9% zplX}FITwWdrXKDD*T1V##D$^4!f&KYejM^8&_R^Qgv!nS%_?mJVOns^sU0ScbLJ0QTllsK{O4oUrA$ zM8_QCLE{_xk|g&w)GWGc(CCH_J`s?WNLs6tGt_P1OLELKhmmsRz#`AgHs6Ff?IAe1C(Cp<`rV6F91tmV1U& zEv1&4;}Uy;c!mg<&a1Ra$Ipyd)5S?jah`|A_ODq(9pg)*xE;d_>HbX(sU;eoQbwb?+A&I`FTkj1FU^MDG znJ&7iGH9T|^6qTH`vr=?kZ!3&A8&nYu2PDFMHK7>_o>w*As^b*3a|ez`or_)lYKYd z7>@I$4Q>DRej++LSYE)Gj(kv9&btwWvcXC-dF9v9;aL_8O1zc`oF#KIadp}KLe)*a zJ#g&C@7)}LJ<3dwT-}^60pR9Q=rdr4?;356yWYe*k5Ez+D7-3r5BD8-#)KMZ0)aOJ znwea4eD7R97kK^+2=5U9u}7y@%g|*d|2-Jo)$lHLFVf{j+A8 zA$%p~x2-kA=LhAQ<>KH_$Kj#Jmp{k2$`~J%8$JNeIUm&2UI&tga(alU*y`hpqm3&C zF*Nz-^;twzLqJH>Qh}gA?zsGXs-ZMOk*1f|Uf`mwPm>~^13pUHY0n8|Y#bcKlw=h( zg)T9(YIxotJmSSu=O~Bw;l3zhq#wMYPk8gdur}G}_L8@Toe6A+JR(nh?q#u1^Kt z&IncVzB(eZsKP^32Ull!XOW@~V)j!UExe6mUK9y=Qg|LnNrV@a=L06Kx_z5&C+bp6 z)g4unn}Ha!rV*oSPy#fq&}L~GuyFAVNKrtz{R0#~VWy~{mN2sHy4>I z`phsqo$o&=G_g91Mj@qNf*B$%D+ zMNaLJD#^a|I+s9hwr^#*!&Uk?=PJ310^SNB)-^-6+emv9gisihuT$@LQDLAWrJX=V zxsA;Oyg~%!1#BCI+_bCN;RNIZh^N2q=j(pIE^2`% zt%N4U-rNX!FuKFIyWbxz>}qenlC1@xgS+}2q*eHZ$}Fl}^*iATqxFX5f|ole*ZTYW z>$?Hk>k$2%k}jO6B@sr=knVKgGfbi9y=b_wC7NcSFrBixM9ky$oceg9J^1dRyt?d5 z`S%_H(?r*vag?x|4sU8vKd}Z?1QlK&;q$> zG)%0C)-VTcv@8b2BO)We&XvietXMGk>Hx(Lo|VfpV55)I`=dZ+Y1og_JX1>{-AYHJ zZ*z6_%c2#J8ko@Zt8?auyW2DQ!ZrQ}jWsafLE^{U zh6JW8XJ=;vbnF*<^+4{Zod9C$*Vn^bjJr!Bb$Cm4bDD@4%OD9D5S_DDe{R+o&FQq=h*4AKIB z=O?Mx$!4JB$&;!NI4mry&E+@MUsKplP5txnv6>xRnC7roshoj{Io*v-=>;ob$yyeQ z_cH=r2sL#e%VFaLt1tV17a6mx{I)ASSc^R)Sg^q|I1~ef;(3;!6mxeRTHe24P6bGy z+FC;v?N-}b6;N*jkuUjO^S=&LOWqY{M?0( zMqh^07M;bDYJecCO~I4^}YNA+uC|y>^L;8tp?ii%R^IeeLY;6xh-B_uUaLj)E|q8 z$;KiQoSnc(rK3c=LTKOk2FJCCi9r)Sl8)oGbA)}+J_a6x2tXnEP=RQs91ZE#a0fac zTb!;iti@GnRyGI2!r)6wkGkRj^iY7u#e6LWY;3dnJPkd*JzsNi{n4+q=;_(-8<3I* z%H-U29RJEj>mgm1@F*t+tWX(>yX$$CBpCu^G_w?T;Ajrq$L%4ltqofKt21`wen`tZ z{@Xg1D#7DArrx%WL6B&Gk5AxXSHFP)?kqqW8V2*C*Cv{2mb${<|7xfsxu2sf6y#LY z{2AGHL}t=Fs&2iYc7~p znZD-*xTH4hcI+|cvAfpG2?=H zwK)SWN8%n`j%w9rlsN=B)wjc{qPF9KCKKljTr>A^Zc|+ecO&F*Yi5dmYKmM~m-ge6 z!1e(7g52k`Ha9c#t;IM@Z<4o!MAKeq_)GqTAnqVx8e}lUw!IH^&8Fc^YJ`wOx~kA+ zG|QRkH_t41cgHmJCRrkpa&z;1b5-m~zM4|X4@i1fiZ;BMGjJ@GspWIfBIWop40w=C zp${Yt4hnK|x&Sk;p+9CDxH}qHLI4{$c5Snu+V-6N^IKO;3>luw&2jNB!ziMHd6b{L zN~|LiFhZ6;V(^%6zV+~Z9Z9azFUtN!B)0aieBq+Avo<~b2W!fvk57~BH;E!B^4!2) zCi&O;zabnUx|N@!69|X=KuA&)fk7eaWL%#EunS6{1CE(~=4%XC^&EaWk526ZQ01d1&@`nq0!!8SZC;3h52(ASq>JPs~+$0pDuLEpo@{^COZ_Co&dLY`$G z1j`kdAq7AkSp|ixkKdeKT!v!TDps!eu>o?n*?I1>4pE8!kP`4yQNb$!UOr07q8@jtRC+rq;#17L2O4SxiGgztt&3JWJ#!E;f%C z@f1UNzC^T(hI$WIpl?|0DSrcW(BpFCWIM+rDqV1|!XkaIJ-XN(oZLYa!88Qj9QgzZ zK^&k#aekmfuE(Cv?JRW)gMKe$mTs{WBxSAVpG-jek=i)OBDSLiP(z{5&n|p;9@Enpsz{a>IF~C2*f~$JYP6XBwh{2($RQ%&7Zda_10QYO z++4=TKRNPDu^tRL2k+Tc&8?822i6g3@iPHv53&eidcCzgpp-Ly<$Zocp4?hhp!8@1n`p~%U(qV{{d zrZhrQqTCS(+Pc6+CFImchEU+;e6 z#smOqhuX(si8)4paV=R)s#1bzSr7D~-Q4zlxx2X!&HwfBhE~z2rpKcX z4@Ib|MRp*D#PMYvgb}f|=JjpQeFuxIJ2%w0ZPuj@cf3U7NDoipg;^onc=P(Oc->~! zM#=a?g*czX{5)OW&ZZbsFc)+n6g(_3$d5xam}Z-hTB9{V9h;XxS`!-yLsZ<1VN|Gr zxSD5;V)bdHz9v*~ud6F=SP5_{X>BW(k#%5a+cRMb7EUz9!VS;V0l90j^H7nIgIWIc zs3~s!K`@hd-UMA2YMgoI&M7)6bQWwKs|pZyQ;0qNE5~e;$+C`bj8(vz?HshUNx&n= zfb1Z^b&`H|l9jcjVH6WHDUPb!i~5rx-|tF&kkS#^tg#J~{NwqKC-t5 z^Y_hpaqmE4wDs{^56LEoquLz{bdWYVspsURG(DYS{iSkne6Oy7v4+y)d;I(oZs=E@ zdnkK(yB=kd=Az<};xP2d>~Qrn?R?@kuW}hWd^W9X40LjBYTR}2g6vb8A*pQlSKoI7 z5#P(bpWylKhs&cH8PkpkYNEnXQMEXIAP9@U&b>}<3bVccjr3n5WPla)<6k|=euvlZmVgz z6q&o2n%=bzS z0Z6!_;h(68peR9`zXui-5=`QwOhZOxa@}bQoyrjq@#NGD3#lSkjyI0 zKW=({)&ba~q=if|lW0X!v8rDN!&_-qo9pWhw=M7j7q6r*D=y%%a$2R?pb%)-S&y@J zFM!W236cZ)-yIYJc`HaLb|SqP6yk?~UWB(a^rzPPdL7`dkQiNSi?G@;s*sB*DXIkq zfu}3&Mq^`eA@z({$1^iSVi0Ohs1*(!xP>Db>8NS^j!#PVVAOn6j~5J~dcOhCX%-8; z*}$AmITr0{FvK%%JKO7~NC+zC?i5eW^`s;~e1$Gtgyu1|hpi(M6&Q?(qba5tQ0rKR zALYVXb_Oh4Vj%BPbfF%jE1uQ2bPeNB*&IHR9ks6}7C& z(KaAN9bO6s#`EC;(x`c*_}TeSAYItm1@ljo;Yy>gFi2;TVp=%=!aq(K3rv%G=Gh`8 zWqukI|2a5F;L!29LFRU&q{hzZJF_WBV0+a~6hLe>E{+SfNhn9gqyR4Lxy(H35W0_F zW}~=|Y$ezJ>xjAk>xh}`mm7ImaM5D-Q1dD(6E zTm0k~0(8SOBKM^38~%U>0n^bismdso;14QhuR&bW=F9 zLq&cNv-}&w&;kK|d{O(ZQ>>GO#CBjHJusA-9kfu?O&bwFUEFLv;rdKrwHIewh+z{M zFMJi2p@8DuiGO0dph_Fla>S&{Tj5Q~rS7+s&W}6Os0_w9MtBj)2&s0i@i?OY7EJFPN=rCwFyW>!Bz_`5d#^{cnK*4lr zu1$>3(Xb@0vyeBuX`VcTM^Wf!pr!-Qu33Or^kLO)+-+Ti*F3j(_B30tOr8W47? zE>#?b0=(jWJw$HIO;C_qY00fmpL?IOH9qvj+=mPEfX2J5o*SYh{^yG|R~mf{kXQ*zf|)aBP?v zT}D$n2=EAAUNZ|ik)I|d$hl=j#%i4kGLR(#gte=7)?=S;4GaudW*l;i%jeUYn@brP zUAVXh=xJ(1xah3x3|Eqpk{y<3l6NAR!T2vg%1qbh(hEqRRCD zxux0tW>}InS0=V=RtvhgA>BC@SsaiPIdkIrJ_tzbPdH#c^pn_Ry+4E!8{5vvMsu!u z*L~P;-bQb5IJj0Bq?X zz}lx#!cH-6R|rbUfmDtbswaX6^Mq~+)nDLJ}kDWn! zsSssV)pk~4(s!m+&nqc}W;uV*3Qc&_CA*KSxOnGHbMxH4f8eXo)*PAaR$7s#gQ&ql ze`Djeh6Kv}hlOLFxUEc;O6@#`?LM?D`F79^j;^Nzsty-zEefFF^!AAVUI&`fhZG!7Fj49 zXpyCz(s2~)Qh%|H87uqslk9&GfC#%P?-&0n6HHtGv7mCD>NMwT^nKO6}@qzq!AY{u&)U>uuqhLRS=igFpS7Qj;f4?C)hFNbE-8^Qk*3nT`(D-pAs6tsLk86^Ym1O2&4eQoPQbH{iQ8&#|z~#3%^vx z1zq~(q{Ao^M|Ef=Nt&r@5H>LPniX6$wyvBk9%QP*Vo5i)xkXt{h)hiWOw9oPQM>z& z42=K&l}w%WN9Q;$xf%o_kQ~piN|)(4esaDVJU9Sici9b{hlK9eed~?>P9UJeNyzr~ z3LhPYlke6Wy}njQN5HsJz*aGIfj-`BF)G+?yGbyUGI_*_tD3M^1KYtCQj+~}h~>0@ zcpY+i4klgnuV=-inkB|Zo08V>_zbGAiWo-^aTm>M{m7ukFv-bN56LX-`2q#$fp&r+=IW#9K+XoeTj!$B3^ZZvj3&7*z&7^wY5c%Ht zwq&DGHAZoGYf$UKwwu!%ShnwMqm(K6R1hQ=8x|*&F32`VNN`BmzZlj1CRl_G2d5Nw z6{i=em)BxBx$dNU<*HYyeb`(U!_8U7FPc${#A5SH@ zY+*-YntBMfur3eO3|Q2=!v+W8|NR4YPS*6R*Q^%vRkK)^5bSFIl!Q$H9;zGzb!a33 z6y&$ZhyhV*^6t~-n|3;Sm6@1a2OFoWbW{N#aV?Cb>HalJaS2_kqyJ)P*yn}vsDO9GLe zX)*?)-i00SNMaqE?d{(1sSr?Fw}$|?UJ(>LOS7E1pBFA<$Hbv-(yD;Ul0!*dEutHq zqFK+yJXVnC*&pvLt86Fp6RwruYMiXhCdk?sd$mo6GB`e-EmJ}xja*Q$DKoF-Z8#Wp z>a3)>rL+{Bfps;)4t`9z++Gd|Jx0uGpB#nf!sNE5z!QYU(hMBK!l8-tKS*+lp|cJ< zNSex(kW!)PIqF|^zKr~@OmeEj_C(m`iYQ&D%*SZKk`AUrBZgpD$HtP95S3=viPI@} zUQR0XMasYXxXKT{^h)zmx&kv0C5sXKThqZjDKJqv{~Dyq_;tErVfgDJ&HEfu`y=^M zJwl)$M8n#KP@Jbz*mMMWSbxvJ#z*Dn%X`70}U@bF4k zSHQPd&C0e0@|`W`r{{Jt^!7f_r6Am;bUZH>5fQ|@ne1C=a?rxn+5goX=X%+KAL>N; z*0;Tuix)r|n}^tFir(5lGwkm;v`?$eoSy2nDcg)H$|$bd$B8>DT(R@DTCn4}$_2iL z+njE)ZCr=*uXJq76ZDH^&G!pjgh$hD%|< zLAD?a>QaNab4V8r?J}DNPnWwM(A+6Y5$`MZHt4YP@CS0xWQmPqis?r~SR-)1kRYx& zu2(H>`@WZF@2=HZ>seV*S0-nr*i^23^Fg4B(}?HpZ4E2Ms-xCM!WtBJ9Tp%+_^sKI9?jtSNClN0$a8mpC{cI6%De@n~9N#MKh5$Z3$My-U9`Q`B8neNo zjHaIbhACl@Rr5TwR4ZWJY@(DTdQ{f#OgDNi&<&ZRt%D?$(Jy7U{pEf$3 zrm4?u8>+@9bk1qtANQT|@}`md0WZuR`$j=zWN`4X)AsDuGN5=XcYA5xio&7KT=ywY zxe|7@QTA|c+N-DhWK*|Ua)6I&zj0JGrTl(yZ~!Q>0OMh80$jT)O=}|!ERvDyE34un z38-dYakyFCjGT@?HIT3#2?wEv2M`Fg$D|uB5gW()i>0!fN>)PBEHg!Jy|B&-`$G3Z zH~u7SHueMNd{^I=#YL}xEgNN~ggNYq`!8>(0FehfNKiS1_3b|?LTH<3sVI>2%xEa| zUAYawH?MAusg_oEcX!{-0*L7ZvuVWmFS-Sg?WR8Wpi+X4B9*&|cGAJS-{n^bnmp!AFt9sdgQOlyR7B1Lgk#RfWO#~RY?o_LBjt_!) zoJRv$?X%I*m1IPEm_(1dhKALdnO}h0cjs~-R`_wJLN+=@>|tXXNJ-Laa>%*4$u2Eb zlaWg;Djdu(+R5pbYxv{t)$zRtjj7Ooax&(_=Yo_(y4ACNdbHCd$z%LM%yCRjia7tU zJj?=0gkC?;T~k<8D;3kxTSWzadb;ihPH8Gh!mt>^Iaw}DGESyIkhENs^B^Xr*N(|* zvjLm)e=R;e0(b*}incw|g+;-r_7v7oq*i+zu!sm)=P`&GxFqG-%!1Bvk&RU~aXw5A zA+G{88eLsVnwoP*3!24c=|qU=B|QgLKi=D%OT=Ds)3z6D9c81zdLLH%1_s+^)pOBS z@X#?9Xs3dwG&B;;f@f!el=B^HDW}Vfr3RgyefHt;J?2aXaSkRXa25V_T6V%!;T~np zROSw{+C&SKqQ=ypgoFlK+Hw?B0xRodo#`FLF(DXIKN$5d?t1}s_gSuMei4ao=|omZ7%KS-`_P5w?sOoMGGJPQTXc#=iX;=k|c!n zPlv#OH=%YWlfg22+QUhK3@R#dc1!t2>+)-!f{>}We_TFy77ry|D0(mFSSDk7a;+6(Zd zGdWU^w>ooM2gnu1Z8WTvZ>1p`h2M1WM#VYM%^7-a!l1hnRx%QPH<(F%tIYAq7rOM` z?jLb>{@B;gr>6FfLs3v~SYx*m>=l32;SbPtcJN&8)(dQ}IWClYf_aNbww=@2MINXS z3Os+8$BB|3Ftjg zhfRLWJ)ZpqYrjm03B5l^wS|Jdy1z7EpEl_jdzR{cJZWE18iSu$&~`Dp_izWHV&o*n zaRB)V5>|Uxt-=Bp_NPnC*Vn1Ld#VcZ2b<>@n8KTzQ|tnSvuQc$f*d@!7%1GEcL!Z= zx7VpGI}Ag|zI7x| zwKzGXOS&Ids)b^#N1`&a{wOvQ!W(@!sY?_H?Dp73+u!P&XtF~lg76U-mWW;^Mhua5 zmdFzt!W`v87%;=F)~}QY;Buj)#RbgX^IKMT?;auD^Wo&gXVq^ExNF zUweL9b?fk1FzQ6?AjZcF=KG}5WK-!^W@w_d^Rk0eB>(oBh>Zms zd=Tc8;UXsE+RGt9B#eZ2+;IN(Ooe^t+I9xQDn!itbWhtAc3xo?Ss_BLq}!)%{V*bCRbS<6 zH{9*UP`1dEojQnIgcXkZA?Lm1qMts8MNy5wA~f@i10|rRXZ8*Q*`M30uyAw&xTXD~ zfOy?<@SM4qvFFod4Tm9P_7#!Z1T-aOou;Oow5*~$EG(LcX~D9WCptP>R!Um)pYNwx z(Zpo!&u7Kpx#?;%?58bvf|z~P%if!fKDUI6iW4)j;v#R;7^nF7qki9Km2&1pD4Z3c zXGS<(X$aiX!=1smmwcJ=@vQy+KD0?=KGgPRNJu(mnVMC1Zop{JCJa&RS0V&P2HHFZ zQO7xl2(|R9p~{xgt-_04WJ_ud^dx#DDqN~j%5V|y5uyz|;8Bd^aJc6Gz30Z$PbcD% zXUpt@e#+yGJ2p4B`eMm4JhrO-35L9anax;7i$*g{8(f#BliT_D2%yN6ojtbkA-%LL z5oTc5&->-zz^2nPtY@lk0;97i29CEx2#hnPbHNk<{&Ab^*+Xs=FjWx!+0$qzsSDH# z&s9Ts`{@(keC4xZxpqX}q2pv$ZnRL=Iq(nP0cub;0(PT6&r4rTS~o*tgUR59UaM2}V{U=LwO863z6@gugCaDD*x_o;qikr z!z&I~y6Ng^s44G`pjMeecu@EJj!yi5pS6kKbqW~E7zzG>WJyd`& z9O!kO!x!kr+|~;X3RY23JUZF#`QTlA(+ZFNN3XJ{r~4N@-MF%1Mq}m$aM#rI zy!%lkTU&H=v}4oRi5ZF6qO6Rzijv)h&CQp!wH5^ha&odUQDKB<5D+pNnPt;`-M=R| zxSN~n5nqldUal?b7vorAr$XdWi-3#M#OuFhmH} zHnmY1<4{H}eRy6Oe4nl9%!c_KGJhr)!7x>~Zjo~4yFF^t+O8= zR6k!oTwV~g=wF$yGyv{k@4WCB!q3WslUM~XmSKY`hx}(Lk=g*DT;g(>%njxdBLdt6 z`=(FUz0Pk*9CT9>>q@_j)a9Y23g!{Q9FhVj#cB_8LwvN;KP|n03PNFS4%+CW{^(!pzKxcg+_c`0w53zivC5hPV9xU9=#8f;*oQx)MCQa%z` zNl95**)4Nume&ub&7Dk6W6Vr{w!9laUyjNIQauoD=_JZ(QYy^>SC=!j#DD6bQ zC^c{&9)c+rI{n9azs^#3O^8(+pFN$_F->!SE7PxOl|lp-i+(sA7I9Ni7l(%4&nR;z z0cjlEG^2jS9bI5vmymRN!-S3C|1_aRz{weO*WCu81leiQ;UW?2;@E9jF;d-)xDS6z zUTwbAjf0Yt*)#oFt*>KbgM`AO3qc3K<-WWnQ%Ss`0Cl@nY^I@kQ*3N(4&3;!U+5^v zha`lHBNNqs=a%|LMrb!^ZfQTuCe+~Y(x&p|#-Zf@3PLX}0CX%7hF|ncDC_@Dh`F^t z?)e{AckpwlJK7ct!fI9c6Czgh-VP0s9M5Xt)N}&Ya=?lhx#s~TUiwc#7ecMVFS^+O z_7V*?+STQgaAaiCCE`R~m0)19D@}Y=ZEb&cOOkPxWdR+#w;2_vu$0d7=kxUW5-UL^ z06lIjd&966UFS+)w=sSYNY;sY-UI>}cLt3W%q)h6O;zphH^3YM3h5C4zvBTMj!{bB zcmS{@LJ&@%Mw*a!bba>Wp#h-z)jRl@TAA1}zDgpZQZKIdSWw-e(|`18JSy#Y`-4&i zWAOg6uaN2ga!2xPO-tJ#;^8T*DtT7!XBJ(`k{vmN+kX7`>)~O>T>Jc#tSUzX>Kt-f z6Nt6t50+zx+@h&9OlWk9e zc&VkiG-X3+DEc_#xxu0z@OUXH&D^D>2^<~Eeq|`ed&c)Rl#od=M;-o|jq%8h2vN|N zE%$ytJ)f_FXEAQM9#6Nmo%!_X7ueSP6Lu1}FcVnW?&Khi@YBGZYd2iyG48EQB%$NV#Uo>oxZdPST^N@!I;tTO48cKThnmzO~IDQZbEmciSTWG22c z!^`#^Xgq9@Nd4xrF-gk%(MJP(MVutzCosr7+v{BcxIVwuc3Z8g85K?(KnxYM#%MFy zIZ6ui|85!puX3gp_}DPq<<}MOpX}_Y$jFq{z0K*MWT&w8;hMj3Z~|rHkdt%VVO2{W z&_vD+JZ+)gqPtyBh9ijqrq%Ad{U~Z%n;bjyWeEs;Bv$1=tIubHf&kBdmzin&FIH?r zFnBK+n$JL9qx`8324+(*(}~+9}8!j>4`X z-Nuy$_sB?q`-cdNKy!7D35KI%O#Ih5^$0t@6(%3z>)~CFQr&5PAD_UNm;QgJ+|<+q z2=HC_QVmUw=DJ>AW${x3cX#z)f>To-Gk7> z1N(-kXs3RSaBxug$BK%43)LNsg_bH#m%v;AFF1Gv1tn!?|GFjr`|0Q4;1k17<@jC# z3H5mnMlpJ6KZTg7Ev!7B3U_vZfgY-hb9O}Rd1hL3r9@yuaInj#PgM~Se^@vmKHBr$ zcc=V!XKkfEKk!EVxV0+?VF$i~W%s72ZsamxhkHIaM0o8~X^6Q_PpV-r+pt9ZP%zGp zAuJ|lmT>Kzq>zFu2)%_A^!Kqj%nG3S!`e81{kow2!;|1~p+k%P{lUrIQYq^rY(M~C z7M49>30862E1c(~7aST%22fu@fcg@r$t9<)jo**X%_+>|>Z89@_wA^p){oq$bNGaX zwGYEgv!?LX+Z)I|D*Nyff(C901C&4mX*o3}1Cf(8c`#*U`dEVYcmEj$Vd-<)SAk)SF*je6Kwc*wlko zn}yk6@I+Oa3DVg_Xqee1;OxUDqUCfUfZd~}6G32AgW1TD@W9Q|ydm>Bh_Xucg>+6X zJ^|4mL`@=4*Uv{Hh^r$ZDk$#!O;Lf}t$%Lh-zdVI$m1ifeo9huJOif%lZ}eNHum4P z(2kB-qz3U#&5cio*v$Dmk8lJGzKU<)$~!=kPc3TsArWns|{I*w;k@)Nlb_z=i zt%C2O%Qch-olT@Ijm_>BtAEbbGHzy;8o)nVq(U>j?p33si{Nr6?tFYKwzo|jR`q#z za(tZG4EVYnLPH+$nI$5j_=hUt9-D<6=A(#hegP?(4VHl-cA3~*uI-Zm{b4mWJPbdx znN8$b3y{dFQX75}U0pyg@`=nXHE-tqMNZ@?EOm|!b_>@j^hu|f+WZ6)me(e8ni8Ny z%n{Ig0lb_3-;s&v^#3wIXU-}2ku;KvYc_J}{#=L9>10vd*B7*Vn?X%#(Nr#&heIdO zVxS04`8S`~yR%qFQW7pDs7~uvTv4e!Ih(w(^l$Zf)Q+XPg@b-Ze)Ooa zjYTQ`qHbKepopM{v@{$Mz8mweXtfivIDfqXnBUR2IfMj=1r>}85H_PD!v?S-uzE(G zQ(ig0l9)xirgP%+%T}!2-@MB&fi@E}M%o)$C+Uoslp>4*()r;Ec(na$gRp{}-k{Vt zQRZdmx4)IzuR3AxD|LMW849n1~46c=9M)n=xU zOrRcO5~6jqX1DGq`h)SV+53g5B%Ry*YfVu9m%O}u_gR=eRiZ6dT2C5El%uRG9&ME| z7lA9X7_OhE*gsD{{e8`=sj)V<@GAVJYCB^!f4eZzcG(Z#wDNmn-r#=SX+Ukc@rNjQ zc2kagwN#^z?R03d?F*2c6G{A0c)fy1s3|oChLq%ZZh0venj`N!pF>4$29{Qx5>5Rw!3(2~)@a@9vLKK9d^jwY8fRj+b;YwG|AJ;nK> zoJfyxe;pXYNZMW$J=oT%aDa`{^Xj}%5=X=f8ncY`Z^c~PMMkz7oP3nT$ztc3!s>w| zlE2;?>vKKu_&(Cd=rCJSda~SvQ4q!L4yeB8Zccn{%f*QU*1t?Nt(RGOZ*;~WpaB5X zPV}`iq~2OuT}uDgJkca@g>i>M{r_hQ(=YH$s2_%&pOMbWRy)O|3#MUGpyYCheq?%@ ztx5l|eFOKuS(9L%%YIp?CLmYq{@@wIJWTE@us>>Bo);3Piz3BTgqBG~N20T|Iv|IZ zOG7)Rs0#EGA2But2cZP;zkC@R8HC~u`A8}P3)A~djQ)YE^UevIYm}_kI&QVS>brfz z$A`Pqm4*1@s-6OqFBvzXegp!hyPgg0Ru-&j{zS=VqI(I~rXL@BZOlLJ>eKL&lIaw*#DajX1Zeq5^?MM=4CiV^~I0sIv@ZbQ4$!x4Imn29cA8s_$ z%nv=2@-3G7ku}`p(gmGv88C;c%*3}osCu4Aj?k1)i{y5=ye^_R09 z1=}~7;1GGZczXj}DKN|wQdC;+GWUMS6Kx7d9|9IwM0o+Xj9FD|k~DPeAIbM?1!ySb zP(&O9fPKuOz~Y|<;wBOAhvH=BSdl9vkkiR#=w{Ty`Cmtu*Qev-S;|uQuKM~8k89Xe z!z=SheD45?2|hcd0Hny0{ld|#AZj_|=A$ynpQpCI@9_1P4PWOl0m<)=hL8!7eBn<5 zNcM0tTUJvp8p26vMv{>Xn6`|#jPIYg{E$NPf=aX}(g-=fd^EAizYY%PzyuBwG?#RZ z{vz7Fq%lE5vqr%$7_jM$*YlG@r+?d1Sw`swRmWRd)ZhTQ(E5-2$LUW`GZqE;^Eq_( z2lo1rz-Cyvc4fYbA~W9qG*VhU{|k@IVpdiRL5K+Xp>rRF1og&{`zHMqJ6;0Xev%Q$ z$suwpPFNWzglD`oI~M`2=N%Q65h0WmgKA=e*5j>RLBv$uL{xaTusuP+`9V%?fl`um z1AQvtu{pmuLs}^{@?X(O=NNUT43dCPNA3X;87@T;SO!Wf49m1o`M?ZwN+C#M{LC4#6(m`H z$$ZgtaPCZ3K->2}`tZz-4It%}zk0MbPEStB$m`#yw15cI5`Gm#D*L|Cf zgc{!Pq|y1kZ+OhUTo&X65nozprL4rJ|U5MY_~tp z1G6XwcE`_Vl%08m!Q?=sVC?b2F@5~Mm7SwxonLe13!`9}T^pyb*Vj3$Y-g2+^uZQ; zAT1<2nd(VS6bW^bQ@q)fZrs%Z_`IW%98)&pK}UFYY1iHoXiLNV@Z1ouKm9VS{}rEl z`(=C_DSlTO)fg9GvnwscgN*(JzQkYeaSQbeDmwYvT*=hd$;tTI+7mP{=Jq9yv^p>~ zYWO-|q1Wd5P3Hgh;{RKas5<>`d)YP?5#SNo$49k#;?-w=sT2eRvMmX=Wc_(p}*x!0qP8{VkOB8 zt?rGt2&)}zW5uuV$m=od1w`^3=7elPn*jvoV+sjq{vb+Fl}tXLf_NaIXN9m}9z9qY z6RSnlAVIon1g`~ue@APDolQdQfT!rmG^F{c!;nc8zEaS7M^;AUY?l^ZhElVe0TlEU zJ2{yZP2wx+l<&!c%8p(-^V0DsEAvJHI)ENgciE(e8s6+#CgC%T%WBKdH8pa&O!2(^ z&I2w?`Rl%mC%<(ON4e0~TfxQ_!Vx2K**j9HPu9GX@H+PMoRQh9&FC-f<70V51Ye_V z0e&sKp-tr;D6Uq-OG~@sb^dWP^c?W!%&DB2IlsG-JO;L(Vo_0#^K9wdzTXOL`Q>q){IfIoDXKElt7&Wx}v=g3p-16qhm}xyKyn%=7X2!9ebC6rsO; zx0-Tv^jzv}5EBR5v|QJ(8rBlPrH~L+c;#0@f?#PW37B*dA`y+xs&BVgWA^%Vdputm z2!Nr3(f^;*`R^Rb87l*O8IJrPsmzmGqi#B(@fLR3?#@oPPotf>=%X8#7sPb{`DJRk zlUp%Q)_H4>8nKo?@qeY~Jf^GL&g^RNlUw<>!k#Hs3aGlduI0Zb5D13vyMvFz0BB}N78^Kpch&CP zvh~p#7V!=ZsZ-gafEL+|Fs`eOFwX2Q%<`ufZg&Y1Q2;@=Y`4e`qTSYaLkieZ>$-1^ zx)+!bLJ=Ll!I0djq&%OM!!T6qgYicg9Qkg;LK?+s_Z>iiG8i7=xoDoZ7|i0>{gaNy zuF`~jp}o4|qz|9f$N0B&wD1R!vH+BNEaG^Ub6H1e8>tHE&sPKFWd$%>IRbf49M$cA ziX2v_rlzET-M8S-f(!11L>)l>Q?7$?<-Rmv?eqOE<>{6m=+}T6C3CvuQdf7s;vA_D z3mA%*M+&?>ucP>(e|Rw)PO?7!KSZ5nSXNQHt?4f5?#`F)7Nolbr1O>T?vU>8k`AR2 zK^mk>y1Tn;FTVZlbN+C_KQGr>b3QS~J?8S#Qn)8J@N=@;+w=2%jmRtJS4_m2Cg4wJ zMhGyV1;X4ehP1Isz;T%csa)(Xn)%tbGwJsHD3{WAfAn9l+1Y{i9?i@M0<}xO#WKub z22j#uB9)2fq5DsakK3!cG(_4~eJ=6>&0|NzCW2V6i9on63Z!SkRLD zmgQRdDn;hQsad@Cs}0uE{V_zwXpm@BgT|ou8p#6q{k`&rByI$Xyv$s{1|p^q8>Jo< zvKeRSt>_xu658s_F2JYTo}jC0I)dM*g(J+9M!=ZzH=F+pGcHO)ERJSW1`lYBPpL}O z4m~&&`4|s>zH2y&IMTi!yQx_RBKEbb%Qraa5eD{x#c(72haGWPU=LKHB?_4xzTh`x zGBSb#{U6jRj?(DitI5Qa~ zl)*o52?Uq2HG?RBdcQb)di_{)0r`pUq|Geg$Z2dE$wtL@Gn&oEF#}1? z^WXNQmIk&*p#VL>UjlfA2e#4SB^|Y&zr}+|P>Dp=S}tBKMrWM$yl=}G^w;l?YMGAn z>mfY{zx9{qF>>dB7RNk?!QIMx(-6kunGd#efQUKDai2jq^cn)BQ|V=829S_YK|Zl4 z=ww7B!VDAzQJru3=jX7kt-wZhE-ks;X@Eqq@$Sh;P-9Ej=_3u#U{8VMp^bfe0VcuH zZ@oT~G?p!AU*nCDnVC{w-_CSVGAUb&`UsNzAI|)z*e?l*;uG`{fdxiZB9Q}K$DJDL z9j+L_G>+h{UZ)Lc zkdB85b5%53ylghR_q>I&c4cEyGjI0f`OMnq)96Xgb?%&rkYs4>IjNi1+Q zwFvv{K!I#x0_W+8dVPIdIT^;REq4JWyd6^Mtn{)EE}`Y5XCt;IC01A{q)TiWRTo)| zG{tIN9FHrM7A=WW`oeSwOD`#5K~2LlJhZUzb0=4xZN+`T9KNPyv!t@-vBuu8acZSd z>ThkWL3J*=;V&)U%>!OrCyCzzCu zlEr5H62JO4%)z1YC-`-NL6WfZC5)!TAs;@63QW7qCW2WTk<(pjQU`LF+M)|LIT&dO zR5XLu8-X+25aP>4jSbPbPow(vaIX*%x2DxxbGSNLFXR@TY4Rq@h5T)@myZzAfcZNp zWTW6{(PmyBjmk7XYyxrMoK4hB)H_qBIy>-1AA60Hol9fXrQnuHdUz6PKPh)7ogI)t z^5g$f5E-hYZvWQHf|8DrTbAzNjT20;4LM4kx1PWcw#ChlEX)F2)+|CkV4eAp_;`_> zqaYP#wWef>h4s_xH4O?pJWF7yq28Vd?k14Hr__FY*wFt%Da?-sFE#2{AvrTUAr|?g zC=CxRtExUs(%A9=6n=Jgw8gn-mxn^U}qH|>kW z`M)KanE-J*%gA*4xx>vdBIx3u9wTWF3$Z{0A2$wkIP}riM!%Qq=?ZwbPz|yvu~jeR z&KsFgX=RiRqf@-j*kP{eFg_!md+paul6LdgLg;MIoFe2ruvudJrJsqL{BDbF&Ljg znO!$EL_m2w@BrX~okT9bUFCXffLAj8LwV?>FJf)J&22xYP)1cP`F<#nJ{gqJ=$5e< zEAzk;Fie(L^6=oSoNWgaSWz0%(9(8Ke|rsW@(AlFL3EM$tr9)+A&)hZMMc8h9PD!a z2ux&p`$iRH(sR=J==eSD1Gp<3#p$`z$8 zFVS3{B~AS*%-rm$v*@Xldr^1Yq!Xsc>}=D7<8hRpzHo4G#leYZW}bkWEa%i$O3yDb zEk^W^T6q;Arw>L_)nLmGQ7=e+d}C*~wzjr}CaC7_%2yQ!w2~eGC$E%l0z%kGm)5N{ zNt$Sjipr9XZkkFJOUjWGU=_s;|E0QwM?VDwMC!6b%hxU_152|V5u@gsm!-L0hd_Rx z|7q(U44>QSvbDwg8pW%kxUWr7h#vDd@n5iA1(-^%w=POl&%@fP!Cq3;BD5hp!$gh`n5{R*ZzsEZiI zOYcO6QT7#3jY+!l`+&pA4t1ngD#N;$c#XmQht z@OGmzPz3kXX&(bMGEoLwl!8%)d`glyF-}9H&|t#IqYETwD{A9}B+lG?9L|l%^8DTx zGm81A^ZC@RR6O+kM3sETH%Y+tMUe+*7C(Wym4F2ZBm+RmAmes)sowSINXqR=9!5FQ z297Q}l@FuU|41h%9iV4F#T0GOQ-ha#mb^n&L^G<0+i&jmzJzb;fDkjfw@&VPoy!lMFdb$rmee_bX0PXX5l~h}6BkV2w=0uu6ruXar6_g?EshZlcTZkG>FlQc9e^kIbmf=jcy8o~D*GU$vj&QjhCrQ~zJ9cS_sB*xU@Z}#Q__&^ zBlF2eIH(^7=Inl+N7XI1C=)Q@k}=i9{7#XRN&YWi_)uzKseeRGk@ed}`edTpQQ#o0p>=f|6gCEEJ4JL7Z*}#Rrb;W@5V8mDwHP8UvbO_UUKMRG{gh~)0y@{6=q?UR9HmB zvD=ebrp>{rVDzzX-|$WA0TqozavdvjXn^SL9 zSHoE%&}%RTgc3(9UBXWa>GtP&G>@<*1uF? za0G1Dmw;~G5J*bY$e1T;)^`CquIbbdRMHbGU8N=n`#J!Jcm@p(DHG`E7}=DoRb)GF zf8Wg*!TbSoCB}l`^X!*BfK#OX`Z|z;Viu(+T{YNms_JvSfyYKDG(6hzTIbpbaS9xN zMTRgK%?ZJIdw16)x6V%3#|PKatn70hTYLWZF8`Otzvr3U$e37;t2)GubA)esFv%K6~B~j_UZaj%=&4o5-FBiHrG0 zF8g-O8l!P(c+bJjjGA8>LhH)xC2;^sX?W<(&aIlz~LFg%*Y!%Nwyu1v| zpKxw%rWTj*KV3WC9`5*D$_lkzP04?{f4G_HKc2+p7~!ucK|7r^Lb#-YP-Ryoq-Ldy zn2Lu=V9#NyM78+lfO+k#F5V@fQl!TlO8D}~f7WW5*#me~%tAW-MWagG`E6aeX*h&f zSkdxAm^k{Q9ZJ64A0X?$3oz(CuZpS#2sN0T>K{e9QdFL=6i(n_Vf(y2_?U5xx|vmRk>MSLKCP~{^Pl82-S>ZSC+XaZwU>_j-jlB^ zxYD@>9KTPgY~eW!?Egys6bZWMc*tQ8qO8>Fy}sOpshU%DQwA zDMJERumJs5ytaYo&U#lcfZZt6T`{G{D+h{G1sH(THcjiuGgYfeBJTf&I60}lwS{MC z;U{Vy>D7N+KdxZ|eLUal46H|)E-z{O`eN*7WJKj*9mQk#op&cjtn3>EjAOw;#Gp@o z@8#+H(NX;UyNRJg&&%@*AfB#R04}A94`lQE^X!kCK>ql}!t!DQk8SekI8E?^4AsBfcK2eNdN4(Pl9~ka7 zPmk-1TT+Yqo2!cq3|t;M9ycwQ>tjG_{^3JUl~y}nZq_Pw2HjG&BJRhuC2GwO#3EuD zj?W>z%4Ss`$%C04E9COH*h8QPH)t#FSG{xMgr&L5H4PuycTx#Hd{FQ1?uUm5wnRsj z1=99Wu+)?{6LyyQoo{vP>j1C<2;^)v=P&TMKsFpv2O^__Q%fYf9zLi*yNF$o%V399 z6#JDUvAqgjJxM9CE~$w-0u_3k1@%~ zl5imu@fX0dvEz2}YmT7%cZ-!W#{{7K9Q$bYN?V<502>zV7R8x#-cN1|f?ZSzX5f&F zcR63bg+oo<-5pbsCHO#UFk5Ia#h1Wmt62PSwKv^wW!=Eb>CghhZxmbce=ox1?d33g zPb#&{?nfU zis;zizkf$Ygjm+&fAte@sW_HU6A)rR$ME*O%E6=cL?YrQuH%+L^_-co%Qt?K=%u;B zp*484H4%X2yYF4gvJCk5F@W?~L_6(p*Cx(-TD;7~vp0a#eWDIpRJa#Rvb)DSGS{59 z+ZxKrm%f%rz)!*3w~~N5#(c+wPm!0{!YxZ|s)8q)kX22}@sa)qfNfN|Ms3FjiH%O} zPY*1VL|yG>eGkgDAG5M*7A;`84Q*1HvsyI8MLzi^l>bAc#;hpfmW$g}Zl|zhhI^^- znApnre` zF9kRPP+%7!9;`ETun0k60dB17AD$kb&=CsI0Sf9M_@8e+F9+WkY}9#g`(%!ues z_w?Z}!4gXo<}cX{NuqS85gM27uH9S$Q8T(1&W;@(Sqa1$G_CZkO_Z?7+6afQ&w%AQ znAMGJCP25$&I?>3r!)Nd2x69UzthP7gHr*N%%mI+Z!a_cBI`bcY`Dz&Dlg>|wJb8GbUEYx3;`50x z+t>m5CD+$91OyXT6;o5Xs;Zk~Y&yWK4ZIu8iI2+9VqGDeyV$q%t1?b`?a!Z8NJx_3 z?N;W6IAxFm!IZKVnc%ZqN!5$}!b+dS^9vOOc(I{<7J>fSGC2(;yH7v9^3x z#G|1PIJnx#^OEHOvSrDh?l5U-x8{Z79}oi6nJ8wGn{8IEw?luW1q&f@b6%fOB&pT9 zced9uG2qm2jE!DGK&vy`)y=DkjQ?Wd^jH~z5-}r+i_QinhF~E2LqV4*UL7+aT%#N4 zulYOdebls+{Pub(*hlyC%W^%!%z^)8i^8J--Y%nfXTC}yy3hHKccF8+n6mE1} zK_NRK9>mTF{%EzIpHJ>I+Y2suIgYhdW#u0{i}*t}h+sA?Hf2g<+O~V%MKTxd?OkvWOk=Yr;LJ!vPbK_vaTC z6^9xGXn7AEta6i;*Or|^z`&o~itiBcmM^^Z&`YtQ4dU|Pe#E_oT?3$TjoatihA$1q zJ<3;mXN)Xg(oKe$mJJW)XVK>~?J9DxzOgzg zimNbZDxZKw##XG+DaeflG{+0&Ntv}?FE@Zr4p7v_p`(%R9xcuFK+k~qib`)6mP`mN zo7oiPbW>6qd3fFJr@u$3sn4aP;(qb?fqHSbTb{2Ge_?g>u!~Y`=KyL{(EEdy?}~X$ zO#~wQ2hm4FM!-)i z4=}RUw{n6D2_)#n2j-80YIALfPQy|(fKH7mL6!)i1`Rs!_n%&w;nKd%B{+Eaaaq}g zh5W!*jN>eOQmfVH*8UjYHK*b10n+L5LRqO9oerM|fWFTJi?(Q1GNPJ9KG1_M8hy}J zWmE@H(kfZcYQBIY`vEZBEVp!=-)~tg6oO_DHK-1th!2+L34Dq ztJGpCh^Yt&ruu-lK4GsX8Bzg<01Cxs>(Cbx=pciN{{sgk#=f*T6-WcEoXK@bQ3Osu zrKvLeKCoMl=;bVyklo~Oq34zlA}wY@)I4X%*Z|e^SwHlyo!54WWh;uFJ*3GuG-pGj z%B!i(*sPVv;~2~6cIdMta2N$-R$iR_aP#|H^7ARQzDm7kK#*W_q0>Mr3b;7j z9ySV5nYu}Ir_w6+?!Pq_3O+blJ6>w2Y)xwECi(SxCJnzlZ|jET_m^RSA#kCE3;ldw zp2Nb!-0p7sIVO51;yEgD2r)<`Gh}UwL<{q*$k)1KeV9DDEWN>u|V|LWSOPwkdV^n=lcf+3P~6oSSu@)eU@UziUN!TCE{z_`yeG4uIhNE4g3UytXy( zPH>9rx;0chx3`toqbnQxhD(t2VSaZnZ1qhFBx`NvthBecd3lRgFTiWaciY>8S*xCZDrMl?0bszUAjTPEOWOPif6mG*`7C~8)KDw&Yv&v$FuAr`zOpj09T zl4a82+BophG6ixYZk4yvCSF3y=o_W`0I+fP=DD$zsdq3Or^1;uyHXGW7;xl0z5q_n zv)^}CrmQ2X3=T7(K_IO@&Uj@|@6%%k0m0)>(}B3kW^udu_2XsF_}R#=!mDE3g2zLq zq1?o`ySJH{Ng8?DkT4VWg9+!-ZQKg_sgG8G>6Sawi4nLyF6s89R5;e?P)u=W0fuxE2mYV8|mMGV^=@M;I~NUgT>w#_aT=U$M1A1Mp37_n?l{h z$7CNcKvW!;3+?Tp5fN8gTXZ3zKv!k`YB@ST@#5JiVfrA?SLEh$?WNLcS*Qs=-IIANY~Hd4e3ZG0%wR}MODh4vS7hY0?VXsY zIR9l63g{q0@O{rfcx8UgW9V0M9Q3f1($dnSw;3^%rtdT+Fk?WWa~%Sz3q2qK3aMFGqQC zB#HSYL_NgS)OcfJ1cC`Y80vj)Gp^RVM*I4@36_n@fv3qb3Al4~tRw>@iImD2931K& zbF`USd`9TC@OvVM!gypne4)AN9|)zR$Hn;S<0C=vc;B((l^28`-n)vXwX>nbqduGf*|a&q%H#LCRLj;pBxK?d`P!$n%_`Rz>soOPBAAb zcXiF~%&KuJV?&7Ashf_jmM>lBcmAyYi;Y8T#_Zxpmxn z=DIZ9`PPNQl$*n8Ih@VUO@U%V9>i`l{T=miLpmnvJwalucLWKaL;)QQF-e?_82MYq z{{99#Gnb2n)8(mjOtRtIx#aSn*f7+z?cFh}l{QLnm0t5{lCsFdJ+L=>+%EzI+oQq6 zEP747qzQRI8+u4$1njph&CF15G%ImwX@-&rsqVF;6+Zesp$h3HW+H_e(U{MWFKqSopOyUWfGq2~eX{bp10>o=xZmC^(m#x>Z-{U%brn8|pf0gEmse2ctUK#?gud<= zu?D+Hfc|W}z0){)tiw|aQ^Qv`pXcY|;t~Ra{Qli;iJQk}0#1z?@R3%7x?+>ZF*}e} zAb8f&;|b71ed$RAfQITYB9^`_W}TPlB1jo5)9e;U3X5<2|YNTr0nvG^lOFQlmJ3 z`J9mf;u%S2KPd=@n zcFHDe4LgB&@8|k)k*DivWhRaR0(gEtQHvu6^tg!0NyZVS0lYEt->+@X7n`jY7dc#5 zO+r*##c2g(f;=>gk+ml#_rxJ(4Z{MhpM|CyXs1*VpKl!Z)E(?Y13< z1!HH^B8lPa*Hm~`u_o>~7){*xkL5rR{DGVRLWI0OOZNl$%hi2-dQp+FD^5|7W`o~` z8my5xNIm{|KAnsm)i@itEmRE-qFP&Q-=@Q9UR_{ zlIN#Z%mWk2AT$TK2&_vsku2Jo{}q zK<)P4<69|PgWx5383j8E%piFlN%g1u^bxz88t||I(62#z)#)ea71JrQcp(I-h}gbH{xl;d;KKR^R(**kU|a zF%<+p5f<7V%vSj<_EHo#lT=f0F&Z5~5w0?cNSu=24}>SV{`R^#tXhHkr}(IhuVPf# zK`TK7=aAk*#0`VidZCWQqbo6y&hulK`m%>Kf>V2~`guvFU=+@tVGy5PLa{rYJ-E&L ztP37EM=Cx=&M!3A>b6GBG*;@$Y{d%BP|*R=PfUzpppOS4QTzzaT-XMX)$mv{`{=yG z;9*&S^NW;3SVOOAS;hfwK&S@i^+1~2<&*DeF^FSogus8SsG9fjVG_w|tk7>iSD}90 z&@B;*e4RZ8QNY8??e_9;=j<)HuNyLvS293@z+wj_`x77ZalWrOKAh!X9fD9-Q*_fg zgRKhdC;{L2LYeOmp1cDh%kasPuZ-Zc%$|2&^mjHGXrt#h=U_`n`;&j483%lKgaB7ecmB(Sj3cexI_cFExZ zG_~4VaJC~dvepHz!!X=%XvFuUylX(H=$xVNjizFH5v_P^tMlGePIsj1^|?-NPIYb| zHS}f2)5>cytNs7h&Nco?YmyZ;D+!CB?;DfTk}Hcs|k| zTPOk0oiu%}atWjbE;Nv6KU(wQLYb#&$A+NET`4KAtC5ut=WSXFm2ob#n zSSJzEf9}v9-45mHk8^iBt04uKY~2r1jeV`nIdq6dLm@ssVQHCAmqhxkkaHL&xNutR zD}f0`1|Tir9v)*65aRP>HubV$UTc5MGD4`Ty5#xfhQbBiaVNeva9h!v`&Nr=5lirB zRi1V;|JE&*34eRet{N7p-l`CS(t<5 zL!=$t=TQIBJSDio3HxWqDkn}KVc=MG>Wq4-K4X?rTrESvN~rHZV27FR@5NhIg{OZ+ z!m3_cDTF2bbzELL5)xRzi2&UNVFCw_f*%mns=8%@4YcTZjs>WT_|;M0L)oED^7=Eq zz0n(&_J|rTZkL{h|26?6AVHSul3j)$#ldi>kEg%RI$my%1tr zk)(LwrP?BL#*8z&pc`MFhe>=Mnaq!(Dg{Nnm&dO+w~b$ZSxA4{3IXM(qgOFn(%0*D zeUr{aaL^!J#aN#RQ)kRz2gAh@id-@VbrlLhqM@+H2VUUJv`E*tbCb=qv{3hKtovQ) z{nBkzL$8V@D9Q!UACs-lr4<9NN7&#x%YGDY*iv6_SOVTPAx=iHzJr7R$xZcT03v=M zRq}jF02|Te(9l9WHG)Zecs}A{6k$@v6(N6Fg#Ou{r2`z8Auc<+RKFm*x=|d9m`)MD zA|K;CpPqu->6bJjoTZ8j2@sVC3m+E~{jRtZ_h!ys>qdHp8SN!Zw~Q+sv8w%pq@XxAQkI#LYiV{~vt7CJgSuN>oqx?@!MmV?Y*lOKw4deriD z(!x+HDN$g6YpM8d2dFFF^8!11LwTqTB8LL*$o^&Ge)st%P4r7%ewKL>IM|*#o_PM1 zbMnxB4;_UAqmT;Shm-l;zdoHQXY)D2y;tzhTV!v4oQ$9Pr9biw#A+HGzTDw}i3L8| zd-&++0NXsFzmX#fuwrXITgz>((k@)D(#OnA!Fu_Evw&oAXE-elOxd9NfiL>}eQd&3 zPKa=e3>`BX$}$)Y5uOkC^tQ1f?yo9(ZBMbxkgMp%m zg;&HnkunZgYcI_JU2xOaW^(oqV%hkFj7))q_3V7mk#{Ws;jln~1v@l(ormb>s5Z4v zDe<2RbgZro)a!gyWi|euG6zf6-1Oi|9@olWiG`msf&6 zWx_!SMvE4zZnK&!jEED7wB0ah`Jhsi`5}W;*z5XuK^@uHr`=*Slc~1-r~BFJEC$?n z^e(umC#Rx*DMp}TkK2?W!bi>|`wsI<5)Yn}x`CZd_=VqUD~30FAWo79AZr26lxhiy zFcbm+s3iIV6njtrjD~NE9Pq{+URznx(Uh~l3VY8m$Bc_=*Ry|D$RNcD`XN;4cbLsoqOI)Y5D?2Qi}Z@^Up5?hi;< zoG~~Z67_@WensQq+_kS6J)EgcXz;Rcdp>fhJqgv-I+3&esv9-uFDH!?P!pil4V~oN z4rZpW=5Du#+1XNn)M$D-&C-lnUOp=?U4|wW@L1kI-m)t`Y=>(IyrLV@zbbDofkJGi z>ywpQss98~ugT2^e4LGHW{Qb}4|TPF#*@{n)Z~){HVArS7~L4HQ{!>E@%a)Z>TwH* zp%aA+VqF|OGTW%_`9l!&&0u_rYziubXE_noFB$4XO@95N@?(6(ljZ7k6euAgD8dg)R3j^d?$^u;^O&g%(%y*uOA9`NnS3SM6kk~#&W#hbR(}WcN^DRZtO=2 zFrG}-o`NQ30+QLyJ%FV^L7Hj|Y_U}}`@ovfa>(;m$o|;o)IR-|cioBx?wG+fj6MV* zoNhG$F|g?GSBi=yCBWqPGj5-sx8{@(a|%yo6d0z5-e5KSkB!ayVYf2?Vkm2)CL zeWYk5CTdZLO~~;XHl}`D=GbJN5~MNzV!V;^;X|9{E7N-+u#WO^&D*W8Csda8>M9t* zsva{0^idjoZCuL z-8JfzWrWpSPNtplhrm({e_j{{y2W$=4dAdA1Lf8gE1+w44u23j2>lCH9jWU|Wo5tH)W7tW$ z{>M$m13I>xs~tGt#E4R$K8@yiy*wfE5Z2Phi=U`)xA@DEjs@TdeBFnTM7g~?+c{uR zXC+6JwdujN=w81^(?v0a6QSmCtc4?xz_B9#8-Q#^#5*L_M|CMn2~E8qyD4+!(_eja zc+z^>=1D*Rz&UE4j^NZI3yU0Y2S11DxKx#q`_SBd?QnmH(8Zu-ViFY0I#CEO`Z23( zRX+p@FIKD6EVMI1I>}K^>;)CaTL1PuFGXadMwVE%##A31lDsOYOU8@H$i}MfDC`MhRrp>h@h|MOYYhVZ52i8ilk+$ux zZJDAMn^C|419%6jP9}kTWxpq&ZW?_*_0xli9mLL7e}}uC$bE`5z?S8M=G3;vT!Qxz zI^L@P*)qS-`8G2+xSE?Aj}eQ+7RtLxO@KCVf3ejJz_9_zx?&a&;(JZ28B;n#%;!Ml zMess~r6T#*;+{+wsLBVuepEaundTG?qU+%3Nby&t^OIV+a6aE;J+YY~XZ*zCjC0{5 zWhrpmdHk2ju>GW|`InpTPR*}ez!Z7w=xAG5*b8r{U&tv1E32qDt2LsW)B@WLH(+UD zK^Pyny}j*FN30^*s=(w%NQJ{fUFz@HB`-C|=l6WS8;Ts78uAH}`DGs|sDRJ3`_I1d zWBkf5_h3RqM8r1Z|2+ZjlX{hb(EN*`_SK^L5pFL_ zJalobMB1QBc6Z%tEnH$AR|9&w2#piIO>d{xGLQvqb`}AqJz=QQ9z_)w*pO)4zFQi9 zF8ot@_L0;tCrE+pNlYL;`hWnHT)=9+%d#W_nlG9WnuC;q?5fZY)iS_DW9_UeBK}7K z+CW86&@2EHii!jpmnwRCkB!$S!?vTEi@v_C$Vlng8euLrdk4GI;Dv>gpFc_e4tbI@ zMa`UKer@~WzVechQJkgM%o-hw>;JYFb^fu$L@ex9;?H$-G>Q*Vi0TK9czwldr}B(J zBQjTt(RlwGHig6zSk-$WW%XDkc>&i$jSn2r+`@wYYUBDN56Uc15XSgey8pCfAVL>~ zA4mcKbI<>t1ER`|Q~fEKeO(FDPrZ`Yw|99d+3n>m_hO$HlqafH@9Bv;p3yeINS zU+=eo|9BwH;dY5S1Md(mrw2*((6 zEvB}=fR8-Z#qVm=UC>=1j(B0IY5}mE0BoTu21Z@2%73d__N(s0EVav?dzJ2Mze}yB zdCz{G0p3f0-DaigvBHwGRMr>k1&*62zKD7(ZU7B z`~|QAUv6kXm6Gz#eVp<5G5wz2IiGy|KP`^5LyXt|< zBqv1dTW|v)fs2^IbA<%wW5$8~NY>D0TLMDsLVboyh3+BP1Hv@81Ol|gFmZbDtbg4B4WAoi1km-r9TlqjOl5m-@0tkkjR-w$ zXISQDa9YMP)7y#l7f6OPO7sIg5>xy|LDP3S?G4QAR1|Gj!9hJD9t}DCb*WLr;8@(t zytilg88MeW=l|3%_w`ct{IZGKbs4`w0=~HPz9w_?e!-<} z)E^MxA-p473`D$kTydpUIzI{-*Hxx@86n^sjN#C#S`DP%EMdqbQPmtZAXA{kWH6gR z=TI<^%;XS>VsNgXSk!?lm9YQ{lTOcHBNWT+`h0)qU}3n&)W)H%9&n-34gd(+&+}{@ z4t{qzSEYF~g11Kcx4@WLx8HV|Z;vVtF!}&tH#lwn-3OuDvnt z-fy9Yl4OVUE+}QI>^%Skt1%##$_;=gY>Z7!oi*D&`EB(}0+Viow^zU=J%^1~%fxVy zlDWu8Rm3OIChqg>`r`*L9qp}>vSD&sGeGd*&&Y7D|9NkwJU%l6+&ie1IT#g1A7|Cr zqg5FIml8Q^JViI8*Mk;q23mLkY;LYxL593w$xaaVqj+Wjm4@$^tQg&3v@Gi@g_|=l zZl0A*{iKsB3Zf!`DcI+)P;Lz=sqgRh`?Mab#eL|`%@Ioo0y6(+$I>v=ADku*Eew;q z2MDr$C?C2Z*v7&@uX(iZ7tQ~Dq&DUF^I}$o>rlA?q|{AD#XUdBskRopZg7|V1PvV_ z+`>^0ijmBWn)DHRVA)`pQPTeqCG^@yL?z;2Gt%~WUoaV?jYU@<0}aLHSQDOshj=#; zM=s?CDuFT(Z@+s`*zXZZ@ZPrF02fd^&wh$YZ@C^!c(wzid2 zpUSQ6ViLo8pM(Ef%)1ouegkk8*H;_&H1_pfIXhVtm&V1!k>L;tGBbi54OUa(Py^z=#1NgaFi{E5H88x{ z*-jX#ue2RSMrq^X*3i@I0GR#ELQ1q#47@urhdNL=8j#mF9Rz5_dQk`#|2oU@<=bj$ zmC#SjfJ?fjcu>d4CzT$Gn~g?#^1rBW&3xbMnaD}r0BtCWhdVP=JgA$KKvG+R3x$E zypzxAnvbD~;fw?DB=FxMNPxo|(g5NA{~7p=adJG66B842|74J~S)Umwojb*@X+3@# z`C2`+pel%2Dv#6k^u)l!L~mf~{tRKKCjNV&!|Et6fzRa){r=o5Fbt*g(#>2FD2URi zpZNQY(3JZ4`9<$JcDvvWJ$G;lP!a7lM7<@uiCD)=5ghP0P&pm&fsZ-1u#oaN3cK$O z_8}ZD0pF3qx%^WwW0?t~E;vIu5Ct2e+5yp&{J7v#6jt}kx_vgVJX*bM^KK@j_q>q0 zshUopI=tb{?+0)XuKVT*IOJRRj(wDRO#y^}QwO)Sqb_wI%P6fShD_ zg3`46jh-Ty=pHBGxtdIr4ugplgFQO*#oUsG}i`C)#81 z;W{z9G1@`(p@G<9C+1<)tAXdNBddB~2F zKhQ-y&eywuZuT>rdh`O(MA*XycG|gg$#|&-3|iWG*!y{Zj-*EWh6iH_EvGizLWXFs z(wE#h?=Ftv_LE{2dFvE&DF0{#QVJPuyCI5EfChBN64jIZ?rw?L-nSPjp=Vk9vkr0bpaW$u&oAIcHEuY|01}P~v2Iku z(peH$YL>LiZ3xC#d;VxLwczJSe^t0l(Y2{Z8I4DrKd5xgx?Q2=A3lITdsRdqE_E7; zQ!fE=J#$s6#Im#PH>ptK4%HD3A%gTtwH}ap8;=eye zekYH-!45g_mHC#tkMKr#r7ec@->1FYgFx7%vq0Xkl@wc)d0F0C`uh6N#)h+rDWE6b z%?61>!uTQN{-G1T%XXe)-TPPlmyShuJ6llucPT0^fvCan>KH$V@!T=GyywHiO3RO* z;!uzdw+ zMfq3_T@;boqp#7M;2~aNVF5nG2$uxI>&F{(a2YTrKW-XUq!4Z!f&W^yL%_HWG#kO6 z*eR(^En19r5b-Fiuu(VLWH{e|Z)9mC!U1gfeqcBJ@%`PLp{BL}?ZTa)`zvtBxvPJ# zA`>-t3M`5H#FdOU6ED&Ex+1QqIN=vNNc1PEI<1}kfW zXlo7K7Ua=IS_<@d7t6)Mm`@qvFLu%lMLNB>sXv~Wx`DXyacx2;!l>;+sP97CoQAJ{ zxr@qogUkTM+|VLr_4U8&FLtdm?~{d?^{hT74aNhs0i4Beh$~Q4fb+ws!0OP2!Dqi#>yZHp2d)I$@+(A`w-@`{_Y=>qy>WcQoq`5N5xxSAqpAXxmjC zg-X=k`!b97A5g@2yGC*)af!KD2?4k(`ZED$E^bV|_=&*)b}$ARHHVOZUy#*K)#<11 zlj|P{Da@~P0)fFh!Wi7aU@j4}Ep%xu4rifT%SYeaQyY(vDnov8@NH-tJPdS1=uWzV zKfPWu2!mx`iy4T_wQfIQ#w9o{{x0K!L(fVc91GJ(n=P&_{)otBOvn~<%2=7Qc8SB1 z_S|E@_)+{o6C3Ha7g2LP8PVpGwF)vN1}VhFU#4G#yt&JD6pDUb`QnDv2ER}GiSAb1 z^D2^+MLc)POWjcLL4v}*yEJLUdUaxx{ZD6=o?ROM>^pxi)~qa#jHI2mJCC}~o#_97 z8uT#Uk^78esx%OLj=@j2WQO0vF~3H36iAcMxHXc4+pz-|nu2$;PXQD(1u^j&A|KU$ z1A3x&i0-qw+AIhuM+4E~TX2F{8ze!40p4I)GRSIA0$+0@^d9AcEE{r3d~+mQYs@-3 zRT=XwQv7)1=qb6D4R;%uPx+rT&Y6KqrdkVGH)1OK)%9`ls}YE$Jph%^a)5yjUf4|I zLRoF(WT-QJnl-X%dY+s^0x;wEmp&Q)7L${g-A>%Szn~6FN2{dqBN7p7Cd%hiob<_z zb9FF$rTtQ~yU2AsAWO`d`?A;me5rkP1&Az_(NIag)}!8C5pD0xYG-X5SGmVt<}2;v zdpTu=4_uBYlnk^8KQ1Q%_^Cdk{P#ILrx-y0_T3zs_dK3Ia$ju){HCZhG!Fg1xzQ*k z1;9iX)M{62rqnQuEv*@d>IPT8E8eB0{D242Y*fUNRkqf}LkS&FET2vVLkU~@*Zj2` zE-nU;#Pef>y|V?~i0PS@<{A!a0DRm`HL`70+jinG2^!uY#(!U)85oWb5XPkJM~&ny z$8c@14MldK-QYDc7tCvZW-&`v7E#RHH}C`|{NhR0(LdOF>;~&`IHp~QidE0OC+@Ge zEV{-0?eyDJpJq;mQS>u5#X2bKM4u?gm}#ioX&+k>=Of}Y2WFjC5%DLI_Rg^OQB8Kb`V8stq;3d z3`;F7e8BMN-Qd-!1`LmOc5vl_2sp&_tf`ACKw;tlL)4-hXgVduFpK`!pH06cLhoT?vJb{v;-T`E&4Mc^7g}VzFPyV_(`?A51*As!C zt+tQHAClU&kd;(M788zzH4IOECy2dbs-G^&dzXyC$w`UXviazKG`~=}akyCbri_Ws zpPL(>z_HZGh?%O!j=<;4J`h2GgliEHaxayB6rD+bNRi~7*1@a|Z~+5#H{bF8I!IMo z=4z|pe0MNZ3=m2?jgyvNi+@<~Wo|vUe|mr#XuWc^2KDI65`IC zQU>Q?UMp!#uH>UD2tU3}>SZ-K`A7Yj?A)`k{8*;_6#>Eb#LCIg-IRzVG3URV= zyZgA-5-YL^-ew3bv-7mHnMm}}Ikr0O2O!=geml0GGS}+~QB4=DxrFVzTeOovMNKE+ zAzy8O>RWO&S}j{D74kX)q;9UhzP~3;R_< z+KkMMp%7tXJ0~()TcD?xVd?O30)n0I-$67zT+3%uo%y`VO62V9To_nHZ|__+9g$Vb$xYo zcrX;)NNWB&^#s+Rr(v~w`PcY(KXH7@!7o9xI|cU6{HEosb>l3!+SY9uwpfHjYP!P;x0uyqn}y&;TRC{cwb=h5gx@^+&{i@dC9eQtyx+5) zl@%Tr_ZtAy`vzDQrP$E6$aKaBn1 z`2#PIbO!fHX2xvPxJ$?fMHy8UTnmiEzYr($K}6}<3}c0PV$Rxb6Hx+)rwBq3Km^qN zq>PASX1gZpJEp{_r8v#Tg{}99z#%G+B&%b=lj*pu!Z~0XI#C-7+B@ zG_UZ%f_gMf>tnSzI$x6_de=W_&T9NHNnTldP@H@nNjhd_+-msygZp#4)7CLaabTi{ zw)4s_Fa#zl^p}arH*AEL5(x zb=o&?fb{azs=)&l&1Y()v}Md4BqG|XZ-WJk;92Ju{>XAv6mwsn^8QBEK>Oo>NSeoT zcF3yn0tVUi&#a}7sd!{U!n}lJJ+Fv@nsldp4m(*z{`&F^dzj zC4g=|czr^z)eXJ7M@L0!ocoP)$y4{)J9p`7>te?a1w}eE6c*>x;t{ug!S*YP9=8^d z_u|6ajDToHB;ax4157FYxo*?;s#ny!Y&kh>h(M2Ya`_ zZ!97_1>|)1Z4hppvYJtUA=pQdr+yg@8VAHcg(<&K;=p5E{YgpZzjZC?7;V$%KFjg28gL*j(8N|azyj@2zH2@FB6 z)D$FZH8}BS#)gLC9;p3^_ZM_|$6-aVC!6jX$;q}xPhspFngcmyAx@>IV zkzPk4q%z|?ZEFBbG+(VGFU0GgA8a2W6^0DX+^$8$*{K3^e2y7jq<{gwm!QI5(c!D> zc_-@VIE^+V&es%2WnpnBXvKhF3-qQQO*_G>C1VJ2d z>V1s3v5WI(9?sWNL*@s=jgr2zXe;R2-7PviK(<&UlSYEzAH~=n185v156pWMO zQ<3hC@1ztWJj@QC3|E`GyYrix0Da3)!KgJ)(i&D+HF>_n$RJoHr4*o{YB+#B=x3ap z7^H2Y|8`B_-z7zvrcHvTQ~$-;UV)NG6w#%AuCxFN=e+)VaqhR0GDi4BHX$#AMSzzB z(Ew53D08Ag@Q`(}HB(7yPTTEFkvy&hF|V7<=X1HP_u~yV-v%3|f0EiD4fiNxtiQ9h z1)(sZo*u&~?a&RQLhlD1+wZ5yJQM*i+PJRW{dOg529yKIb0zRrP;{lJs65sqv6WV8 zA5YSvqJa88^77&Y3H=!7)%xSj(tUV%+wn~tIte)i_ffhG`GH*nruA=2V83XN$IjZm z9#1#w*dwfhP6*!BUbtk+ zG^$vtG!uaOPZOg#b*Q?!yg^_OF%OePlr))H&;4p&&3bDvjwTd?>(PB7;T@frzhuF?tbjA8$t5e%#HLDA`lu_tBZ` zOkzF-YWvO6(I$F&98=Sf`Qy)zPm@cAzP>LR9lZmrqoZjViJWXKAEliyE*^a3VfWT5yPABTFmY&0@P5M!H^LEgpZcB$x62n(86%GCJg)7I9Ui3iMS9Fu zbW{~tYdK%gg>F?B6{(Ad(BV<@hlCA>KTzdFhY_*A;!Mg!O;ht=9}o7L7|qwPIod~N zHi$^~yGLPWham|$bD>$K?_^`-y3QwY)QBSoI?mUhpPLwhK)VPQvPunW?U&^(osrDf ze<~`}5vTq6{Lzriq``o;w-+SZsYpsE0b)|pSAGu!n%%dww{nVUncuF(N0M5~n@c>N z9?06-2Ksw}2L8O9nMF&`<7$6Ox6WOaUZa_*tJ)#eG^SxqtH%nmI%ibcmEN%W-bhV7 zfffkJ&Cmu(a5>-7$-X1V4XWbbiUY#K|4KQkl+geNz?;}h|zU+Q^jN%}r%euy|;+AGX6hSsxnLVbMGj;Ih?cBEgC z!G1#cOv)GWK$Y15Xpqv?LwH6z)q2hof2Vf_>}PD~5AgARdlmH)I{%al zSyc2iA&4e>d0py!%@d138zEaC25(v*HUM>iWW!9Bmt?%NkZ0?0r8FgDdUDPJyqoNLwk!iZS9}wWOI^%zlYyAO3Jh`f> zC;NKC=V!nk&5pKcY9av~O?(2s>EeV4!=u%*@g_h*>5ul{v8^pN>1n$vadKu?)39`m zMPFtv60>|g?TGaJwP%p9?Rcj203t!6Cof;nKX7|~X@`gR=D(g{rpVIlO&GvvMhp`b z5ez4N;TldLF2pWYY7D3-k|b^+?I+(-ve69g3X&ErLxlMX+}4-18X2*tasCV9Cscx$Tb zA&<8mDA%1B>tA($tC}Tt{M+~Uui_?|7MNb6cqGfqUtrcH&30Ci*{|&+S{IfIV4J*c z{_k0}pvDmgMV;C^Q61`Oi9o4s3INN(9ksY^^;v)TSqr? z8ymlKb9Q_Cq9ei}$3(`(2dN*I2k}U}J*R7QWbYKxEMSj`Mp#^}w>Q3=c8IikalOSP z_WTD-RG)7R{Bx(yLaL^cgLtC)+DE}a9)+~8dU^EY>+IM0gT`b zb}HQ8Up+qEe0UkvR@D6KBfzs-U!hghiA&(1rMK}h-}dk9RK`bLc;`Hihl_zCUM08k za~2B%G2NU>BegaISdCxV&S^k}u_v!^pIlob1{+MQZ}E%#b90Dd5@&`STZgYyP{knH z!cwgqGYh-pYK+&lVdKN$5E~ni&9a8al;g6ya`Ddq&Id|(m~T!|8XOm;T<&aSg+3kR zMm;>f$mvJ?G`t8F*Se9b^c^Pu2JLGJzaQ3OOeGlw{DMsO9}LMwvvN=;`n8S3U;$zO z{&2;Eb$lo#DZaY!=Mh*|p4u;aDg+|5knk9OPv8)sG1KzmU;pClesO~!2EvF;9Euji zva_-GZBKXJ6jjmDS}B=qyyV#t%B_ydAgAWaSOy4!QkcHy@!pnrfVm+G8I9DkE(VSl zw4q+!w>M||g>JqfOZFpnGyG*|At1HY_T|B(VR!w4I?%W-Qc3Sg=GFh>&3$HV<$^x1 zM4F7$_&Wd*%WdH1fE_0+hv(=I3W_THOGqxPv=tRBCwKdBfAB%8SH zWA{BmLJP+`JHa)*sPdN0rXRr)C8fs5&tSHRgD(^T%sV8kx7u!3Il4(Er1Lk>KX^;{ zZ=t<~XmsT8f%D~oN{h(X8%(LGCaxJDA5Z;Dde|l2_5*^_=GN9VjWT{KyfTb-0IOr6 zl2<2%s;W2%Xi@MK5R;!C;Sd8?lIh^XO=-_vl)FQYT4e;#F4nj0M;h$UB>1ST%t+#6 z>4m>0(ymJ&1v)y}n=hKSi~_Vf zHvxeFk3AIYECGIbnSr;9T(~&;Rjfh}8&pthzf^{_7)LgG*gX(WtmUC&d2N`x?I~aP z;4!AT?1_`G*4c2d@&B;jA6A1m4>JU4r!Z?WYI(YsGv|!(gTqbi>CnOon=at6fZ7<| zRvnvos@pc-aNg#%t$h2YXM22aiV4Och7^oCrP_304hcYQb5WUmP#y=|dyN2}vw+L9 z4X~;51Yw6CY-~7wPbF>dIFuXtfqoSvh~V$-xXH=emh8Y?6K+BTHx7`A6bQe;IUuN5)0II%Kfu*bPITd{g+DbN=pvTj3`X^M9&_yte-juUe#egy8n}8`MX!#+ zjDPW#??BoScw_JqANNE;x$QfZKR+B40$szo48`ZGE<_3-y<9A^ya^%V1v(P0%uc79 zPAXW&5aXt)-@kuPO^wRN{rG{RBFiM15zXlrIq2_{FJ6UYJBX`*qTXBvf(v@i@LhF^ zy?l@np4%{WMp0ms@V-vNpJOjS zp>}hqXDrhi|GGv-1|XSyMBjQAVpUt2e+lM3@~qm;u`H7qT?b2A2vZ6f5M|6)LcdbG zgx(uf8%e9sE6k7~6mVDic+x#RZBtc6BO&3fSGLc53y&PGz2)qTcOd7&tl^~!IcqE` z3SaD2A8~O8`gI^z(Md{esRyNFCs-+StrO}mqOuls5sM-o!zZdKKP=N-0?W$M3&sa}>ggl8T6^bwk6jsdEydNqBZ&Cu^~B z$pq>ff! zj)ZRKce~-TD=?L%@GU|~%JIEj$t-rQ$1*d)?VvU*ZR|wo$Z#Jqq|@a$YZuh=CUPW#24ie;BB=W|4xdCbK z?=jmlflT=4&v&2OH+tq~N`lK;4+>I&gucV44^K%@D0=)uLxIvatU|rZwSVNg9ppW? zFMzI4pJH@31V>h~WG zSi8BTJ*@2oxGXp}SVRzhsDiJ-!0Qcym$l9dbRa~~U2?JpJoUO30k-FCKEbSAvs-rO zjR;Eb`|QtHF?4j)UnQ79nA3=Q#(W`g850!-EYpEcJUr^A?BUY0KB-nl|s&E&)UK&s4f8C{|%rg|n zzgKZG+Uc8b8&FXpm{2y~q@l1&B4bg)9{6oVCu^NA$08w#mljH^ViOT%dx#x#&cUG3 z!Dd#AtppH+?4Gd-1arLp9`FqcSCu3l_iaAyzBIhs?KK%WxiB-|C(y%&@VjZ@3nOPjLBq7D;Mu?1ytE#V5RhrE2#3c8rl0wy7 z-$x+6`1hP?SP!xyhBLq9-z?R6Hz|`T2cN`gRy+9i2-^;kIZ3h0X-yi&{4_D{i6VYr z`OgE`A$rnuAs@(5Dd1*b?-~j_@O#v&Y2I|d%?}V^iV6zrT>`MG# zOBKBl5Mb*oZM`2mcucM$Wp_3Ksa;T0r64H;v;s8dWR?JP+wb3EmmnchgvaUhLICujK3kL_= zGQUtoS)P~-hvT(iE676E_WL)$jDH{g7{P&5T$zyR0lp#8s|g-DYh&yE*vd*9Ik^!R zmzma-mj>!1z%mQDD=whNf#9U*5@W_BCMV&m373=doKwQ(cVw_p+!1lg8on5eK^(lXLF zsA%Sx`d z^(g$P>V#mKEWDp3=CZZrWC@@|CMG6pe*d1B$#Opi5f&2=LmBvcdR0rn4?3YbCOCc&-B~eE%)Fh4kgz|qL#VfGbkPv8p z!^H1?wGyj?8;&EN7O0zxF8?ktCpSkfk?j@zzE;7MgQ!FZ-OE;IAT4~D+?YDKE8Fm3K7@h}5#)(Ihan`#hR zR|mEVUc6SM0ojHO;?vC?Ded;mj(-5;1^|XzG}QgUpxpRk(vv-c5LUCuaqp3C{JOQ33gutoXsG3DOZvH1*Ub7A%B3( z>1$5y6lq9s$hfRL0N(yH8HizE@5;YDQak7)&a~@66Gfzk^cn?F1j2D6aj{6cN%h5RM~uIKn4PD< zK)|U-!$3batEmzi{D{cqwPY{!_$1)@$;nQ<2*KpcB_0C!$qLx(`$t}h{(mKB+{@kJFWdnkDa>gt7r05aO zn)t##?ssnpv2+Cr#A0(=0F=)ECDaa##V8SwJqe4>?OWV`3Exp3`)P+_v@`=;Sc&E@ ze}53BlJ4UJ$J*Nt5g&!d2poyK@_D?={c*_U!Ll;ZM^8^vB~MPCtjh$JAm``j5QNQx z10sv6MBBEiUG60{{t8z+4D#M#R_nQwzhIsN0R`H-eIN)b`@qJ zd<^u;jsfvOpC2C)7VUF;dI|>yUU+ONvF~VveRb`Rj_y2tZp_zvHV7?ivI+B87B5F} zBqR!L8DrEvrg>|-YykcB0|!c%wp*R9QTrd3ov5$mnoh--!m8Kpwle|^%Q@t)fh|EP zA#t!5B>EO+x`PEzy>1lxJuemef&dp@w`X0RBsqmDquyv{g>Utz8u4hrB6dLx zL=|fb~qu35M3Ue}s=w0gK zw5oHD_drauyu1Sh`ZzdQ$4;Rz)T%G=IGt(pt~grp z?#~kQl7-Et`8&)=xZGD<{6d?hNJvN^!;gd}qQ;L5zBISR?E49rMtCiTbts}ArmADl_YzH!oGDMp`G;7JWZl42LJBR*1%Ef;85OW13*Jgg& z;o>BXU?XOG*qw;WfMi4m?1!AVpJNwD<{zrMx-K4SpEEK_(=_E=R@XG~&-rN@6m`xRJ-@9)viugPrdVDK+)>Q;5diJ(3x2aiyBFDr1A z;&p7c0+%iEY|&s7H+vvcjDB>9VroH#CK0>2rM*2X1;xh? zonNrC6wlIC;g=GThgCLI<@MuKZgPi~kwU8wzr7VnZ)?qp4o*pFA?DVg;jMP&D*_Ay zep@hiNBQMX-0|;(pF<<4LbM#6Buyc?-Q~9uV@5WTR~^~Lzf}}o(k)woh2fp~JgqF&Ra8W`x0jhLDY-w4g|k(SlGYVW zz3Zp;dRS5(ZP%Zp5;HdQiii#$H)>bTZ0}G*%R3Ujr7OR$HJ0&EIY;I(;&tAakh5>| zh{A7>T?R4vB-UbM)!5iLrl#;gm@_kzzP3&ygNUD#*F#z(uNp`BVDuj%E@ZlQIr{U` zSF9!YG1GR1C|z$mi7xUOX{W)U`~TLWo!_*l@@z`ut#7pT%BHr>>v6|23Su_a^*Y77 z30K-$AM9YuM=mc1z!so>DPM-#5+8%qpBcTS@|B-A$7_aF;%=uUKb}GXWrWtk z#cDh)Gg0!Z`q9sdii#LQVslE2v8&|@cy8%|Ui)kb@Cr4k2sbslAHT@rN3FLKe@ii< zJ8E!bm@2OGGOvq{6Y{=-jw!K*{eo*ndHtnnU}Q?K&t;-;plnm*ESFSNYUJGl2rEyK zlT)a!o}Dd`(oIip5M;D-Q^r_1ks@{&jfzYq#G<-%UPse^4)gby%g#xIj|!>h^-&u(|)!>vIWgD`^~6zC>ty zDK#hM?betvZusO+ThS6fwR}r=1B9RNLgx$$si`k_=`#p<#uq9wUyxOVC@X7NG%*r< z9F|3QU9~o#Tk49VXJcCt5>nIM7n4Su?+kp83rxQ62Hq@2y+MiS7CWb-!`O1ClX%XT zay`HJK}ga9g4;0{8?;A0L6f$)7Z-(~9e;kR6OT5ky&chxAg(rUQj{R#bGGr~f5$^h zWuoOHc>Da?VQgUUA_z{6oQ!SwMN(>0fPAm<;o^oIcxFHrv7GO7wQgM!0j}zET47}7 zTCS?GAvvV-Gyu`kZp6H9c&JGOr_*aZjjv3vVRmLm1Ln|)Pfi1IM>@rr5l(o5b6W8y zbOBU;&K2FSkr6ehZ@EFx1j7mOg`<3;uOxky&r$r4Ac8hkS2asAQZgz^@;_v-EX)-n zBmex_3VfeUs2`j;=gu2MYhC#7Pp8IPQksP#jXN!oa$B}sX{8vCk>Iy4FMCkI)Ei1; zh|c^8i$TWma*CyE{>&lW>F|1v_2c(<{OGl|_DL@`g%f0yZbex{sRy3CzaRo^pqFWM zNR4ckS#O$7AiY&ZM3cLLVH$YE@N9+MNBMjOG0D5}8WpPyXjgCE4%CNiR~;=CmD&R+`ku1ONnAXct8~jW?Ux zT{9a2#vCYjzPKdNvMMR)#il9ZK;`uZg*gAE`7^sy?$%rMYUtIVwPwqW54ll}caO9FI7j*zn*48$fpc&Nab7}ABIUln%F7gz4bv&yL< z-94nu&C{ZEI7m_5VxQ(?sdM(}M5(yt!yV~a0^c!w^Ru20zc7p z3K@kM8=sqnU)ORJiW-89cqXCjjPRUMzg#c4p4}CW{nVl!et(|8(O9eZ?EJEmq2&B# zM?<4|iyBy@WSII1b!@q(KnkN0ci!kH1Ntfy22FQBQwIw*45Lf&%pwivRq^~h9v@%n z#lYS>ed{itjSg!gMb zX5d)@=?E8r6y2sVsF)Ns4VcJiM}Lmnz@L}t_e`{Sw*d?>`RJFe=rr|eIEzI?izF8b8^= z-o{4w#Y6ZJXXmrt3Io43Vy7$yT9qVP0q)t7>zlHMaTVnZQX)PUknkZZOX27k2K{mW zc!O+qY(nxWvL= zeK3$vbib!GWf#e2_um{J($D6~%((Ue9kz>s-=o?XRM{03Zjdxc4Q80)FzYY*$s`ty zys?pc?2Z(*)G=<`#=@p16NS9ACS#`Fb8F3( zxBb*AW9C_g6mrj8i4D)%elB-1D_@@b<)~x5zum}dZ8Ix@>2@@)Hur5f&%uU1Q(LDD zmTN@SzNYKdMI)2e7j%>e2ka}=F`5Dgy*k^a)~08b7t;*O3=DZ%tZ8A;(7QKG>7?=` zcUiVY@EG`7vF{%_Kq4C#B5s2|?~7Pf+Pa(-o%pIFwr8VfbFr?zqM6n7eYEFW45&!O zkpZEr>)X>;cw8WK%hGhS`^r@{u=cHp5xRq7N)9<>tyxp4+w<1I!o>g|n;UNWZWEQu z&96U-$jK=!Nx`2uZYRy8PFHVGUWtU;XQo}KAavT$n-E$hq=h=J;bkzXNCMWZJ*u6rQqTxih$;md4|G4-zMJ|A=WiOLm=>VtQ9Hbx$r8jEd9^^VUd7)Y z18Z9~=eb!3$~oU=<6Pb#!!G|ka2b3L!Bv}}N>%y3>DLsgyKGLKKpa6 zt~NhMVmhU8)-M&Ua#!~wUPhLW%07aGJnX#Rt9KBJS=l9y|A4X)O}hNFiS)4E6Qdp`dDJIsGmuY-^L@Bb5M> zuZ@kel6BY#`7b!reNYbacV$14xfGW8-LA?|-tR-q`ABKJu8AkxS`L*5bB;>2PybAA zt(Fip<7L~k39!GSsbl^71~)r-Ba;P!K+Okk#6CLkAln7Gd#mfG1#u>l@_q4W&|$)! z@64EL=$ZI^#P%?CQT2I~c3%qv8H1bvi;Y>>*HC`A`sdG`g#|l1;Mmm2gCUvsYMssy zDq*BIqx(jzFNSVE@ddsH8@rcJO0P_x;AsDND>p+uR-4hJl`&dF}5i z)@Hu9Cxr?m)&1JnwQ5f;mETxeH`v`k3!^r3NiQn}uE5%W?=}*W1L!!AFfvY5jKCP) z=XkH}gla(l8tcjR7VWG}Oa%HkMpP64zN)FIy#n#*S9nv~Cew3vJ9z zkNFatkiln-FEG0W8JNkx5rWtTm-Ki8g;qw?0!QSy_fN&Sv5mx%i^S*#I#Hb%z7UX# z#s~1}3uo*ijE(Wks%{~u<~r#4SZN>Y>T96KMG@!o!X`^wd8`Ub5&FO=QnD67bF`g( z(QUCe|iz;M0zAAm4sT}yWf7?>k6c}KDg9Rx-MUK8G$RhhR?W5TDRwYUTIOL`@CV*%^O#mxYl|M_s=)-}iH;X{t2I z5($@Axg3u0y@!O{P~*H#gq>?TzmP+{-1+(8`TEX?_lGjhOcN@qT(Xz|ZoMj-W;#>F zLQy&MMSN9WRqxn3TB5;G((|j|G+wu>4S+T#@8s`>%{Y4mR=Bzk+ zl$R&<0w@I=R&;pyi4a|8;|*~luc0X&pnfFX_~+^Ne7Ur`W(F<{8a7QLl-zpZz0_ZjkAMi*NyHvv3X#T)hTY!4 z!rhH0tA@P_ z6l2jOGPK`j{CK|GPS8GW^T-HA*x~6@=@2T$Dr+3 zT;fR&X&!^IufM*YQ|qa(V|~hdPhp$ZAj#uPtgu4n*3{f=s>pT)wmGh}4}v$ixhnmm z;U>Wdl>&`EK11wAYb9A({gsuo`}=Ol$e_S!u&&KU`)ro08z~bcvmB!NruBEI%2g1@ z-yPPOOT5EB0;XnGHn;FmGn-`*{{Cx8NhR5i_B>d$Qe`^O4ouhA2I~iWzxyomCc~)S zUi4&|Na$IO1C6m`Ft_i&ziXMP+*jW3k8HNePON;|Zd8OdaEGf3qg@mWkRbEF5sG;n zVvlSB%~eQVlYC4==IzC}k@bur;gpH_)%i(qe;>(eT1tA8Lo~XsxLB{c#$$JZ0LSeYQhG#WKO_9bLk6pl-hP9G|?Lw*d zvaY4tUW8?xU||TU-dG*CDwo3;9=l4 z^8f~@-Lti|LcG%jZ~Iz~k8SPER`|v5a|01!iAa7=apxCaHj#^@XSBaz`~z(B1EJ_O zZDWxm|29#BVURXTD||c5Wa5T;4$6Q=m#!a#K5?*m>GZazn1tZV2e9Ga7=pk}) z&xeSKiIDyM{_o$Df`Vpyd)*oh9n?0k#5KRzla{T%zhQmbr@qO2W_Y$%ZSjX!kh8Ym z{6}Gv52eIj)B&;i;G&AKO@Ts-6SUhGa`S!hGnmjZ{F2bi{z!Gr$F!f=6JMzhdXT|M zzn+}r^|TJ*D8b9RU72(HQ>N6_hrU_eOq}axP(C19^D~^X=j9-<61n5D_qfxD!K@=92wk=$LD}lGh#)p7#}%$SP)% zAep6E!WtxT;tJ-^-8em6OC0R>MFCmc9}E*iG9(C*aJt0F&k+6EE@R@k58M1Ys1F9n zQ%^U$zm2;)(Vt)Fo*WzGwNZS${iZFx>MA$htvS;Bn>4qIL6CGhRxMg7B_$P1KqH=j z)Hc-S{VoVqpDC_rb2se`6MFhv5fv%1h)soicmMS9`mj76`(gYu2F5ph1@xR6-9o)G zYFlj2(Aooa{j66E0xoyB1j#T7#oW|6LVQHw#dz-|v5ZqHHBNhj?WWXw1X)}Cx$c@v z4le;{h7-h@3$U}Jw6=O-+CTEDQ`kPDz1)w#TsSVb?p` zw-5$R^{NCg-G1qhx;pE|98~FQOH2L?4_B*=`(9*C^F~T3(@Yhj05|;Ik(AKA(ECpE z%_xqvyrKWpbS*cx7XGTp(XjymVdERV4=a+DE`{vN-<7`{6cns{0E&t_WaNo{*4DQ0 zw@zm4_0Y&0<kD;r04bm(rbZ%h*6hafOal@-k=C)DuJ z%0$mMG*A$6?w@rAAR?m8FH}$zsgYs7Cm~+U_}&OrNKQCUh$r>Ed^VJvH;Zm#>#GBu zC{xU8PL5;yC!f1BO(&=8xH!ohPZ=-h?(OCx!dIOtiIDad@8G^_5gu)NW`4|{gRtD4 zsEL`6;6~h)9(+VauUwkUwM|6QWTl&5u2>^Hu7gt0H%R{E$#i}8eQVwS_4zp`#m~+U zjb}qFEDDMRJ6&BnR8*xnI01e20C`nh?2U*9#WdZF^JHS0H46M(h?s_%KOPzbd&F5Q zsyjOiii!&M_dRG;P9FZQ6*e|~yStwW4f+`orn4Z#MH!Bto$|VQz13Bbv%&`+Ur9onbX|d%%!Hri}|H+Kwp2hudf){Q-85ooxz!UwimIs!DD9n6|$PA zjL&1Vn3#^J?H8=HX`!2TuUV#a?rjqit333-DT!a3*Wef%^3$TZ4IfQT1M51F>u=Bc ziRL0;NX1EBdoNNhfCnAXA=bXp1d+7O=+>+0EvaraWgR=y&)2+-)J#9Q86spo&{-Im z9lSrDY-CHUd7X*v5ZffKBD0E1vcyPYZ#*Z`8(<1nf)2Pibt#QQ=(U7uAhS;w&+VVt zH$)aq%8{wT@61<>PT_Xrojj{w5o&aAy~mA0HSH!E3kWbU8z@J^$f#(O^Zb!ZGxvjurjR(<#*l4DOjjYSmlvVE^uHGgcbtF9lnQFCgv zk&)*=ZR1Gc8GX)=B_8ZV99e{aAXXFZ%MdJ1%oLuQg89J{8bsM3z}lEa&+;)K;kRj^ zNp?*Qi|97g@3T`H42<9U23DUu6F-zSJ{HXDKv|nv?pB%fWDs%5M`NMbLL#g(K6z~q z_5>n0{2FJe(s{MTNsufhEbKuX{Ras_wxUD0-})!e)?1fNPQp5T(Uz0uGA?duru|q1 zOCbDfq?D7lQO(7ICsO~qD4TV9Um`lw`cNYJGR+-}7<&=~#V?0a;I|@8 zs0QZ!N{tWuYC&q4+0w_(;_5;Bt^^(9?Yk+PTy^S^5{A0v^u}nYlbTdcY%C%!io3bNnH@I%5o zR5-rOf9_@_W#~U=^}AQVT#OBMca`AyXN8lR!3IslnRKTh zjC=(xxfncnS5g|Wyds7kfHtAdD5-(#Zk|1gA7%jNpTQojCDAw#tPd-Mt0a9 zfAIS}@lKZ$?o>H4ka$E$keE5i=eRgG%_}59y{bc`WFDJnft;X$g&w(bJ~_2Tdj#>} zb}c!%q133OjEIQ2s_Nd*klD$paaArR=8Yvsm9LfU;fe1P1{xv6b$=qECzKJ1*XwE; zm?z7eVrgY!mgCdgSh(DzT(1O=pG-&s?W34|6`Ww2~-RgBQufk?@r3B{st&=m;qz{@kL` zi-WSl+5N|hsI!`yU&(65)(5}*4n%B9q6tK7pnb!9ektJ<=9JU&H)2q>P8k&Z2a@+E zC#AW$V$n;@R)|+}JP){#o??r;SJO~VD2fFUv9DfPDQL~A)5v7B-YXq2$+SJr!IZH&%pSAL%jIp{!HPb z`FD!%f5Ji+Vz$MA!a7DPvyQQnp*{sMwqu_3R2M5$_aJCPEWNsK6`T#UqlsFJls z21X85-l-d(2zar3kF15@N-p#WB8E9$12XCP)9{MDjd<%(Q#o;2qj?^EsqCcZ){pC@IXy} z-`Y5bYo8q{W#{#DvuARxW@C2H)0#b5UVef_D5{DLfSs{j=n1_=C!8oQdu@P6qW7_G zv%Zi7tiQ{M`Cf{g?&?8C*ONccP2wojD?Yh_$&KNpG>sUE;+}F z*KCI2S~Zr>rJ4M5t&T(lJPS(}LK;<3ms^+4;4966^AV^XSM9lOfB$?-7d2y$J8}^N z{tyh}JfwpODz2C?55&!28_^qy(0VJYxscFb5V!P=13p&%W?^bmZFlZ>d6wpR`J|-e z*Ov()jd|YN2bRZCsEM8=mX_{$Bs*fHMxS zJ)fC1@3kh6S+}M%=*`2%1~fg*wXrd8Vq($V%|%5uHK6gK6kI<4XJ4da&a6NhXgDSR zd&>9fzBm17(SwlG)sK&l;tfryi;4!jv(6A?!S*~F`?CIGkE-dKuOC`y?6I%J#ONDp zkd~@QOw=Cb0#w_~3=Mk*EoCFI+l9_gyTO80lEw%~jZNN)8X9GJl=q>Uv;%7L52B#g zfHNTr9t$nfyOM%|$iBW9F|m9kY3#KyWFUi2r2s+-JUL4V+}pPc6m=#d4KCcv-g|4_ zIZ)xuLij=8HTl~wgzUN=H^?dOT?{~%R7t*1vjs}3ZZM8j=VJtqczhe zR_02Oa!%$_X0nU%P*URu1(iy1%N4&;rw&!#P#M^JJ-xZY(I^VF$#H zn_4U0w(!Y@+5I{qk$%<6Yzj*bidD=fGzVvCME-K# zP+lYJI8};1z5fA3rI(a51ZocuH}Pwvqi=tuIy=mN7yN~7<#68i8g5QHMb)fBuzb}O zu6)=#QgE*>UlfD`A=P%`R`zhtsH_MO!@R1K=~Snq-Yg_+X`$v{7ggdPUh?Tk*EW#s zTi)KT(ADiq(py4Xa*NeARQ*Rjg45))5%FJul9{isy24n>#DpJnXO8@0YVN;!KOUkc zPNDCFjOW*kmlxG#3Ean$`czP=-l!fQanf2LA^Ffyy=eN!8v%BAXcqMbV_j-)cii2- zr`{69Qs$QZzG$35nRxHp9`t$jFF=sz2`W;zM5`w`nAKQaZ#jhFHzO7rTy8AzFQgie z7qs;Xy{FY?4HW?grD=~UTY{x!r>EJdJ<;W9jG1=z%h-B1ndNw1@g zhOT7sFR2sNMT$6NYfw`mQWJM>-OB2Nl9I}ns+SkZd>3|Yr-IoiIjC#O=%| z%UMfg*<=t>&Z-T$6{g@pxK^zhL7)l@ET`xg-S4S&%t8TQlqMp_6!iN~+uF$a_@0n4 zH3XyF;E?^^gX0>yWH#S1Fc3&G8!X?P_Zlb*Ug#PchBUC_BQKeOBa|Dq*e#6VS)?q2 zd*VPy&srTtMZSGKYjt<)!Qj7*~Z6{2z~1h^e;%XL44VNyZf;PQwk z+ki;V7V!AP#&H4YLBsJr6n$jOa;kuq&eA|_W)}$E*&15!o*FQ803+V%ljDX)ec9aN z7$3GtC_kcR`CS|sNDvb8t-HIvpa7DbJkG{e;o`zNm^L;xpQJ0^)3Uw2yQqW7czu<< zbvog*GneC-o1MKrA?|fo6CC`_#H1oQ`Eb?Am4=|Jn~OrAL`9UhsNH~~H}h_O%rM%Fo>04m^pK!+rnILUH z#{-t2vJR0ejG+3y{&7z57gIBaUs{#&^A!zgaLwW3Mb$m#;P=Z#Ap2l2kIm}mjNFFW zQixPw;QYqM$;XeamzVEQ=Oy^aaS%yHU=@t~P9E6I44bxbBXsRIj6y-W#0+;Grg&40 zAky5{-7LkjGEs`2pqXixr$qZz={0dkp5p(S(OqIlxwr%XYE&jd0#+7yW&o{E+G}PZ z>T8wQf*qy=k}I25We7T~m|zbc~ahq1}%-?qB}iRa$aBv z1bz@ekzPZ_T(O8uGe=?>xtmSzx%x$mnGR+2%&Q_z?Gpy*{xvK3PyQ6czo&!;4wR0)gF)yMhCJ zd`M?z7Ii2oFdYCz)EehW*~M9BeS>XnmHd^SJu@wPd|vD5=e~7S5gljh*y}kmBJu~(EJV8;JR93a zwsg9@CQwG!?gExGOj#MZvPlbz1ZHOs=nhRNy68X90{06aCq688FWDG9J~b}mwVO}V zm#lx&4qRLIwcno-^6_z=c}&GO#&It)C45h#I5cbOXa54>BzH%bP%h>+3)4!m%vN&ent=FeS4AOzz z6Kz8uXz@;bokZlBJ4ASzPi$Wmkvg%Cxjho}L2&pLvDfLeYP|yvPGl+|+d;=-oy= zuyy1{XhMSArNs_%%D*wt)V2uW2$#EBT_H)dIBztGoQQ}7Km+gMIk399S2;s?J?7>9 zLtz5Zzlxj||92vLe#$=l(~vj3z?g>@3tP6GgoI1=A!@;LA*jmi`|SW%O+J!mI4uxpplR9yD``=_>Yf0l=-M*s$|$*s>dR$!!ayzbr=qe&R#N6L_WTR zzCIxnH)3M+4WS&1aIwp6x*P1VtBclK7=6(&9~Aam9(KQ_9`7MnPiNChB605u@LdG7 z6cw%Gr^74Ih?0qX^`DupUNqGur|e`CGNAKS!XPcFLK60S^`Vu{c>xfkr5XP(jXkss zrlv_@EwCSiU+0(_?PZmbX(?Tu^Uqtly2?DUE?e53#0;-Lk}OWyz~>eTyFms3?Tuk; zliSK-+9J38yk~D9o-g2{|Ep2kHm(%uIwpZ=khQ)p*{3*d;U|RvpW7uo8tExAXWZB* zB+vJB;!*e=7cvs0H7Z3;iu;rAfY1Ly!HJANe50$7$Jg4<(PB$_{}8jrn-*%<&hmy<}<@*xo5WI+apRoZk@sx!x@pL|pW24Bjm_yn@sXa)R$4&xLS9utEi9zM`^ zduVSkUiE5Z(b^&Qg;XivaE|9_;VqX1xZX6=3w<|62W2C>l=uQj*O&6Mm47bc)CRPOVgTnoysJv&*YqoSIjMX}tRej_u~g)@Abf z!2t({KvpTvQ-Pe@qav1sqvt$yW6aSWcs)FABG&Ow<4t5a1T;BhJ_gE_z0Cf?bJ&M9K z8LC=dn;F@+?s-YLzyFq;Z1nA0 z*#(AfVTf1wdkSCK=1HHcIc5Fb0m!G`(7MYOk67aM#E zc;&M&ygC+V-PVSZNjEn~+S<~Zo0&X35I=`gE#h&lr>w5SWRkavKb5gqXB_QEZ*I^H z>kIT3!r)$y;=?PcUZ=Pc`uNO8MmF>hI*pDAPIbObcZXrwrhII5b8Ts`@4iuTan5&E z3A-xPh8ALykul#C(9M4TzSq*KuGcY*63uvh4#So9@JRUZxa;S=-P$@iXDbDg4+zli zQrpOrDi0kv%EnV*;J{t)fD$Z*L1f(i45x7~^3cRF8sA@1{ zUH1p5$u>HDLqs;c>s ziu3_?;z1+-TM)x>vG!%KW?GzufG^K0#|+I#U0HI3A)%|?*jp6Q+n@adj>5vUBO>ah zWSnGWRov(qw4nV3GKoK7HRCbsLROPqR^gH7n10n-HA+coA|l8pebNd9wn8=(B1SD8$Pf`8lGNp!rnfNewqpYQ6XPge({Fljf}a=*=| zC8>~*>6_L6*&3jxSpzO&RX$v4K11yMpIvU^C!S8#%Q;0MKR7dWXNr`b0VI>s^(BFP6*_HQGNI*c{xBjioIv&UAH+6B+VOcf;C1F`I%!LbJBE zHO#6p>WUzv^-@jlw7S()p%DiMm*S-A7?GjD!GKO)un3G?-A99wSOCB;Ead^ctK(Um z&b}tPmkZ?!#^T+oXq;<8Cy4w6JxOEob93b`E~$)x7##0#Ep=}QGUF4>?SFw@T*>}d z_0{)QnswM1X-XjZ97RO7q6F>8+~yQ_NDe5l$cf*3wqtILjyF^t*|`AirKfk^O@!?i zLSJ}4ajR8*qo^1uK$bs~iH#+Y9~RH3m(+zveDn4*Ge>;am#>Y4^s_Vpj)b=saNe#s z!~da#znhT2n(ynYD&++(#@quhhc#9hJr1jMGYzeU*V`*{6vongr-)$#hme7bw759H zLMb_(b%peqC?|r8Z9|WWwT%1=&f$LC|6S|w3DrnUh`N; z`}J#;K`SSWdxE>oWnefJsP=V@jp(-eWQ38=Hi(sAK1Otm?XMdcaEh0QeUe;hMFBk| z;@XFSA;5A9Uac6_Iu1xg$%mmmUb^zoB&Ze+dHoZQQTR-qVpCFkMXfyPgVej6k5N3hi#LhVnj}Cg*Fzb(xC`m0E%kS}-jkcqrC@e^Xl>NuR+1QX;CKu9=&T3z7&t@6= z**CaHC@`1*WHkG-sN$|Lz%5C4tN>3{==1O2VRP}tMfN@-Jn|_p$axC#+Y=wZ-oj%+ zEx%EL9#dCj6n}@X{tN0n>L*(BT@O(wGL#LpoP(B1CkXPH_Fb6tCSXutaGZ=y?8aJ` z;ikki&`aA)$^b)2wiqbjcKgFcOIn9}!(0@2kE0?_fas_!`|R2fj~;M7HLR`5HHx|J z39->iBlpI}*2l!0jc4{6@!P5dz4jcN!n&{`{P&WrTH0RG ziAMS|?m~EQH}L`;h9?H-C;;kkX}aiJdjA%#$II)TRlv_a61;cRb+F&t&U+kBl2ZZf zfj409`(Ge{)z0CBBxEb<7r}f;(f{^686K-1i!VnYBtclkV6s?v`0lcbrKQDyu0q9L zOdoZBb$do5(MqeP!^HXp|DhG^HJdoc<^98B|BC@qR0aWD+!1(qiw(GOvFtw?c#TD#LG2np&ipKEe!r-RBB^pD5>v;NFUpZ9mizr-Edvg7!8Wm)QU3_qc|a) zWzjQ3M6{7)JM}CadpV10=pQ7Dg=si!TR}Azu;-$uG&7Vv9VJ>Ht#`IKf?~K9vcT$i3$VDgB3ih?YUq?uy8}`nq0-Mudf!s zV#vuZre($;ZI(I|f%8e7v7Uz|dhg9g8yr}*B)snxv==^NVy2Lh-Ot>dG|v0@3^{ki z$%j?MmA_xjo0U^0TA#8BqmT(xnx~+ngC`ZZy7b)HfxUY|28CMB>u;C;Ue>Y>A%d7PHrLdN(u)J7>h~3e;5w@gIIEjcc!e#11Ldt2V zx#`|REyC*SZ3&G|jrz+?6rE)k#keOj!S92|I*mEp2qs;j``#gF%w4Rs+oN+WUqS#_H{>b&ot-snvPQ7ZlLFx{5r zio95H8)-JZ?|9V%9m0XI2i{)Knj_gJGPbFXV9*G!t`GJqI|H2z|G^fvz`{olbLT{G zPq4MQH2l}N6wD6%B6aMFSUcKut=RQwdS1%SJtWXaMFj*#EDUfRsr>iMW@fH>THy}n zPiV?j^+qB$`X7HaD_~s64S zEa^K{CuN6*X=VrqwCJ4xY0uzag*xK%SRxwmyUX|CzPAJQ3%j<7!4f9W52vx7(^fpr zZ&1)UGJ@>x2T~iE+h!>@S8pwLP;8BALcFaCPl_@WM*DawtSy;v2KfN)L} zb+4sm*Uy*m=|NME(F2e**5j+Hc7eAB=wUs9ne`x!SxxzUE6|T*J07;2wp5l^9=1GR zBBhj#43_q-1|%01wb*HY88wxFoX&r`Mue8njmG8WBbDOCxOg%r*$p14Q!WRjWq*n3 zuc2mlHE_MG%*jdT#W^Lj-_$$5R#^wgxiK$n=etw&1p{poGQrg)pPxlr_%bq;7E&7c zz&dx+^nK^z>dWov7ef<)DG<8o-w@xNROxBNI2~Sro6Lg^RY}CcA}T!(4sFldf3WcT zj-||PDjuF1M^}^cIW!N6K^mDX#=d9o;VIk|GQN#a^5!2)D2_*NZx2MHWW~R}KE5T& z^O)cEy%IzQKrd31{_9ilpdl}XSSi&vG%e{C#x>RK+SZYgEE1h>;jUx+w@QxT@Lcz) zP@FIn24i|6v+)4`Lpb<($(z0yTXKDgu$GAMriURM0s#u4pSu<@F&~udQ7`~a>%-X2 zVws-o0vS#<9d`piFR_G?CAX8jJcFWKcel!`jD^MVEGZWhM(l&I0uuKRt>5*H<-FPP z=&8Jhxm`??ku}P1j8;jk>8xmVVj)ulZjs0D_ALIn|= zANd4aQFW{@_EaN%q#G7qC(G&C@|DI^1n)0pbdY$cwP?OFFljgr##FeWB?{6PE5qep$C8>E(;?6kpew0tpyp`>;^A^znjasmy$wwjG?dA0%OU?*n>LzQ9i5VEnMq%E3!j#*i(Z#(J+qgwq(ds0 zu>F3#fB#16Fmx=Zqm!XX_}`Sd`F|MlUMvIa3aXq*8WAX)c>t26< zQ;m|$|1y?=9}n;E))sm|K=lEKggBIxqL7*4sQu?BC8`ZNLdwZ*qa3hI=+X&WRlzTS zdJSn^xfQ2>J82y0d_9l(>h@lrD`GU(SKY9(|`qRhEcVH;s#1NI|UkrTh=V zx{@e_r;aFgbN<|olp7jT>vOd@Si-p~$4ekC?hwvB#LHS44cX7eD4@+$xFq6_G}~gY zjhAjCXPO|os3$|T%7@$*IX*Mc&_Bnyy7G<$YYlP6Dv@&Jd{6iG(?&w9h>zFLOReKz zIKr;0_9z!O$+;e7VK}F52;BQFv0xx`omk_-nEOo}hD8j*HzXqgL91;BqMh4O>ED}M z=wvuc%LBge>+9X5B+GISP3J0KY+KPTwJlJyHA z3n-YH_mGf8SUUjcd{-~p(C?Q8arLcTrHm->5RZg~F4ytl|3v{^H0C2Ey;R>megk5r zPeQN$RhekIUz<9e9ZXE1n4~peG|(_7c>YGk__0@6<5u}z0oGHr6r`t!AdhHFZVewY zg?tqdARkh(Z{6gnZTN44N&??j@Fw-^cI(+szR&r$E2!87exIWv0{D_L%m)*GqoLpJ zY}?N)dX?SA|9%USCh}zjra-($b>;t6*B4|r7BNziQmN9fA?j>wefhUkMekqx_l;eu zw){;4ScP0b+my5#%6>Ml4?20_0 zmSb1F%u~F#H|2Wfq*WK5CQcyJ85*b6`j?e!2%wLbA;{M<8Fw%VK}}k9$iU%rI5Hj! zE4+~{Cu@{tS(UKSJTuO;sBn#l2+idk+n%GozSWnfa#H=E8wRe&837zeSAu6m2J1`(hjI80KK^hlLpvElkIv`d&u_S$APYPgoKJl#JLD_Xr6v?1m2T!y|e{=i1= zvP)D0Gav=`zOqO>i?- zi(;YIdo{TmxJ2lY#bWuLi_OyzGVTNO5wO3nr&A}m-`r9!Y~PEh>^`tYJjCL#AcgRU zcmJ{9a|PBp^HEVwO3K>l@9Yf3XjLRE>xCaAxu$@W(CI#=FMx9JCp>)2#Vz|_Q*C{3 zaAU61?qbtwYx9(o_~-a;AwwlL+khP#5=1=31Y}N!*i9W|4)~(TgBnV$sKimQoaB}M zC8xMqpBvW`qA3EAt(wRx@ZMnBPde_aki$OZDd97ss|DEePt`#9=) zM&A+#foy{C_pa}5Do|g_N`>cm^HArAtvOAjFJ`~W5JRfZqA|HnR?<9tpgBsWg?w69id;qZEKGON)e}~f}VwR7)&$% zh3=5=^WxJ+Z+e*-4u4TBxamHyV4*b48)qDCsDg=6Y>%IWZLw!e?yB8+VFHZd{|*um zY;P|zs|9&21hIw?19f(+}J=2)shWnGE)-`O;(D5KH=kkUM8*I zCZiKtPJQ3%p-_a<*JrnS*62||GdT8r6@ z0WZhNp)SzM9|%}rG67615MV4(qROrBUmZL5rant6^tH)BS3O2IRn^F6t*=DUz`_E6 zvq2^%Lna;Be{UlrXG@jeo_a}tJk8EHPvY9mFte)%Q|^2%ZEaC<^$hG;@`){H$Wg@s zFXNN`t!&3W0-&yuag|{X%#(GPaBge?7k`7*+w1Gw#YIyiBOLZjUfk3^EX{RC6R?*T z^_gY4CdmGHxp-IQ+ysW&N(^o}3yYvcBF$~rUFgAlAslt9h4O^Vid_tq6=8lreMZe1 zXRR6gI+px0URqWhN$|b@hpQC$0uW4GZ?p3)PJb_YjVfAt>eNdL9G}XjQ*it&6@8|( zm|GKbCUWM{uGapRx+V8QB;b{ZKbDnUgmd2BhI(M>50xka<|Ny=L?=j`dSgtM0(zaF zt`$Gubd!9%G=k>gnUM|m0s_(>WMm#SlpGwe?G^@ralFePvkqsS^0Wrp&*q-LPaG=V z0~m;ZZUNZnVRgA4Kt^nPd$AJ89{$QuI>;o4hHjf!s2~~;P`!h#L=P8tXOHOK!GC;k zkd>=0K$;g0)Hk7ogw{q`s%T_v^%Ko^Y08fdq^3SjP5BqOBs5$$as%5lDJV#no{+2x`!A%t>$EEQ9d1}cX?l|C0Jk|J5@7@2 z0O0kuP*GVZPWLqMuO3sFob^ol-dIxnQ5(`rWAd#kvDMQ-9sm?DG@u&?x`2Q0D(#CX z=vSNM-}_F0Tq?j{_MRDcu+W%$g@x}}X} ziv`~qr^Sr|XzL8Xj%^L*g>k=boJYb8-d}9FR7*a8T$2IyP1L6(_j2+y`iqE!j+jnE>R`4oq#~UYq63^Y<47E?UzCw&!UDcjS z1x{z>;)Y15rk;G!+7nVFUYh%=qC9ZJ|HU_GeeJ7io#S-$!qVoT$Nl6 zaC)8P(h75l4tDSqpw5z7At3X>P{P7~6BdrECm^=nC{0msPS>vJ?DYM_C-`zuYI_KJ zi+#31K|P(WTrd=HIjM&Rr~~>&f6d}Jh^&;U$(cD8Gx}HfO+=n`{zoOH6I4_*u0Ow2 zM+*|js+o9j=GG+vD!s$bX+I#SCmdL0%D+69SYyv(gFht_sbilVJXqM3%PK2}f)OY< z5i)CMk8Y9)lJ@|6901kbsH%RqvGIGn6G2@UE^nU(29^0Gi|RS7?ZEBxlSVYIE+Z9q z#9i0m`{iY(;bCBVkpA>A?&4X3==T!yMQq4FzlUz|Qt^hAAU!V$wY zG@ES>e|c&pWY#ySpWq>OcEYzS;yGa)YNMmKZtfHj))Xpe`}xyul7h>CT0X!9NmSaZ zR4+l@uriX94uERjUK5~D00F-@Krs&Q4i5W4hDLN2=eT~%7lCke&su|Fvp#&^R}5Pd zil2nGADnd)7=5yYi>5G~N;6AeV_k&o0HeA8jdc*t>#n$?gO#~!nu4-Z_+uFjRRI$q zp>fuA4O&%}A8Al~TkS=ho^C%>Rszs1T6uXYX?7A_p8DfqJuw;8o^Y#{< z*FT_^8B{B({xhj8#D_l-toYFPvh~@e_ZAh=PqW>#vd=!ZlF72r5&Gz{YUe(Mz>DF% z8LI-bbMQT9(Z^1Dp!@Yj^GjC{d*wT5NZ}05`QxNeDIg_Q{LT2PhC@srC!rk~<@8zU z^~Wy^zA?Z6dU?0tMD~?-gWq-v8wIZ){szH~4Sz_zTq+XLlJY56n9b-3j&c(8wZNb2wibEfZ zogGpVY6C(id+x!5(z1%f*cWy3(yKui`br=ie;Z~W;)%T0<*wAb_a;%iaK!M`KuJij86Pg`w>M`&iRU$X@ zL3P?zrp&RU2!_qlCYbK7332iTjXYi5{oIie^HGt2r5asOu!N6y%-KE1inXfO{zIv5 zLIUd&wbz|Lu#BRh=>PX`O4>sdhUg0sw#v?)_VEU+35r!J2}Dy^*p?h%iqaEZM^vyG zqivTy_%ktGruvp%Y~auY%YOQ3CZdm?I=u6aggJ`IoyR8M;`R02^zE2}6sh0c&z}C- zS?QiW{{kyjM#VoXZPe7Jz~ax}>u!E}x^9=HpchLxW9J}7T1eP5d`NMIxB^v$uK*+( zWeMko=mmw@msZXx7BVYhX0KuTXyexz2$=NMzl-m>{5uHT4m$fX?)RhfRTE@0<#6ET zKeGVkUXN(Pq|oS6)%0+kgC;EMc&888z9`R1pZ(Q;BK&`DV)G+8&jWU+GpTzIqcy-! zGb$)}dU0O$Vv^oxi#KAt$~uyS;HmNo{#CI$YbFyX zKon8_dMR(Ad*8=snj4*nj;19eUGt{E(`XuJ@ifj82F9N^la-i+BXa`IgYBlK{@J&V z07D=nU9g}xjmOqoRuz)=U?x$tV_^*-%4)<+X3=UjFyaJN8L?%?ksjD;uT2KDOv6BBE~vqQ$@@wS!ZS@*romhl+{G zxnioUT=;LEjm^WvMA;yq-j;=Ab>p)9r#*{hHhb|=_268&+G(458snTU{5M(0Q`{Pke& zDbdOVyC0dpFv#g5qr+ug00EGwT%ETD;IVv2@cd)a;k`uDdzqM+`5ZnSwO6=aceqYx zYEEbDm>1WK?jC?`-5GO&-qXt{;iq!ek6kWIvU~5{u|%!N zk@-l6Xgggz0-pAt6~7;B)+~;e^Rct94Fc}Bw3!g6PJ_b}2)(4WFC3RBnF@UWEHZae zDdR#urn^v*wQxEm{v0uRyD*n#J0BsW(9Ss|6(e;vv8gUdmh?S;{<+Xok%2y3JBI8G zp+i?qc1j8k`eiAOL)3HB3{&F@obBTOxitUZ6Mpb?Kv;g$JjyaCG53XWo^BSj3{QE2 z#|GNKA3(g~8C)WXirw1Vi^!sB({UrX4V(8pj^=uQCDlfh=dUeXRwS|dB^rG&-}o!5 z0)n}NgTZVGz#R}@$0Q^GBj57*xe#y?7PTrY6pT-;i}pTUN^0XU@aeqQZfjFZO0td5 zF(*snfvRL&c*D2aN^ZU9#7;Hugf2gp(M0svo6Hh+#XE2g^nY{ zJebo2Ev-lYT9o|0@Kd=S-CwW(AQ>OzT_7;5J_(HZFq+ z>Z$mAlpRn1{Q8x!ZJuFir8EIB3!VRJq+;T<%FESxxD!e5ma0q`0P;wOu^tw->0b;| zuRdiSNLSYn3B(^TOnau31AS%yatl8E67g|$^L?zq1Qe%6TUCL)=Tt6|uehzv8_0A4 z9dcjC_z$*Vqm?3oW(9t}#tKspdy)3Yp2}#bCYj0HHw41HvSR({e`2C<9uPC2rmcMw zemOB=CJ{+SA*fSETS-(e<74iDZZzdT1Vdg#RVQFHXRo2C2w3A#n6_=Qj?~(ZHs(EFn~NJ;o|e(_*G}%0zUDf{Wh2Hxe>DjD7A}`yltBwBi0< zJ>5XMzhR(n#h4WX1p~i84vxzjg1tO5z94;A{#sUzojy=A(_a{TnVh>CZWgNZkOJD{M;?R%n{e!ih?8?i6>QKorT>=TDk~jb39uiX zWclBIv|(b7VU>X5^|Cm9gtS2`B79#Nht|ZUazPfZH_9TjSZLKc)bgy#m%V3F*c-i2 z4HokjftPjvc;Lg@QqSwNJ$v@N`|GEZhd;zPm$^cRuqq7No=o~pbkv1&3q&*tSUSgh zQIr8*Yr+bjfc-JQ;knf02cS~~t?unPDJZ<(2by!$IoH#S4w)W?qiXyP>^AQ$kVSsp zf}x@Beo`Ttj~oBZ`%!SjttVF)jIH3KwVjSmMBbX9jEp{xCwAgIF3!a@*8={-6CU+~ zb)5?)aH1U>_!IrH=(Q+LH$i&<4f!VXwN_9t4H;20Dx#i_&hcpBY$%E9u&1n9A?a_n z)6;e5TX|Eg{OcjZ&Smo3XckIf>- zQ^&_qCBsq0B`Mq60rI@?a4%ckw!V8YBkF*VE{Vgsb1PrfQB5}KaJ~a|v;-b`!B2V@ z`#57oXu04j${ZH+eMt!lIoYl?y2=o{8HzKbtNX9pD+zZ1W%LA;>$3&rGQ3l~nN9Qp z5xqL!dcB*_*WX6J(>%F3p<*>7pNl6Gh4qj@)&`8qNAc_<5v4@?L~}aFTk4b)9prQQ zoOK#9piGGk_fW9CWD4KG?Fk@CllJyn`Fbn3I@8zc@TYv`Ju4e!ktwP?zr=Ns1KYEr zI0V~i+ve5yY7gWA=Vg{LWJk?^VuBnLWfyb52iWhc6i1!o4Kdh3gMPO=?MHne`Ij%^ z%1Z>-X>%XVbmAJB;bm z!ENL1R#DD($tbzFd~DRy9vJv}^XB98DeK3wH+$s{S-ENRGvMXS6uJ=2=IQk51p%%C z^g8XCcRI_dY2xb(Bp6seJ|2caF9ip)M$F9rgp9kCPWtsqPus4pfg0_qiY0VC0FRZ^ zalc8#+`Nrtjaf(&`*O^|)qZ)i@cHM^ZQl1`Y8MpN_mI${&^O`NrlW7#O22ffPZI>v zN=w)9aoM@wZ+|ZLkTn0HHbt2uDS|qpYAINZNv2=(@-E7}8@N19E<6-UZoUkT89$t0 zFAl|oAp4I=>@5iAdsbG)m>&(SiOK9wqdIX3l-!l4O3}EG&BU^;QXzv!G}e+UC%J`SM{;Z z*F{1SwX)u-yB#4NDrs*1NNw5{)(ep8vHAI2E-tplTMM1iDEJJaGqe56v4An0*pju2 zUqcj$TSZ(lst?d$SuSsi%ee;!Tr+K-ce`LUk;`EEf@`MpMfCCVO*oN73lR8eYBmp9 ziN?o`6mk}UhY5qx(|vpHEt$5^YsE#z-rj!gc=-RXpYA0Zl8h0mT^(jiMt)4S0=a3l za8qIVNP+mhdUo6_E^hGX_X!ZoMt!E+Q2EM*W1J}Cua5KVySTl5)d>Cs0Mz9GRxkbZ zm*1_VCt;BC49ruxWLt54V_?r}{5yu`qg&;)4leQi(&;L3Kmx%KcF;R!6D9K>{AuE% zpv%;cc4lVt_G~)AltN}b)298`*NvTJ7g+i<%!OqXok+r+Z$FtxrHd{m{`d3grJA`d>5@NND#DHrlWht>t{H7y4kO z3{Z|pn1X`lLl+ypMx9qmJM+1LdOmO~i+`8P7ox?2ZpzPi;a46O2=|shkP8If349@S zq3#yTk^@^`?d~}?Tj+lN+?&A$sF;Sf)fmT~q>qIlA%pVaBt|=4U-+naH`zbm#)Y@m zE$So&_+T)H|NCr%!R%Glz&YQRS`K&i-u~hq+f{zE0aSe&wAKjt;Xmhhck<94$M(W3_&uTu;Lf_?m@os*gk9m4&Lz6Ro@`Hl7W{}Lo z5<|iC9ONXvB?S);Gx81T@%Ae^c(^?q57K51vO|wQegyFGO&YjQ?+&NPfFZZNnP1I~ z;g{dh2yLS2vPzKJ8EQM>3c!i`UCBfJ(R%*#Nh&;~xkBst>XJ-#e z_>t2``I=9+z82u+e;4a6n3Eq#A#b8XM1rCD`U-^ku>Bep6k_Yhj}-1t@cCM7t|J`` zAC@53!)YAN)?F;U1>nn?Ij3Lc=gL`FngNtknWBOo9v&h2(}=svN6yT^W(#yW0z4uk-LH0`0msc*vLrp4LEsxhDOQb=;6&sI0&O=8R z7^3^0jwVk~*S31h)59T)bz@%J2$A<1C!rSPDtOU{M=kxdgh$SG9hq@(YDfZZ5$5vn zGW-{_(fciN12-CRqu)&?Dd;h!WxH|hpYO{}@$|HEQO!wyKU9uzV#^Fi_h@|h&}3p}dx+_*?&u@MIE4HX#X5z|e$wNmYuaq33^+^y za8)C)?6;GI&SJ}tA3)aPb|tw$!`=?$Gy?qCg+v09wARSrS7G=OzUxdNY8wI&DUyAU zsj2NgK7*=>W`p=)GqwQBHy5lVllT;c+FsmjVh zs%zas;zrrE7ubtjQx=229KnA~?7mHIN+p(b$|1aTM;> z*0?;6troBCDnlT)&o9cQ#js?*0r>L&lQ@+}+*Xo#5`$NN@=5?hq2( zJvby-aCb-(+}$O(W$ydU)coNmRb74i+siYB`hn(K&us!;tMs%Y4FaOk) z(Y1ZBx5|$m`Jq!|`a6wdWEox%z(P|))5I<0VG>p(gB3J{b>hD)R+N!~c#d*(YBDf} z`*6hK%*6dyI6Mxb#?ptPHF43UL-XkF9qQ`-wh-HYMS$0hj^dM*uTM%g%gOBm2qKQQ z2S6s^et(}7ByhEqLODJ{O)H}G2@+xz^kS5gqDfyhkD<ccZb6WH`Cu0MJ>xwS5k7@|Mc$se9~L>HD`)6Ha6$?ytl0l zqse-U;*$dpkqiD(ndYd|=l9RD#PZ4Bbo2y%{shmDkm#s7Wfg$);uM>-k)6G=u%fiB zMJ}o-{dpJ=4MahX6wK1crN)SFZcWV|-O1{JGg5dfE!`?dT9Ua?IveA)tbXopTC%rA zz4C4JpJZECf%{bTyEjeBFwVarJ8`makW~XWeX-aEK zt$|N&OJKwzn;JvPnFO2Ulp%rXp|VFGKAC1yX>f>|z}sojBe2d2KR{9foOkLOhkLz5 zLSXY9^1C~o0VH*iB03E^RW0?TY`(;TXb<;lXlMw_qcVHd72Yq2V#nuuhfDus! z2v9k3YU1WfQp98p%wmFER?A&5dnFBzH zMFtZ+{fnF^7j`-&<}-9{$n2Q|Oq%&bIvX2vammBpfmBw`N3B1@!@4;tm7*tTqJ?AT ziQg;e5)+g1u`o4u5A}9WnR6R8I-?`sWyf+YTlzROPGkEoAR3vDR|I9O_xCQbu?Bz% z3+}|(Uc)uvqdo2I2sV~NR(@76y(E?6yY!r$zBRuIX1ZEeF;V562$@-|y8UMs#JjTYv$+n#>lA!j zRuJ^RCDd>dspF}Zonp1s@0rF+@&FAU2W%r(_a%bZvW5mrPSPb>6Tv_5?(W|O$C*rvT4H!(w0ZfmRL|ZbrXKO;fI}BVTiW;-SGEo=IEc6YZHwJDssz%Y__l&`uFynB8pHnH zRL;JLiErG#Vfsq$MmcdH+2sOGr z?lHs!_&kp;fD{zqtk0f$F*MwmO@orZluS}gr=t`y#v8Al$BHko z#gIh)uTR$E>x^K2sBGpCPZ;RF1jMDwv%cUJ`tv`mhkH${7!~x)2k>`cYA{etXdQM; zuOTO+fMKy?c>ASDVU>(`+JMstd%72fl4l1XTgQc&xMupQlJdHi4NeWV0T#b#wk^xP zzSjUl3lk?NWf>W1SJ%j~mdOc}g)k`BF-#~up~^+r(EL3%g8(Kj`5Ug4(0eN8F@=2A zfKXZMbMCz&`TOEkej?OlK|wrtSSLy2 zBw)pm;w?E434ma<$5OnDyfo$lU%c(?>H$9!Dn`NiiO@nH<);1jCjE};YRpAoJF7(E z9b0V)J4@1MHb?F%|IEuP^Ml*igc1S*r=>KoGN0}nxoBF4DD~dL#tBFO@!i1msrkV@TDt zd)Vd{K;Jy@W@>7)u>rV?4J(s<3WRj_<;l;1g=F~h^35It9^tRYVs~X|G!3^J`>Z-~kaYZt``1T%K61n45X$0yFIwi~*J1g(b8H#RCb0bvaG)pf-N~2Z+}q zaGM)HWSD%O&SQeW&rN>=F7&76bjEjD=~bgMm%S$opMKr_$@$dH*jt3*j_9E>Irf2( z<0!y#E!rF7-K?lxVttCDkv1r(_WBxde`x7`iyta{;M-YOET6H)hp%VM*Mk0N&0NX( zdx~{)q+-;m4i(7&i)a~}colHdte(+nKw97ylZM0t^grVcBCd`-Lsf?kr0C;|hnj`{ z#OzKeR<>8B^-o72j^wshfN_W?8<#@cOP(mzGzUqdiKMHzW9`=I&*^9uxrpS8{o)o2 zQnj|WQc(D^Yz!0$ZVtieMI^a-8Pnl1_rFpiP01f9B>N2(n6*Q_co^ z2E9EeGBSo-I)z)r{`85wPk_9^Ak5>ii=iY^elXXI3Z~%D&|xE)FaD_R?G3M`%#s;3 zs&M6_?ovlZ#$=wHe*`ndM2#;_Q-o1CR^LiHc`Nfw4LAQRMGUBpQu}+Kc ze}6OZ^71-3INVpx@%!KW&m5JXUaOZ!oA~QiYvE@jN9$Te~CljI%E%I`8=qNROSwN(TX?W-%9356# z19}?6F+Or1MLf2}Rgch;N}Os}Iy&Qb7`6cHh+;z$<$%`iQiJC`lJ#8FwK_1j%7ejn ze$S199{1A)|E9MGF3Clor+^#$N6^N#6H=*y zCBe=v`8dG!k2|)qD#T)AURs)M9(*I@HBEfm2MNk13$4;p@xd}vU%~G0P~YK8LezzX zrthx`RY>G??Cq_zGo%s+2k#gebLM~dM1y2z3ZuGw(B`%~OlMtWu^<#t!!?v_rId{< zmK9M~i%B2~PNvfp#k z4;H*rsGsvwZXX z_CQX~8CLKDkG__f-b~5e%t~7(O1poX=M$tjx(-58`o>%u zRJ7Qr+ZgTFEoIDB71u4z|1N+bVTJHy${JRw$ummMsv$SbzLtjP!P9;@%#mC45^{*; zBWJON!?<>KT|)p6X+JcS*TO<|ZVpKOp9_JlZLs(+XC{QdCxa2e6pE5)GzsFEBT-Pn zFGfZb@ikB{hDHlZ1JycU2U-74rVraN$PJ{HeUe(ZXf)rbM$&AG;ES6_2FWTV9aL4p z#}`dDCLRZ)?uiEt4bO#z?!o`c;L9l&0HQ%Di>`+ibm1+EclsuDIa40uS*(K&a{Qd!@!Sv?~Xi769+X$Y^V$homG zM@!55aF3*bW1A{`JI%C`azZbf2-UtM3FN77z$K-ca?axOAQk*ispd`?60$OfOe#?Y z011UT{J?*rejyOaX5vf#J7yldgbeQkY#?8&m$f(Gc z0s+oZ?*A4D@4wsioN!fhD~lB;f7jF}{koD`X5d4|fk8V;LbmUnHSgx+AsjK-G&@k# z9~RU1{@!4YyxE9If?@>3*UB1UhO{0RR7!O7C<;w|R zx!*q#9*)04@nG2ig+Ja*0O@pbPInrEKqAha+aZD)j#SrNQO@NZ=*GadC16-M?L#mF z>MVf1jq~>V6#}v#{c{RByy;$>oistUnQ5POTF)}#|(bN5#v z8U>8-`?u!iLU^RDbR^vOG$hY@4ll2vD9~V8v*f4mgo!jF}c!g34nlYarTLOXviAylNx^>z! z{lyVae;K_^oX$?JuJHQ$zR-P4{kj-HK%hZ5!>UX>JN6IKD_1_}+kW6Tb_>VgEGsiO zhr+&=nY}_rn#jlI<{jmJCP>|9-J$AVkE|0GY8~6+Ni=p#VL=_p%S`%#G^LDYt6Z#F zveA48m%akXfirSx)h^gOe&kk)cyT2bVo!segXN_u=}^Ef6$|tOUgBOFv*R%_f0p#k zZA_GqtKEzGLX_+meWGA%wLXyYwy_}zRvvnWe?ZdKueF~B06vkRTdwSUp+d5jt@@z5 zb^XO9$=?Y_Bv7x%nPrp3z^g0HHi+?F($p6kR90(vl7w_=EYg2o2eTxx_2rT} zH2->VG4^h!F~*%spbi@f?8-P)HEBd+s83HYD=FvX=f59>V|cH7B>UNw&*`6E)^9LV zR)&7f7P|X;T3^qb^VQ!U08~L!)ksV0d~dc0caQMDuk7-v&t~r9CKW99g=SWH@*Qt= z{NlmH{81XjbrWiq;rVf&BHt@Vo`{vb>qN`n#?_^*(_aL-=S< zo6g#HDm5u1Bj9-Kboap9X-KsaRF>=mpiW;XDSi7EQd4vC18%}G#C-p~J$M~_RO<0aK_je8>`yvb(ve#6Ve96X*=?Y3diiZa7f!U?5>@HoteSNT-tB zjgxm4$1o77s76U}a3lyw@y#Jx$18Y2FwKhD=P0OidzSOdQ}Xi8A$iy) zCh08!iE|41Czlwr0O2&7HPZox>CybP zk;gJ^OlFnRqZCb1L47V1W;BJRHLvtsF4BQw$$l+`G9v_i6pZxiVJ^m)V6f^!qVJ`6 zp~;d!XJl%k5IQKkTvD`9;+eN?Qf}#XZ;Z97O5+n=Yvi2x^U)&kBWGnxO7ov7Qs_r? z^?nKEWTIf=Ss1cutY;tpjf>aH$$C!V*W{c7W+XBWl{rS_GKCu{sXWY=EBg9@7DA*A zolrV=+;v5p_Z3}@->d=3*BKdAChZonW3q$1%W^jqfeebbe_+6w;16(&HUr1#b%{cg z!j;ppGzo|3>kvWwS-u830;V>SYcB#e$pl5uH<4>2V0{8iiJ6F96e@=dT)Jt!pz7Yk z#ys_)ro2Kl`B6qS7Q@nhE*4yPSr>kjF8$AioflwKWI)x^tn5>djcQ#ry)UoOdw%*t zm>7>11+BMON9>~U!PBD|q%usV3EGHB_)LX!zfA}9By?-*F#5n*o5$E76P@0#IF3Z2 z`TxiAV(U=Af*%GcC&pSMbhRaXUZUUOKBGez4-v;AEcU-&1_sxQ@v&nSRZ%Yc6sE)D zicS!*@a|5wl+;z26fr3deNZK>MbZRYCn_a{H7s*~#`oAwzQo8cBU3>3sfJ9N2)8BF zJR^PxfA_2kp7*sIi=Bz7Ltj`IKO!!DmPpf9J>S#y+x|uO=t#wq)36PnjLi4AB$rm{ zL|};XcXvm>y{tE-0w;qSaG_y&2Me%%>?im;J4!uZcLt-RN#^% zLelcO!LT_L;;{?^)PC}uF2aNWvZ^BWNteLTTq7mRcW%L_UkGFxI69SYNu*$H404Rz zP+SIh7t<&N4K@2+ob!9V_mx&hXZM?%!a~5cGMti<*}#FGdvGzm+#3JUZto+VhC&Cv zo2EIAA&+S%iMD;*y(ikQ@cyh~W|e39P~lG)>v-uX8F z*}yv#4-1M#Av_QNV&uh#66*wMph07A3=MxGKwh3nWkQPBSv-Ny-QB&{tervezm?Vu zEnsFzrp*iIV?xY83XF+ECv9>*Q4?TIAe{|ahsZN>1o(KZiunoukq<+Kw!g=9libD8d@k?x!FtGq*1N6Fnqrydp)9NYpdEEEY?#A-+8j_u3K z<)zudT^md zQOVKY2JGj+F6MWMT;PBv?5w`E+Yx{}`PdZ(Rf@+LIWB~NotK{4_W{N!@QKw>u!NIw5=(~;)IJm%%QL9)d<)*Sm z;r%t-yX>K3Q=1|p6}r_lS{+&a8Qc~8Z$v&RAmH_po?cz)K%eOFP#+YZnYN7MhAqHt zykbpXAqeb%T6l{VR?g{bEG-RpcZJi^0GzYS*ytlVF9p3M8d4q{ud87jo8@eMOEkll zRL2N6&D+UtvWc;=lfK(@#@>ma?~w-Yj1=))hO8{Iust0`_$Xm(i#(by;45h<)A)ti z1K16Pic1#U6MH2L>WXHHwzk?$*+`}!5pU!QDf~%4=y*H>wXj>)GcsBL2DFMl{q$}& zH+t^f!Tlafo2%rbeqd=Xhg(Md=`yg zew*FJ{uhnTt;!NG_vkax$L-zz8PI3FVe#|>2J?q*pT5zravrMClLZ?SB}KvS^C22g zz=(__5SjekSg~)+aF{_JC~S#{s?G|EvDgr4<+@Fk*M&!zNFRi7VId&gh2b5+{?pbh zqLY6NdogHH*oJ#aCg1eh|B|RL9WN`YOJWygLT!bmS>1|Me~W59Q?L}jzQ9kaxp!=l947A6lreKob0%}>o|E}7;mFvf7E?;MrF2% zlb82jiFhWfkqj=sBQ)+`)!}6oSN9G&h9lE>8v}iP2Wx8^&)bfKgv8}#BX@UifSGHz z)%yn6??8NKqgUJV09cgIeeXJzT0H^e<$ilqA(x#0Enh9G1=pDdEh0G0?NcBwGUVII z4%du5%hkh(&KB=AK0s)8+}T|`sZ7mdtt^_YZT^@jvJH=z*RHiDfdP@A8!Z3#SA6pW z0^8n1{$=Qdhga*hY<&hpuiF3`_?n$C^s+Qj0PZ3#vp*U8xCslQ->J)KW0Gg+(d=2( zKSEz%On_6uzQ}NYKkeg%N5CcxXUvN&HqtvB$;oNO-adH0OHTXh5?Zi%vb1!5d~EXc zH6akFF?&&pm4;fIGg?6@C$xy0Dak}SGsGx7Z6~YX>{7wM=sSD z!o40yAVP5W=u^^16O%SR|7UM*_4;^SC|A0E`uuFG9ux$m{)0TL9z+=jsHsfpg$7g2 zrxt6QozK4@HgJ>>7S^;#noL%5APnQy4-=r`{6iO;uqvX(Gx1FITchWJ!rx-OB9gIn zh5zocXoB|PgY3mc6IXC(i4|Z8TpYlt$pifNSI4)^}5MCwg{7n z3BfAg3O7vLXGBf-*R@X%^-hl4skxfsDWVcN(CARfYtVxoLBiu-OA{GUaP4!|N4c(lKBM*DK9JQzm>e0)D6gqOrM?oYHvr7M2nyU%ECM$nCxK;gNa!AR73z1Uop)likF=cE16>(bKHf6gBaF#s^dT_Rtj(`y#M zx#4u-%jLkf71D=+KIZ01Bmy3Gh=rL;9hj5{lOmlGEsL#BeL~8_ zfi$2CmGW%OfShb2>sy;3t!xqYclgtp0`U+&gI{~45u%fTHfA< z`~0#P8CfJk4CoSfrxm*QXKPh|D#v@_zf0I?7~&E_6@wZW!PE(AI>+)7NS$|3A3bB& zmp))xASjh!3gNbzw8R{^prgqiq_~3Lt(4D|NHSHJ(q0Q%*2fNw=X)!m&so~@Zt*z)}kV(t!m+>t_An_ zRUXy;^fviylh@PJHCH=ne+ClNikvu;Gfw*5Z@%65r|#`)PCgi++i9~dmj@s1H{B_> zUxoOaVq`3^{9~a`(#^Ca5iHIvV9}YlEZ zKlN9rsNFU^fao^8`bMMG2t`S%ZzIqdVdrx39twkj4!V7<{kijmjzJb+KU)a6JeFLV zy2u^nb^rXBoSbrS0NO1V>1bL;h62A@S<$j5#7Rx_Am<|$Ec)Ne$fMm|v3rFaa)HEeGS3mhjOOCzXlI}_r)@hbu4&)_jzgm5{{0ZwpqZhPFL-e zF_&j-Y&`DLv;Lzrs{dNzkrGx-gBcA0{N1$nqO|tA_AOG1YJuA6PkGr|fPsaA%JT5n z=;T;%b9ouK)ooyX@=NXG7_`p?XaV?>4_EdvF_q|OwGD=gJIx5_B3|2(k3A&=QOiA) z_&)LZ>ANcB0Hmfzc=2~LBUhU3xnYycuSj|d3NzvYi66OP-6_^wlmX9SDf~+#ni|k( zp*6PrMEb?(X;0JmhVla8Qm*HF#mB#$zs%Y$Go&YGa15A-c!8*?_tzbi9#T-<#D!in z?-A26I?27INcdv(QeYw(UsrcYQF23VA+Ba{Sa7*sxyBqpCMzbKB}ECEn9KQ z>4*f@ut@sCi2+3ru+4Ssc-y3yt{WZA{`a;t5IC({NWIDd4ZuNznwomRTPLuYUi|le z5BNO9ip$1W8*(UxpwGsNddmr@Mfw|wTi9!ojNXhN8Rlb5JeJ+-9 z3@RI!7Jn`QL^hBrt0FyxA34Ww=yv>tUVrvi6wZ*=QW@2{(As3tAACX`v@G8#85CSTPw^Ap+F)|ohG1eM)@2ts0gTX|(bX3u zgyXaxcRITi`m6>#|u0Wg4|F#n9xhPR~SuGA$fpzR}Oo_MZs)Pf@@sUhYg> zSttGdD?7W_7vQvb#O>va=)A6{@~KuBd|yWz#gbW`O8$Ie#o}}#B7bvS*d<8m?0J3g zK1AEf+3BpekV23#wn5TXz4V}17T^YYaMaDt)Qd16;(>uoI}Feg3N{aa?nTfDOK1ux zU1xR?pQUQ)BUmA*F8nQ7d7m*O2Tn58v|vN~PG1tCLAUPwhb6o7kEP^O6_S#vw&$Op z#Uig)1272a|Lu^M0j$R3HC>0X+JpXeUTP}4=>0c$CYi>&yI-4ZZ_?PQn+oBQnUVNJ ziC@7#|4scVm&zngyrD_J!;klSrtki@9lc-=JLv|@9D1e(rK~*^R&8SqQte;A8nPmS zaq*dcCWafdVL~de{+6h8v2-qEsem~VIvp+pS?_K$2FA{$DQPb*m?h9G&0?dNnSPX2 zd{R-{$okS+Vb%k*kXP%^ZkK({pd9WPOH)^&gf|Rh62T0aU;EyRI1&}ZbN**v6$&b) zL{Uak+f=jFaw1odsTw}25KSYT6rW|OaxMzAVfG^d)tklBXGE^1)8V2gSW!WtyZ)1j zjLa|FLOog81$YGZ5$l(iU21}kHYgnIRGb^Jx^}rqA;8nAPT5AgqpTtxN|zr?LHJR* zTPx>h+LMx~BJ8MHkB3>Ok;k(NSW2|WaK zhWO;NpaUX2x!qNXmW|0!D1X?h8$x&1K!Hyinh4Qf{ePjo`b4AG8b)#y|8QEiT%~!S z+hX4gHp^4fDEa7*PAsN%;Ujy)(}$z}JwDa}4e%eJ*P$ZI|MvO`co)jYpc;3@fIB z~Z)EM-z}Ly1E0SgX;{oReY=ax$!mmkJZn zNwshoxSnPIx`F4=z_eE890)CvFDEF0u!@>^TmZrsg6|$?$l!daRu-*IJf@xl4~Dw% z#HkKF`cn{yUWUQ@t_;+*z54!MYP8}E)5rPDywE+tmVO+PsAuBk_AnX|w7QC+Zj~U% zm7n$v#wcV6fg-b`ll|c)j1SDaqHoh$eVCp8slCh*!cb{x%1_l0++AXoSWxgxkl=fx zxUa^+`RE?AebGq(PW@FF)j_PAj~Pi2eAS1Ifx(C{9pe7j1ceZM-r5x8>f!EgXKCr^ z=_zl#Ru0^*1+*=N4I*L$2UxIu6-y%-AyXv(pz&d6xz>Nr7xznES;pNLda8+jZt`Qn z^P--&DS$hHyERHn-)!5@OpJ}q^^}%^ZXKfYmXV!GPRc*uZk(Nnuk7IBXwdGo3Y(Z) z!a zIsiYzXWy?KZ+kC39mwEbi_M~_wE#qC^V?+7kJ7>wYK$ZIHxqHgn7H!;C0jGD^ld4Z zn8U11`&S%JN<4!<>lw6}#q+ng8dVB>9NcQ+g|BTnu=#hbw`$D9S2UTb5+XC7{tP*9x`V(irw zG$wfMNg1w=G#A(;xGO^)qq#?U5T1Uk>kLm(D`p=bS1e$X4t9P1%kSl-wC(n{LT%yO zl-1i?*$qu7@kzO3su&+#9PBL%2Pj^JjyD>pUd&8sKq3N$ZXlEirNPxf$;=($Rf)%o z1(_Y;?nSxEnDp7&t*O^;6-<$$fMLL4f_{jPU$8$~_py>VdQl)7D)%F7fFon@8wZYuvZ;J=1p-}FLw_@sa@eXh~EAL5rmq^dF>W`_&YlBoG5cgghE;(U!oT8*WqhO0*2SdZz&4>l4 zfMI}$+qRMrG+J4&cX58vxp2<9@_QJvgvx8&rH|%dj@%lGCQB9>IW@SvlG9trCNjuF zPR_Cm^fY{SaX~RLITR2WotJ&bj%RcV4UEQsL9Pr5WDferd{^8lfz0JOgI08^Gy$u8uR?3^HoR2|!{y zKdqlh1ku>O;>&)_f>?aGd( zy!;?TQyGBX;UF@g*xQ2)@xMVaGGvV(MNl#* z#!rYE(+df^4a>&kDFcw{ z`eIO!n{ZPZiZ%*$QG}x@cDJ2=2~7e?6q=pbLK@9)9uP{KBLOQIeOfNgwBg}d!cV|a z==E;7u}bz@5AgVCZVrchb|5z~?>Oq2mBfwa^m_lZ2aflQ9D9Yo^x8ue*uU(mT5)qX zCKCz=V8cg}G0)-Qqjg3tq^ooF5f!+OMZ&{;PUv%O%;ZcWq)y-(MigeVjY)XHgTp=$lEZe{GAIqDqy%_RerB^p8~3c|L~}fJI^5@ z!09|*q;frbR)3#LnJ7=TjX4@#{HXVbE#;{lGF3ZmX>hRL+H3wTIuOWqaDcwPyet~k zIu@5~cHimpYBU1-x;d`agl3xieiCx=Ix6u=PKE(S^O$iZ-a%;ZwSeaFpX=pZ2g$u1 zl?siB_`2U<2-=*MJC>G!1|=;sGp8Mf5-i5;I$I&ZDNoleziYcuUWnMy{heJDakfo+6dzve=mH*P2`d*KZL!=~ zg|B}&C=F1MQjvnRTL>_eerp@DRNksMyU4mVcfRPWE)YCD^2p1tdT!NqRtE+G4|D?H z&h7unJ79p_A}L-Rdl!obidTOOPkeP;RaZt{>3%F`5H}0z&-Ro+#P%*Fr56@pxxZ(_ zKy`{sC~eI6za#R^O99F@Q}*uAJL zN|-_aCvkN`X(_2K*_G7G=P-?xNV!lGd-+^@49K^$BY_bUe>HmvsJsR`BUh|z#pPve4| z4uI#`><)p-XPa6Wb@mNyiBJK)UZDOrKPka8I25T+z`5c{c)p-1ts6Bi@Z0QPH607^ z7(NL+viE3*lLyG3|9kflu&t{&!~~A?F>O4eb}?$JKtDfaseWsMuk?AjQ1$erq;~C{ zhcAyTvi+fCo9H z<0=+ev}S#JN<9O-%I5|1I)K5SJ@tf+t{(-|sUY7X(G(_1hAWAR>;cH@$gATCPX@yj zx?beFAorsrQeg?SRLZJP_j`7^fKP2C=g2?8a*5qdnb7zGuClEix^;%TJ6%tBG#%Kg40V(p1WeN$cVV)6lPKNO!`>#+J3}z)kt0 zuSs0)gW52y%h(rt$FMlVLB6fQ;+lv-sWBuzLyCgJ=wHTf<=BqU_XpsgP{?hf`K|M%PZ9J9|B#Se@i^NsuSy z@+gWD99Gwejg3{^-+g&m;zXIR|G*9_rC0pK^Q*C}zWW(`IGuM7yqAdQABq02j&K~L z2YyF7q>;Y3VZ4vDkg@mW+BL?GK6Jb&RzjZvv45>F_a?FVS3sgg=lN5BHw|< zxU*XTG=?)$^R;GSv1U1a)y3#yJG4fccuu->-AeMPC(}Ja&Y_#i^Y~|vj*s2z`KM=? z2LU(^SfsfRvSjf_~S>BSx|NuBx_?k>3p?G&b>Zh7JJ);7Msh(?*SvDz9_zof3eGVl#WC6|6){ zb*|h#^|7fb6BL3Vzx^)->%l$XWx%FU$>~*n;_0avl3y9p?R{FiT2k`W-;WJKM|HgA z3I48u0r7o*I`p~RASESbg{Jx6qGIGf9e1PDh?6L1D9*y7vD%u`4x4#4dU+bZ8ztaM zHUy|qDR|`OT-KKmsl1quq-?l*)DxW$LCiY0mi!=A6vt$?DU@EdVQq+oc@^N!)rcbe zv9J)nQL>z$G_h`Q(jxw zGY^l+YOr5wqE*KA2HZkxOSytJVqzzur4Q>BD*3&zFCy>0jTQ8nYR*6$yfxs9h)rDf zc>CY!@~!Ltx+D6UQtIA1 zScVn5JmRyO8ovR{t1{9ec;J0LX+BKhKNO@?!}@XcddXwBhSePy&t(Vu3f9h;2pQqX zgW=L%9cyST6)D?heRI*QMukQ-^U`V0tceAlX2R@jM$W(}gu1j6qlF;do$3|GZiKZ@ zv`t4cQR|$SAq>Pb8~9L5IsLo%pa{S_-(2z$?Yg9~F_z&?R0^^px^7BG;6d zdg)6*dp)nRCdbj_-Hnz*KtXcW{<#T0{*UbP%f-vy6v~;|qLlW@m=CEHYUk4vMtB5z z_$0;zL41O!^e-e4fU#aKH_OW>{^y7A`QNNLuW8kApFknRB>4pj;Y}CXa_vjRsMB_*H4AAL ztMwDxyNZtkp``J5C|EMhnH%7a^e^oLq6K1rYiPW3Y^Q%!g=T!44EI^238~ z^@*z47crBmV2HUAsgkuGkEr+C#h(T>9NnpYVthpR#i;#2`>^A23Hhgi??Yi9?Or z6Z}x|aINIZS&WJ>0)e?#i((_|r6$T7v<6sjY|42z|W9NMzWN-E0!b==x zCO;~X(~A>1dX;9~o&N7Im`^?Xwle*&`F>FJYH8LSqmkU7li^a zl%oa+@gV2K>h9a??Ab@|IH0LDv7V>m3bLW3oeZkuLkPp zQo{Y&9b+t5Bzff68`8j(KZglC{Ag&Z{jDudLER_e!0Rwp(=hYC>~wB6Kx?3;zV7;A zL=4kVfn0vTL8)+-j2E3$NZV9Ry{YW~Kd)X*cT!WsSo~HzV=@jqbq)OieRXq*kxXZr z(lbkzBE@eEfH2A-lRPQ+Zi&!z#)ujUV_LWmj29@a@{Wjh4Hz~jB@}1sa7+C@UH*$U zU%h~So?l}b^=M^UVsg>JjF{YmVi_UvLmmq7`J(};>kMcN+r2| za5tnEeacmHC{i237aF?BvqYFcNgJSM8Dd&Ldk&Ffh{`-J;1b3>G5;n@lg!FUFgt{d zT1U*qV97)o3+PdX^BY&G@HL045feDp$S?l|oc}=ooUE8w28FsPWn;2AIdsLl!U3DJ z&Iig?*v*ER$$Slp^LeX#84auQD3J2GhIArWMz(c$*qeq1*wSfz{Apu-2KcfDZh+cS zxmbpe1=6Ye|A7L?_DNboOXfoXwjNPGQ5eV-J{nUF1+0M!r^OPvOD2F-0po@kTm`cR zXuyP()#LYw>}xX#MB|jAkLLc?)*cE9f#&9AJ;9B0Z9tEPJr++WATc4rj%_tyt!RXQ zh#H8b=BJ@kiE5kW9P~}?3!(Y?_{1wK>#VA-ha%$|s%K`hwI|ca!x`sU;qBWsCWDm0 zm@?u6I(2&Jejg~^3&{wwGcu`RUxZ>1gfmI&a6{5Im-Q7uTg*>s06ZA{=WvN*?m*)0 zBZ96cq(Ql!RA>-xngax+*%yJ2|9hvsT|OG)A6^h>I$f?*)1I{Ud?TB~LeJFHNDDin zQH-^gNrYrnK*0A3=qO0SAog}2j9I+sIIY5>4pJ`(LE#~Z=-Zwjgve-z6+n|T#lTK) znY|bPfTC6H3hbmZJQwr_qlPEZ#b9{M&Mh^$7G*28UyAvOiYkz?vHT*tDlTg<7g7&+ zyIYh`BB`pZ#GFgD_BVTO2e`+Nn)HF@)W7Y!fE~d|u0_}f^lKv_k6bL?J$haB+@EAZ z>5bvmxix|K2nor!A%I4q)e9?N&h;bQ7mGguZkt`HME`xLDSpPBx(@nhcv;1uam9kGjm_t^;!Bp;EUV#g|O9^m$++Cs#Va zH6~q*DH=R9sT7@rx*Q6Pu-ccD1bj@D4J}w`G$=3*C{+RF2a~tUI7AscuG2$b#(7t$ zDduP8d4U>pJV{AjN=mftZRV0<-I2kTkr5oKPc*%%>YG_v?G?1L+qZ((4z zQ{v(moEDAGRsA)#U@#EDJcwZ5&p=bMY(Ed1l-!hOq7)18GsEly?o>;m2jPfZ7+&F- zsl<~%;ik7ry}a6f2?Yn=rb9C#BQ;m76-(hvD8v?$S<0`p2o&Td1(gD=jKY|K+c<8`eBE1y(32l|i!_ zJ-+~$FSaV>Dw-)D4(B#U5>GphirqhbG7PPg*`=~>A3|J=r zk(I>$GxG`^>;4~-&Vjqmu4|*QPi)(6Y};tk#%kQyNndsk9}o{vVvP3y&082a;X6!$%0 z;`{FFBl0$i(3&hU;|NG*z;U z`bTWE&w!44rG&r#uC)l6hkFaR(nD{U@t>GYUIp}tlh!r`HM?J+HMs&{|EY!`2wOKL)x4CLBT-w!s8f|yPNNc5%$&k+$Z z*48$}p^45lnGUS(F3^(JMq}bpJDAT=8Dg`>5@~S>*0An7(Z`tmvIkRsyj4P!mwk_q zSb!6oVkfTWS%F^svZ%L=i=$Wktu19YAb_zFM_23eb8!wZ=nNC{=7JQ-!F<1he;F=! zo5&uQp1iwb0U_&Q;4P=8dpVB8AF0{xPrn=4I*h+`+=q2K0Bu{X8i?>XS;u{UAAG*; zuWY(|v$NA|Z4D13(twq}S(5=kc>DxDp4}EJ;1aH8aP1FNi>;cxHL9qiyK$@?&VV?IvW1~XHdGV<4$!r-iI&TMQX z+~{m?gi}PO?8jL2wLS08*L%a#-~X53_4}`4^=Y^pG>k-7@J$OD*X8ZEXy^K_B*OB6M=w_{?u!;;}QDlG1ZUW(fPsX5e0?UFr8&RF(D~# zcsm{$>1qQ!U<6kE#yi%BQV&~N-~x9ytQxHBdn(DTYna7xv|D7(KqlKymp;b4kYo7u zU=P}HAycV1CkI$M1@?s(7(sqsLPJg)zd0Qo2t0m4mxp1ymCwp`4$5Od=_qV2{C(Ke zvE)ql^kjB+mVnYCLPX=mM~JgFzWfy()zk*yS~<)DKonq z9t1T5r%SG)Vg+p)Ky>BK$DYj(Bk0;m4!lN=g3DdPMBJQjija~bXCE$g_UMJbsa90l zH_|nznaxq-ylitbihu^w zf?l3aMlBfm%khD&?3g&xJ7q7NrKY`}PCXno00^Y5? zyy#?NwQ8r1=-qMYpZ?`8iXlu~>zxbRU-o>ww6yFEgp=WOSxI_7134lQ#tO|$VUt3_ z#ot#Fi@H}dFr!|uob*T<`8NSk8}d@tT9H`!?B*FbTAQt^poG5KGFujbKRTokZQAc`g0xdevKLysDuKFdYl#YfZquZ-ui4 z>i*X20ZCNt>_WG{J9WB1Ky1~VSx-kzMmPu%qUCvsavjtta-#dx+zcGKv)$-_xloW_xB+c3-676D;(LRavY~aUsg3{lPGl@UyMMLLN)J(B#i7 z3|NH-2n_76EJ4kxl7Ls^JuP_P|EX6$o6!4jPgp*mY2e{~%^i})S%o(^6=va-$wa0a z2yH?*H+|V-W|+51BFO5?&nKFnLlxuE7yJW3zw_9fDSNBJc&w@_C294g*XdX51F`CK z^6adU#^`2;C|oBx;SJ1Z@Hf>R6vhb(oWFn1%gX@OuVfELY)gX2QGfz&+p>FJJJ8GQ*q<^j7>zL6sS#oyvEs6Fy;oJ z_V&56hRc*HTgzTS*+`X_HNG7$G+9^Jie3e@*Pn$EiGdzT>%vgBqY^T?S`RdBGL%ykKY?`GU(*C?L{2<-NoPZ#9NrWt*HQ_9E1O9 zl&<4@J7VcbTfAz^Y(&$qPdGzVJTfwj3s+YV2)9QHqdm*+ADtWDE25ZoIbpf4n2y9^ zXhcM$-`|VBxd5IJmNE?77EVqLmgR^jz1qoqJ7vYjE}JaH%Iaq2LVz6HWsU7O8>T|U zmvfY($yx#LI6^KPS0x!HQZQBDPw>2e$OSzw#>43&0ehwD=60%ro;3!nr^q25XYr{E zKc8H5V3bIJ8rz^!Zf^DWcO`rfZ6YispsYv=WH_4ypN&79bMx1VM$L7W3wnD2YnA<% zFQ=P5+}uB{FMdOJ7o9M~d@g|riKcvuAFh2Hpx&cw4Go3lwQ-}<@cZ!NmdF$SI?$cy zEb#Y7pK_A@&qPgNt}d(b-|6Z1_CiJ^pfw7Tmq6M6QQ7&v{k|k(iTcbwJzdKx;B6rD zpR`>inz%eHZ_7(|(ssnGn-4FY?e+CmlY2mVeuh{!w$pRn9@rjDHZi%IomH-{KbxDo z2N6GF)AWWmkt_WJtgp}qu22NtyL7)LM|<9?*Ai3g?X@TA?0LN!2%7>}tONxX{?pfb zp8z6=qlAQgIIMOwkZj4`j4X9jn^>;8U5FT)46Q5V!~*ttOy6bq%Z1A{qy%2%46v8* zPV`?IY9;aQ3QRChlOfy;kIJ0wY0J$0B~7wK=hibL8fT_nlJnl+-Bj?KgcWpRzU68- z^q}$$+mO}hs2rxCa?wS-5IVeHm^cp~t9G zd6zo0vR`ZGGOGQlw6z(dI2xM`0Hy5Jd74#`q8Zh<;h4%DY`a& zt;q+z$k{`+g#ivl#YGf>Es}y$;g|-?xSObuiu4rD!0aKrL=n7c?guxl0d=63 zp_HvAq6EDvN8IUs=h50mc@M;4oC!8?HoQLGY(@<9YF^OPp##ZP2}!DFj?`obWB&R2 z*-}!iCnoZilsYJ=Zqoz{G|f`QMKq`r)}Wd=Ggk+!oRBy__M{KN!2zq3Vkqms_^n`n zo;S@Us)N^KU--kloVe3>{hFZ1+Ly5?dLxin#p3@wy_ z_CLe$oV{x|B6BAmj`Bh4@VbR+w^I;+_6szMB;oi&gl?Jj9-~OaumYnK=@vHvqb)};L1W?8e0Y{gKB~PPTdi#(uXWxV z^CTXw2L5jVUPVA0gcj#(X}K2l86ZknNGEV%F^2nwh&arDf2oRzHOx-?%|T@*>wZ7= z<823}nLPzd{L`|2a6>){Jse?>mO7(rWXogfrB$;k0j(xrSyk3u$#{HZNl!n8#EH0& zyc^;^7;GNI1?F19zhsWU$2>Y5!&X&5(kU)NH!&W9;(TnBf8uY%^`?h-_+28c`XWJ2 z4vvWlOJt;8W8(uFu_p>ZR&cOtU4SCyf>f4ak_-FbOXl@+ zX2?L4PTmM+S2J5f^*ii8X@v5lj*pFELIAp3i%zfgW6yJlh9V zTGgxxA5~HIIKYQ$fO!9mOJ_Ng|M*o5`gCgmkwf^LnVzNJfnOySKn7^-wIN8|VcU|$t-Cw{znHK7zq7((S%@$C6 zD=yiO0KTp8Xh3?fGks;k9uhALOm8^_5lkErhLOxio>9_y|1!%(esVVCD{O!EktB4! z91TXR(8FoMcr3}wTLa*5WVsfW;-IfyEvd^AiRCm5h;(1EaI`T9+88My>DoWhSpvH6 z_xCrnco!EO*~24EXRn6^#5u{d0`)TCo7AvJ-gy={$-rEr;>9&$tVRm%yPW_?1wx0O zY8euMgxtpTr#rE~aMvi;(VZ0)=_~MeDH$s`7%NC*CMGtNC(%ewz0a!M;zWLW7ebFi zLF+*MDlgCE{A7aBObnw`)U{Om!AfQMMOwV%%fq=OLY};>hK8A)owk&epX=?~&-1B%*)_nKRt)%L=%eg}f&)#{Cz~#lF4T55#&qD0Y0Yr*-8bQBRHnTJ zU3C3K`FSR(m$G7tP8imD#t8yhJO)7QMOOWH_5Lr23^Ru&1rq*?VBs08WGstNV-`L| z%;#o;8qK+4lDq2Mv6+RO&ITUE6nw#F2q+A!Yv^+4yyBE#umW*OrG#~#Uc_1anhn?>YkpI z(d`;$W7I}fF>_Q*Q__J?tgL5%&DAxa%Dt-k zJVT^wWZbr;@e@A$v_aJ{y zd>1$vh)U(`iqF>3WVPR;Vcu&xYG|m#Kp8iL!H|=q z5BIF~x(mb#rUhSX0*S9C$$aw;IlG_2RBS|N5f3)7h>#iklotQ~1$al^hN%e+EP^k< zNLyeiPWEUvM0QrUvRTCk))uHBhZYv>Pfk>goDnCVCuma3e6I%;iuU{aONXPqP79*8 zy03?T$ILTLO`?KAK+P})*cpO?9`%89nxE;c7wB4}35!c4w!DtW@`koQYH&jurq61b zs)~|l(M7EFdokP!NPv5dW#RWNfE(T(jE%ju9rZOTdXsv*waw6YE}Vhegsg_+q)y8( zb*#Up#VWfV#53vy;8u9RB4f}gkl(nJ2RfE{xesRhc!U$yvmEU{@wLQoc6b6j1h0s= zVO#{ID+34#M=9bSFnNghq2SUh?YZN-g&-@IPGEK4E%SmQJ4;z#iUI&z&dC9`R_$OA zuHZef^`rjedFuNHCry{;@Cy$9G#gqn>x5CGSK|vVSdmcA!h;^3rcqI`Wk~_$=L6F8 zlM{SdCkf)gzkC!*TN1LvfJdjerba74awXP*Dr%+-TF>!EOkW?PRmv}jppmsT0J;K3 zqYNNy_=Kl2_?wskQa>K~7qH{;9$n^C`z^>P5AMLxhSf5oa1Vgp1%?lszE~&a=7Hoqx5kj`L0;5 zj-X25cet0ESx`fdkWXD4*MYuqahIPE`Sxs7i&^Z~8#wPpDrIjtN-k^bR3o8=p#a2K z^LwdRtT~ywAVN9gz_{tYd`UDAk-O(g9hEOCfwS?|4#4iHXmr@F0eA*~D^?pjE_!mI zQmqcupb854XK)EI($6Mu+34a)K&4$)g}^ki`2Bs6f}+yQtkvQhW3uO8SniJNgVctm z#nR@4ABets`y2?-bdjiKAymRr|b$UB*AQV&=Cs?A9) z9O6z&<|=;927bx{4BE!8XTiZ|K|waq(3O8bHMU_YUp>?YdP+!~4h$$lL6`b?D!F*% zo*uT0m@^_Heu?~CRC)<0(jTw>+C=Q`#Vuk_1K|M%O#3IB>DwdG^$RBp%dhDv(i~Q< zAscfA*w&+%+;xyP`cBf>J^pR(f=rvG0LgG<(Jj65j1nF`KgMfwf&T3)*`nCupnl$K z9-KRP08Mm3z0g5!uLqzY52?~dQoNL6wj?|fWiqYgCx)Yv1(9%0i4S?~?mQ)3-lw0h zAd7>l;Eec?&=?1(S3JPT;{0JU;V9RXz5wkl*|4A*G6eBzOqUYT>ue6qN`cgFTlZ9S|?pyEvl>Ab8wvP?g1}&74Njmi1jfz&X%gTP2f=igE1cKrW@4hP-1s=6@YWmp~<;8!?>ba`}=6lrxMjx zmf3jTEg;Od%)QJ7#75zV_OQD;k(%M{BXef*xUhnX`O*7Rz+g+6xXOGWe}D} zotQQ*#UX3F+xmb?EpKyE0g+e{i+rww3rgmuh3D}B^HkYq_D}NXGnC+bX7)M;PG?Dl zpWZkxnyL&7k9W0;WpE-dwf-bN=gm|gxb;oSO=NbRKY#4x6wj2DVY$CzVGWXni`mUyh#T&Y(GFE&5e7vq{oB#+;b`XxRGLh3d87qExj&W6GWItcUte=Y`=kEUW8HG zCL>JgaI6*p9aMXNU4DB5#b=%sj7kGd6h!DMf1BP-i$#AdG!{oK4qA~=ll-$DPWU1G z-|9Z+=#P+lBgGp&$PvjM=u||<2(~0~)I4!GM)UFzUnF;6es*ER`B@`{TnrjtaDqmJ z2rGDSSi#f@hVzOBKM%>_vUqKblV|sF6%iwSsydxeqQGPv`u^zVp7#zEp;GadUQXoN1|+04uk@s_ zdcI)%cQ*t54PPA{n^1h|q6mbjTFWW!rk7a6d;WEppRt4s3Msa?Vjxyaz@zG0pslLP z9kY9+gNv?)gI`vnE+MHSE!89=-8?*0gSR%bWN)XatgZM3kL@$nISozAUr-wwE;$6U zd5GEh*MmljB{J9Ej-5d_;A69+SjPgo;UHo2g++#NEkH?05b_Dw`C)-wycB4A`o^8> zwrb(bCfUs61LdK>L~i*N^Z{ezxM(%Ij*YNRB2aYb-DDCCOrR@|T;=11-57Hmk{7?0 zR8ONn4An=V$`>di5KJx*9UCd$audiV-Su#(<>zi|Ssqa)mh*5m35ytZefIeH!Ce{30bj~y&)aCJ-(Yp($qJsHb7Ap=AaedUyz6@5IvPLEkIK z!ARvksa%pb8Z^8nvCy+*KrbWNC;><#P;!QnHqe(`UuS(c0*=oZ{QQasKuFGzAo@1y z4v*A~2^S<<_V4Ag(hr?mS;=;;N{qUC0?(_-*ChX|Bb72DXDmtO7B82lV|A`kXsS+R zre0uUV=N&Ty}kHyAx1gQWZ&Yf*U5-J8#-Ir_48S64*+!M4flSYK`n1 zcp7yf_gq(IiqfM@0!|+<6q_LCloTmQsO=xtvW8B0WA5orUM(*SEgyQJ>QJ4A*4Kw; zh(A6&3-M9A@uX!GoY)I#hdb=U#^iGbrd+K5uD@Jd8kqn(W!Oc%YGuzPAhI$$OCmvp zi1?EyAu(B}cmC+8(pc-W0|cb!_W-eg!nQBcm30z&nIOTXYE2Z1D~eZO%w(#HPs}t= zBVfK0qoBB`h9n|?6fK`1&zxv$xY#T&a$0racn|lVV&skCHz`%0L%3e;4X`suf#eN^rlm78`R>Rhv2L78rvKQyC z)vZ6rR9hu4XSK{%$RY47at_<*O8-;3YZlT6R?J}3m(E4n?nra`mAX;XhRP~x?0S?! zQ(yna&vz^+II{pa_=>>bWz{Y=Qd6H7`@1Z)HiOWges9f`XmZCuEK^fxTC{e7hy-HK z9IB>au4V08AJk$J79FE*2)1n8XDoU{Luq$UU}E1(x2bTIJ*KG|GPHP7)~8LS%Dh74 z&^f!bM7wQId8+sU``;1f9cd+&KJcN2xifR5X%1P=4!BjH%J`P4m}0IM+ILy zWBaQpEpsAA*yh{* z3WwV=(;Bkgh@hIti;^}VcwKZw)PYQxbLZMvC|B?@=U`*;+>0^5Gt>nDF^4B#j(1H( zy~UTVCA<@FMF=rSQb=X*%BUAhs9d@Vo7D&WUhYrID3+?&-ckXH!=rqOze)0O+x+0> z8Gi%RN^)wr1RsGhKi=HBfqij9@WEnFrs4|0=tfQC0bxfd zpy-yn*(eL-DuY-=Cr}XP=T1JtED_|Ja00Rihb@2RUD8`0oJD(X0s?F{+#MawJG<$37oxzeKY6LR?gAXj|0A`e~)lMxR$QFb0$ zM#`?N?f4w68@**aL+_+Re>-a7 z9prlh)OzIC*9r-V1(G%f27vF^%gs&MY@bQko|gbI3|KTy)fa(5r27>{b(+xXy^vbN z29xX40YEFlF*HP%iOWY06gm+XAfH1|cv4X=i*u!iq_P>OiLk4*oPyylQORBd=!@Ga zzyz)|<9)A*!n&?6Ag*Z`)vp(ti6M~zUywP16u3&U^`h_kP)wrPm2}I10dAxzhxTuH z_ufWl1i>h#!NvyILa$hLkP_V* zex80c7ksIs3wLCu31Ca&@oV?~Hk7DZfslr!oR(Me?^>IW@%$X$Ak}b<+a^g&CRr>j zd>D}T0PlRe?N-dz4A!))DT;^vwpB}?S15&i0c3dMV!MP7s%-jw8ty&bh!`jyb63JO z0zIC}bCb6Zk%ZkK0<78^8_}eF>yd0|ro8%0$AgMS~ZCh`Z?2P06 zq9J~jYz>vS7~TbiSfh8$g)%fWe_Vqu;?*mu&v^ zaaO-vK{We5i{MzFl>BOocU~u56eA;6La~;%UYPB zrH7Og>!7gy&Q3v@nExa*4*%giu~g=KKDLXa&QpGkQ6u*T{v9|q@%8yFUxl&+uNY#| zFM)x=!i~?a2N)p#vfckhXQncGjGXQjy2grHh2|as6f$)%+$&!^dNGmAUT#Q8Dt&!h zq@}>*89k>JH5_IwK%x>A`3waWhy@iA{-);)!<_%Jf5G~TBU}?T7NmkXKJtZ+|t96m{|H1e~#qg<_-yU!tOYc3xt-d_lbutm=7({Bs@5SCXyi1 zHNVDf#~&<4v^Sm)<-ukIyw@&Hz+X4C;=;A^@WoX8uagDDwJ* zJ~$qy1PdKlmul(0(!W~9k#?=y4tplh>C?xI1JH21JyyeMe!E=XWxO3um5hdiGSIeSF`dr<^ z29D|_;GCv@VE!Wx;$kDSw3m7u(_pZ+sQp0kTJQu_N@Ef6uZgOJ?#m`b6yYelm{Ve6 z(FCGMs~Z8G$nB>)`Zft>#UAv)(tb)9dWdug}L>01IA3(DJ;$KUjS?8wgw9dOdWJiQ6h1qlu`Xm4>*J=M|1BteIx^coo4f6Z5rm z(_K*}uG@1WO>ro;D;jHq#6w@?hwlO(aEyw%L1J{j9z=-thSvVzwh>mbzn+zq4Wm~2 z_cH*N?5XzSXNJz|Zenh9_Y^!EssKG@G&E}7WXJY(?8V9ai8uM45_#~Oxr5IGPtQrJ zi-oXyirkJIzd7pbdY2DSI6)C}j?497jpU8YS_GB4qA#W%(l95=Tm~vliE(xxi?m3D zARsiLm7)8YwiiNq8hJJVLUjL3~j+fDHYKe7>Sd0wSZY)trmDoqX zd5*yl@bP`nJdnv_A`!P^J^jSQo8gsGGy#-YMDe)SRA%T{LQQ|2{9dNcz~*Rd{nyjs zwHsbXXA!UIp9)tQ-X&7V_e{d24O)(Jvd|z7JiqB>-u?<8XHi|AVRJZTl!`)`C7zS9S&Eh%#z}S5K+QG=Hy)2=@mVrOXTeyQT3P9Ckk-*k_!~!g zUy@m-USnO=xA+BfOzmQz>g269R2o}QAq zMvC@Gyf>HwL%I_ghKaC*IkE@#So9M#1}aEY)a37;)Bi`V0x(`rVt+ zMA5~q8Y~<44iz?TJ-hod_somKvKEMywWy=Y|M7PP7Dc5y?2ftVs@8!ajN;+yEOwR}Efv5s0x8c9FBo`uTk%poIxbhe% zF%e28b)gZpB1X)m*Fb3gLYh$dcSFqgDHlfI?BUgRk`%UUaX>RwPy@Q@X zam+2)?^zI=|D#YvjRCeOgvUxLeFc5Z7@lt>qp9!veA)ugMX#@oEkt(vsh%J6Rr`PN zL&Ld(2)S|M0;tbXr^V&IP-KAugDCcvuihZn_v zD+{U!7JdC&Hv6YHB%`!*JmC_^w*N!^dMAAO#H**2i)!dP-!fk^YLQKEkVTe@YOYmF z#lS%xpP#H`tR64n8ANpu&+nKa(Hm5%Ma!U~A!|)Z8F;}?_bKD0IP&zThV?cd+M&BE=zET^#dpYFi*cS*`+ zRXU}D2*-WZH^xz0={4KrD>cT0lidSTp%jN9!3CSoRF$Sfz3;F7&d^;uet$h9XqWG1%J*mrF7iNNF^x_}_o+(xE8jQ|739(IDTK?Pred%O zb3pb{)$p$YZla9LJUpmCbyfSGVC}s>5MD&HoO<%Sxp};BXem!whA68rURll&J=hWM zE-pxXm0b8&shn=<{mCcZZrA!H{8OZZ5Nse)0i81@&%zy+98V^n+9{n*QLgT-{O_kJ zD044(Uy8@6@9OVtQ;$C1m#NN!BdC|l9{O(aBrt?O|MWY9L$))L_I$g z>m(QjFY@bAFZ{*OH8z}!J)(KI@#%|gf9|I18A(L@*X<8Cz~GlXUt0TP`$ev>gTSby zjGo#z%RP~j;)L(R7P}a9A1FCh07~CN?;o%jlgSlnTwAw%ID=Po0}=B%%;dqY+ci2K z&olxO7R!~oOE>o%QQya>_~8hQsOynmcteIzq0xkc{NK&lp)MO$cOSZW)0;ex1Ut6h z?=OTOzyOSjvc+>g^Rq+iW^G&wBI*Vs>K@BH+91Qq4^_{$dA$sIp*=`oyOZ zn+l!UT|!dkh>1gp?;4Y(F`ux&&ge8Z7Q=Ye4Lc#Ud=eBQaug7p)2Dy(gB;H&1>#`qizOUDn9;*i#%Uu_z*&c@Nd$HclIFQKi z4gWYdrRC7kI92nczfLkqp*oxhLDnMJ^a2HRyO}9rqA;~Y6*+&o$L0LXL+<952vd#~ z*JxZ1t)<*0x*ey`3ARAcuaVh$b$9I+R4-%7^x>QKP8t#z%eq$LhF5Pwj^DaOK62}v zNT~P%O}39$^Z9@M?%UpyG;0`=bE>sowT71%;|TR4c_8i8IOV;Hh~(`x9+N-GX_!~0 zcX>}<%v++KROzrWeA;JAF)?9;C!@CfW&4yrF6`2%yM8*;$` zm4;0M?X^7i_@&XEPwVTo2d?gK&;CY*Cv8W0F=tzMH69BRpff>i9ttcXI~A+o#j!s! zxb}5)#6Gx=R{R|G!ICMv;xD;#9S>C^e)Q}v&v$1*Z~$Wo+N#Sd>tx6f;ui{`>o;(y z<{;{@e=~I)yN7Rq`*2d!}T( zDJ^8Li2}+D!W=oaI_V4v7Y0aLyU^p90SW(HzF8&XH+%(uZeR_4@`}JFbyOg?U4}t{ z$BMkW=lxC}UJTI#3Wdk(^{oHFLRb3oN4@O=Y7 ziHlA?VxUo&U;;BJa_P?N0LeqoFLAHQ(5Z7ORhXif(w+UJHY@0#2aP%XU;ULxz8-Z^heCl4CE{`9+fsW zmZ0^e50yjJE9#ZieBjVr&$$+hv~hO+`My{~1KFaZL49fB*=r(9S1EzncE@d%%=tj% z{M@-)u7+AvLs2fffhgf2Kc?I%o7@Zv9?U3M=Ukdyz=U-?SnB_FcF}BWshmz}Z*l79 zq`nXdD#uz-%w8vFOUzp>ThQSdi>)W(!XCcpCr%4)_=$8_#&38+$h@DAONc&iRT7Hd zV8>!tBfG?GM^Rn9sOTOoY%aQLa-a2fBv$lL+*dr$@5`XWg{mx7DhEI7%1N>PZxlrx z*D~**I00Mz%MJ9k$W_vo3Ek?EhU_qZp-lCfb!la(O3XX&(I2c1Rgt!{yx^6i%ckb) zRWmd-=&9eH;1%TI=%=kRb?XIV`Pk=E-foU|gkcg7G3q%rF4Szr%o%db53UIIa4uNd z&&i*twN{vxXzRt-b7nd`Ca=){>8_DAvXGuZ%_%nofZ^2X3eMTTV?uJS_6pHFD>_$?27n1FU^(5L(dWc%sg!Jl*VJY^ng^}p54!TN8HfC5FrU!xF zRVLtR7#f{d;R6v{=pz%{5(q}a_KlvHQ#Qt)N7Fi`zwIX}R*DcL`t2Dc@cBQHqj~cPy znMSbjlU>x|BIfQCv9r|zTw~J=ZQ5H>OG1L`O9U+J!fkfDzPXD@eU0pARvN5E>s8Rb zthz1Z$2pgcZj`ceC;w}ZQ#GoR(b$Og9YuR%7t{blRQe#OvbeA+1 zx6aS2UWv}nEdlENY!ZWxN+#}R#NA10p<-sO0nwZ2X%_#(Mb|?GgJ$gIB65j|i`_C; zy|@bo%J9Rbznw2gb60#~Dei3XEa}G^^%@O37h@5!m2Zl9Goc)z#Gz>?y3k=gZT^5zj>K zhh6baQ)xVlA#gZIR7WjVouz8hmd6&MVZw|`I};Q9RFYCC$dXGhMaY(jjq2eI@5vS}DThQX~y4~lG)k2B##;`D*U zsNpS+D|N@F8Pg(6lR3~{3PimzuQ{>F@e#1DFnl8mNW|D}%!g__y(wH@I>9gr>$Zp#R3;=N>sbvZy!X#p%!z~fU)bXFxs{Oc?Nw~KvRRZ4 zT;K%ud!bM8p5;<3$;=TDY#NV*1&hA?OH=OwrgRb@OS#L(Iz;C(x zFUV4@MNr@ng;eP!^E&TbwX7~Y1U-hWzDS+Ai)uW=%RnHCCK(oo8$lgPyCjc!Dvu<> z-t(7xV&PVKW8%s;;i{Bx`^kHJ@v*UO17QX{UN<-$VCT{O(W4j=LH|DytCHb4LY`OL z9q0G^7_wE!10$E-%l1BC^;MtC=1bqBJ-|oxqW3-W2lHL2sI-o*|Bd|3W*0oMW6Q_o zDvKWQ*?jv|N2`=ZF4<)=%8iq5CeB`|np^Y!rW(cet*T6%wJ=4_ zn^ODQDFKf=8n#US8{7gHotvCu`S;iY8rW!{*kLYFya||-hQprf)fnXxbw(hL$Z}A> z^I~Qf-!cbjWO}Km&>0{lRlV=x6|UQVoEafui9uJI9;sfeKiouO6%qx%y&g5SbY{}z zgMOB&IH*mSdk5RsQzSG)z^~&RjzFQtDxQW!lFT7ACG*gNXe?wDL}cl3vC)x3BSLW& zXvNZaM1$P48=Mnanq2B#2~q?~6lmfXQ&ls#d>*f`;LvgajDD7S zVYjexF9$SSQW$DBzwe`Uq0A)Fr^75ntQ4j625^i+=b^GgyVvtAx6fsl$+lnNS@(wj z2dLwG2={~*1a0rTD@9~vCJ!aQmEbYRO!bYRe$gn15Qfo7FY%Lk(r92$V^?-u9j1Gj zsyM0L?#6kGpk>GT6e;&9&`;NHyDe)2b%lWS+^*9eZVVx;yFQ;uKQdkJYj9!Q564`; zE2e)(vRI-e@7Hn1$-`uqhFuwOJ!grzO6ZfeW9+`AT&#dC^TBpyU*P#AW^wE)?HZj z8vX4wJ{$+n*WT|av@|cOhd)?Yl+Wf#-fAg0JtwWj*RPgB@lXCG8~GA&c5TEAv?P?79WsRr_l&r`T}hY z^OkifEHKEK;91wdXWf&a5%&0l07vUban8oVMm0!G)zkx*fI=o9{Vd%M zhvg=LGpB6orEYk{(3NtC0+or7x+K{Y{fDLF54=a*$FZQ6~PCLNmUxj8Y3v#gbtC>W{}>t%}O!aT>32 zZ~vphe(CVTJe0WH@B+RwaU{yWfGSy%jMYj!W(l4oMj9HVaWmX_>`|lYBn>3O{9%uzUr^Y#1*yEZ`0MlriO9;~#ywkK& z3N0+|CK|x_NfH6qliyC2^ohg7E;g1&k->0&#ntCTZ!cJGzFE(c zdO4lNvl!s^GOMMnnx)8Y@Vmz?+O-9eE$t0%IH2}V#{VXSxjP>?b=1JzTHp6@DjSy zdZ3DvTK4NXNI6D*jKcGn&i3hxz=$@A?f>w09Zv!dpD%esKVHr~MkvC;+AJ|3x$ZLB z*?4j}J!%9E{A3Lq^YZHReZ#K9+cS9cEN=4={%f4_qo!6S8@1U`9`-M3!@lmTE$cTJ zxbf|oB_Mt}+z~#y`!;b$L-Z`Nk2ga*L!ru3X(#!_ps`?X@w$Bfj|<+Or(_PZB@frD zBmuh67^GpdioJjK_M$enFXDRZjn~0PrvGk7TUtsh{CmiH#=Q$Ny*vM9ZwhVozlMLX z>xK8wJRWRy`7dp~EBL&$$D;NhhTGe--Jjkm$PTl;JkWXmSgYNhetz(8E8Gk1_VZ%A zzE{#)2aJrJTt8l?X;q?Gzx(tf&iYT7k`lXVxw0(#-FrFq1|aM`-=;4w!CB=j&(?oG z%JzT0j*}H~I;;LDDb94b=)M{27>c#nVK;B@xe9G;-TX|RSwE2X=^82;0y%`59j$Ic z<4R{s+p6~p@;IRHI{;UR8x-Z4TjkqIrs1gd<;$rLp19w{=hltR4&>Q!z7L5@l)qTB ztwrFx0=g{nPv(2QcC)W;X1jzTOAKjgoKE0l_0H{{b5OSJ zbzlCky)XZVvisw%XtK|@_$50@>KeP3oQV;K8xkS(nuWU`GlVn~K)?7NUkc0y># zc84rQ-GvrQyvYU$wdQ`@zhtS7JIV{cc@KBu_uhxc=UQ z_VeLaL#u@;(b|Z98fuI0k5BPj%dpO{k5?asW3K#TTTpQQLzZ=^Be0k_1705FZbt3t z9ooQMovzv|2h!Z<4bj^^b#Y_x_j^t!`NK5bw$tY;*=m?=G~N{Va-0QLQ0gE1vK;66 zi6-F2WQ3;;-$lx8S0_JzGc^8;=$|kD6gG&by3Vykt!>{ z5tm%A-=0uC75J3vMYzZOJ_Ru^1zidY}!_v65(TE7YxD&|sR6+b7gF1l*x6(-r9#kc1Pcbm77htkY&bVmzn z-gC#GOV!43@;2d}?d_n6H#P^2i%-h`=;M|j@kI7)%p(qmH(U^haj>;d9sU{`{K=U^ zndhPy2dWaEaD4;T?zi9H4xiF6?eIix&mRgH^8HK&ELy;2|HGw=zo!BIg}lBx>!cd7 zWB>3W$Z@F;|4I4b)AdVFo1SBTni2%)&s@Uq)JR4%PO z%U)MgxmR9ZqY`2M#lONiaHp-jM%9d;EpK4Vj6^aXh9?a#`-^)ixp*jL;S%_9Ssmr9 zQ{`e)<*HLsruYg%Sry4=ru@Diu1RGcLqES2_;1vUs%+y~Jnk(fl~ToTgS(JYMd`pS zyGpm!vPXIcJB0WXegyIuK2vA7a>X@_`Uy3@85cgfX4I{r89p?~%<;Kp@>O-0OZ{aoKfl30SH2M_HQVi49*D|OKD&3` zty7>kxmAv?<3SuB-=S$|^1-%={=VwrXQHeYKSVm~zL&b9_{aK-UDxQ3Z=Iu>zx!S$ z9L!#KXFV)jzm=SM`U2k~wOeK}Lr*U8cF2UOhl|zEk~;aM+czd&muFK8b9K&~%GNB( z)ej?j3k(}#74MhIR<@coPQPqDaX4}&Vq$D17{>L*0-6{mVIE=m9Ev&yLk z;kSu6rN_f4)qBS#$!<*CVUlY*of@C6h`RF?Xt-u6)u#CKebLy5gmUtup=hIvuQHsK z7mfa;JKhz8j7vK+T0~Q2%l0~_l000PfA3FCejP}uBHp$Out<9$aM;ao$^NGYh}KV= z4m9CWj4{A6GTWkzDPLeGr1tvh6!N!m`iH+8e3zm~&sKyfHl%X~I*lwE8C=x@IJWo*8v4o<8+C=bdOtu4!0q?t=2?+%nYn;#^fI z*Ij9Vw5y7D6M=B{vu2+#ZBG-AH;}(DUN~ z{|1t)CXHtCmaj1`l&Tl3xaTs>ACBYB#a6X+V!q_MB@D+yvD5jdJ64$$?h({IukgC@ zIcj{UWzyx#!=9>Sa^s_F426HefgW=5J&_(!wAQl>4+w>+sCVfg27(~8Zheub(Li>F zx>%BlLDrWxgYE-rT-fDt0^>MNzJ3I>p2llvvy!)9Tm-$QpP_TxhZB&)itz+t@rabKlt9?}iC8}sE1 zm}zF*M32-Zd<7<zOc*u6dAtFu3RceDH3bhc z>$t>4`0&%#hfGkfyi)1LcSi%%6S(uWe+7#W?Ypj^PNRAUnK6tnA}n_l6cT#4#qkS3 zj<{L!$YvW7OZ_s*gmFRw2rGy}@P#+?@L7_V@;6`c@wqFOToXSzAGTS&dYKSd z1e?sKBy8Pd(tUW>f81cNkId7#u^YNOVRMj~{7AFR1gl-!CB^T4R#ql6qR`8Vs!-vb zLYcF1FAv?)6mw&0h^*lATPjm%qzDJbi`r}0eMxRQt&SP-g+uFS8ppVNG?9c8g#(F+ zdRk|)Y2A+Fx$21YKbOBxw>`S7=wRH7d?63_Dm?2$)kCZ#-$Z*LihFBIS7my?HvOWJmVEv z49fX&NN-mULux;_n!>52kq<}D5Xg1WR%*#*g^6}R^x7S3x12cSW08SNmsIzC%Ve?x zei?yJwrl^1!Slo1epJ+mqz+NigAt$4|NA%?MBt;W(oq#I$2GCw9a#uFgXIZ-Dmv40 zoLzp#mTXx{XFcN1W)Y*fjl+uNrkx3FP{v1Y_eP0Sym7sO7(6ctx%{lOu-AZ<$9BXh zyFV=-C2m>YGmR&yI6tEZnMB=KRI5w>Q|Q-MW5N4GnN40s!hc}0RGZkgs=xrYz*S>Y z>_$#tKWOf1HFH66G={5e-_at&+?IXU(<_TC(w#4#?o!5!2Kmr^G2(Ca3+Fn){f;0s zVC}pZz4pjPO7L}D8iwn=i`0^>TaEz&l%G-6Wa%bv%=i*D@ydqc_zkriP1&CAA~!Os zNUS_^l<1(d4l)9eO@3d*OPi4=i1D{dOtEiu5Sd_GSfluXqUrKzr1m)ndww6hn& z>V3P?+3UqsGM*;c>`V-dX=`QK)*79+M>pS$M{>Bu;}G0Zk~cERutCze0=mmf=KT6E zg+`RfA>8nVrE!58#f^$er`X*k$02hd3DfJ4<odU_87B| z;go_7(lkw#4~lduTaLIDdX1}ItKK(NpTIJLqP9GygHSY-kif5tVWj)9Na69HTMEiP z(8fO{R$I+>nak*6^|q%r<($wYr|qYECM&Y1ptNxVYgE@`VlS#I#Dr!d4(VH=M^si8 z4PqPEH)@DP430zz(!H1yrM5h|K@rN|#9kO1&$+8qApPJ*VVclo7JQ+P$%+isSJ+4J z;W&j6axU``FiHh+ajZT^g$=SmM0f?%N5rZa{w(UXb!+Jt%F3ZctIFdGl>^m^2*Cy% z_=PqUO=f!3=Loh7+Qj7&%zA=(406p6*0)QaiRO)3PVYrFec2t;-gqUbrbu_W>V?(Y zf%W>6_%Cad%TIDfzNo2)^zyCq+QijR*qVM6t5p6N z1NHhSH}o~sU)Z-x4{@n(%n=ZP6k6!6O2z{`*xKdgR%5WcM^`T^U^AP(V|X3|1XY)+ za0H!;7fYhSU;N%j9xxplm)2|Lp%!9Q?jQk(h;j)x%s%@0t>Z8e0$@w4#KmD@My}nI>KMYkhzW#Kj$KqM_CkRg|^C~-WC#| zy2a}zHms1@Ot3cM`yd%KJ)*Upd~;3ISLEXiPbvP-JAZRrlZnisj{RFJ&IU($d`@YE zc#u!U>I3A!E&Yr*r_t`2#HL3(Q-W$Cdp~e?)kE5dzETvG;`bk{m+123j4Q~)rA{k| zy1k+m-d{+*_u*RR12}XCnT+JVc5yp=C}R*;mTwmh?F+>0zr{;5H}Mp%?reQz9Uii8&6oK(S=6IZr= z^@bV}rPp7T-7*o=CfaM)e(DnV<1H&o{5j>3D)wkIG|dnpQH6W@HPiO?y7i-*Q6`B< z>Ck(ZZ(@qmROZk$oxysD&}eM6aQ+DAZU*R?B7=Iz>LFZFOCjNgm%;F#mR0qVxu=~g z)sm&^^x0)Qjp5br+ujjuS>9S)mBdl)25W7}AzN3Q*&wRU>F|RSBB#vNu>J?xH39zT zHRu*qOMqy615vA$1mu3 zo&qO_)&8zg7KA?!i4OnFD#Iz5eU?Z-FZC)6wTh}|S0Hj9Fv#j5n!jGG5Xv}{oq)-KCjP|6edn;T16a*g5C*3&;~ZLK?(y! zatjI)DqtODswktyU$2ydA~hO3!R)sG3JOPw&QhSt^{j0wG?qWK<&>2Wc%5AcQ%O?h zN^gHyrHqf~lTb-3G@jhD|7<59K>Nh((Vwl?@=wsZo1Itc-v>ry52r(7GQeAF!b_RFo(XW2Z-g#T!YG9z?XX-uKws*4G|@Q4;{W1{rTQ>7f^7ly`INs=*_cwNlbG%LZ5a$l>JPH%SI;_JTy@ z2MInCHT*GXOF|`MCDc|P|A|x5efZB-V4JN_YbHFeK^V z?qp`^MCD#8GLoT1Jx2Dv(z*_U&pfUH`Ll=+YJh~4{d|$<)QitinMeAy5t&M~piP8m z`1h|8(tJ$2B_=(5P+MDDM(T}~!D2#YZ!&jgDl+tB4hI?L{`lN-h6FdB0iUMrxT-Sr z4aIgkt>bZiCZk5p$k_qSlOk>Bhx) zq_BnLX4gcNJ{lK(!k--tgmIIa+XH_SZ!%2f^<#})ELd0>A4lr(FTD(%(d7yc7Pp(6KAjPoagxXQpDQ$l)vyD_V| z62mwPyuYwT`nj8+yH@5Mzm;3~>M#2;x6j1r1Yl46iL zJf3NuYw(b^hG}$h@J)3Wg?p~|Y!w=_j~>VLcBnSO;(=^?ZDpE}#l`HZNixhLTP{OJ z#<%FvaA*Q+_f7@CO)498q0sl-OfYLPfn90p1ALAPpA+()fjWAq<;MlB?*&0D`E^tT zlwo-GpwjAGV39%OYWqu@>ICGjotRc4^3=*7$F5>Ru_m6)pS2Jcy4z#$;xdq(TbjBt zKy&3W9)am@iaHS~moTM&?4G^SeFZ9qXMD*;z$xQ~J(C)Q1nUEEn%rg3iFqM$3Qc!0t&qhhUXHjoA&T!N1|#~H2F9|R0r z-AQizdggh&+Bks`e+-e5g<{iKc$8JApW@t#B>%!kIyZQe`%X;v6cIeX>16)~n(iweX$HNXNjf)(lsm z+&+fEsu`ildt}<((|JVX>Qf0svBBbfABPl5`r(`X(rSe6VsZm7O62Fi#xcmjnWPXSj*alcRN{B-;Qthz0dgG_JA+gVN1pweUK?LYaQF$-Z>bQIZL+kY z#516fINT^eXtl6Y$Sy4uSwW-OIF)*o?$gjf-0UIktAh%RSm7c8d4fBS3;JZzCy!#2 zr*il#fCkrS1eAn>b-akyMa3$0d6FI4p4o(I6VG|{iVAEDW`U&;9v)8bQz-gEWzvuU z4>3gOTmnkv0-1R8KO?4YlB}%40@eF2y;2BZ2&SH|L(AjY#(S5T%*oec%nLqB4RdG9 zqL|+$AtyC@oV06wQ!hkrWj+!u1n+!h#399f7CcR-@xu6)D=Q*p0xU}rBOL$$?y8c% z#v@Y>SIb|H2OA<o>7cF82`HL* zN1F;qRM(0R*-rV$dI6ZYQDuH2r7{5#phIXmPVIk!xJC`G?DiJA)+mBsuGw$fAvyVt zXaV;Sz^{;&B)ryuF_WyN zo?k%xcXgV){LQp3aaXU!L>RG9<3NJ_)hOj9)cG)J=4jX35IrJPmNjfyLWn04`HEic3AYOMMVTJ$Ov6ADLzZlMsEP}57uPHt*V5K zof&Kj<+$e^i+5eztxI*YoytaBHA&?G4dUT6$<9Z zcQ=yXFAVWInAXODci=59Xd?o$jYHm|i|Z1Iz<0P7D@aae^C>JXCQLME%XTv7Ijv3Q z8NGC`V1yh?l}A%QRK!ijA(IQ2jjLrw0GoY&tvwi)-M~_5y!5N+AfnRdsveQ2li9`P zXE?#G?}bV;0Q=owf=a>ojw*in@KhqOM+S=x>Uhz*z9PG3vVqY@-lYAK#l0`$m#D#D z17>wfbY@(r{*0_1@l@AFt5xoIo&3?>-nD`+k1FNt2Dw4h4{_=_dkU(>n1nb{qMdR& zVvccc^Fng;G2guQ^D4l&x;2%1B`I{zB;X&sry9!xH-Vme3a)85qV>5V#F-Pn-y!7D z-wh(4?d8LZgvq;$;=05(Ib*r}ItrTF|573PJbs6f(AEzcI*0bK|C;vf4L8JPIuPb_ z7C&dB{KdbBFy1Sl6nb0{7)~{->&_$kiZ66S1~QD$G&stu1lK)xLwm}iVjVeOs9BB1 zKO}Vb^gsl)V~`sJow1H?pt_gZ1E|**<2Y#{3ihUq-}xfKL;>h-W$)w@hxTng1*2OG2;7rq*qtX0PFMRJ2G~RswI64Lulv)n%rR3105?`D6WGjd4eO6q2!9`U@WUM))l-nw_63#^EptG= z#bl+ikE^b`A#;o{4>Lju1m=$PI3zMCJOs8`sh#44TAH?+=9|s}TpJ#|kk-^~s$9jyKInaQhw#31z6(|C}{hc?BGrE-(H( zouV|MW zR^cp`n*;@A_1GuO22N>0iZ3s)NCbJBf}W7~Vyvx2(MWt#kGD=g-xb_Y2rP{T;085U z3!`*jT-cMkrnjR-k29&aUNJlPi>_w~eu>s#wpZ!+ww?nNf@k^!A9koHn)fLi zAj!Z_-2i+rM>c4to3ZJt|4nz`pphyBxG&MMvu+IBns(?j2LTX#UtSyr4E>TUcoRm9%Gk6O;XAIs5`JmEzz-E3z_1&m<^9 zdB-5Rc(e#PnFwr4DEnme*DS1{hf)zt#n0nyFk$yn_4-Y8YVECJNvz=ejgnf9IjUXj6KQ2VVIq+*F1+2DEEpqLP8&#es50;{mCtLeg2iqC0( z&=S-1HfL%FM!j)AM|bIt$bGj89f@yVrs68&&05;PJ;8qNegNG)!eAY$7KSFVq|A~b zrt$Tr<%uvgU71Fqd~KutW(SGZZwUm|>^qk4N3x3u5og!b>W=Q}!Wr}5;D`5okzz^_C= zp)<9KVj}%PFG*wzUZp{ishqp@gY&1Ay5b7$@a_Q$Ty^H-D@SG?iQC#m`gJF}7`7-Fo037#D8^pouY!ib$z^q=U z00P+U@K5H`hAgNq{puU^(w1OL@ZOKRidNQ0I_i!UJ2cdF?w@1$9HQp7d62`)$OD?w zn!$$$+w<4vZzp?c6H`wfzaNIlQmMSiZkigTIN}S;#m*oJkS|bDplNlRnHofOwcpl! z-%1DV2cjH-9jXO%ylLywa#Ir05~D713Yz=q3NU`!ihHDA2ho=Rc#p#2wK{RUYlYx{ zy7vPNMPnK+zZ@H(rtl9N6`PTSWK6k;q`IE_j5Pz44Z|3V31&L>B=RuRcG-K=qpQM~J=hqD)Xis&Tnj^oYG`x^n+ymLi5x8MR=)vqOicB32`{ zzFkB8>hC5{>P5X3y>=3%!{s6>{#KSKnmjKnpq1jCb_5SH$4S7s6}`XN$#@Brg1!k* z!D9oHP67s`PXuMD+skimTlg-8X>UxxXKHWmFKPr8F9|;!G75uDg65A*21x-@;n@4Y zEZ{LZR@;eQw=c@Y4!rWILZwI8-E(6_>#FIZbW4?gZBxcXLHtXLLrNyplzYRuKg1yA z`&79Cz-j2>DnKr2^=_?;op?(juhILDgyzl$8^;Cr{)B;&A(-4_1Sj?S3&(6}VQhYM zRe9_%2^g{+4rhA+WpTvjxzQ!jeBF6I7g+nD3SMnR*%i zm1h&bPnxg-98qf;mSn?t_6P>P*2(YyX8dtRuD`24Z zg~V@`+e{uc9<6Jj(XkkMa&b5XX*WS*0EJr)adY5#*Az;kwGcYe_UU#2PTBg~W806$ zffSG23i`l-SF;F^NjdUZLRU3A7+Ymh9cgPpoPKfsMWn%=A%1NhpC%W@>_ScYuCxMY zgtm@Ic-rtQCP><{7*?WF{V6<8n+xAi9S<`1VDZlR0oMmpUHYj!3_zm5NH%a^0L%G6 zJc{`_O5m;-Q<4fg*4?$J*Z3Zvcwp_h=^V9bW)S7>nu5Oy zq_gVo3Sj!DltCJ&ZnMG!IG43dSGj+8^4d==hKgxjqf(Mo`g0j?-9a+LvT5b=fyP-p zEQpt60ILP)3a!=WlX}JxwkPq-#b+uD30$=up`RDM;}0pkOtcPo(y zW%D0FH3hnJ7Ly@wXV&5SoL2~g-3-H){7=D+{-!QABl?n zc-ekt+;z7Zsk_;Ks}Z=~c_qOd@FJ>ZnlC;DWf9hl;tk_qUH$D(G&1sO(SWhOCMo)r&7`!dqR)H6%MP)m82 zUyd`Q+4h?T7>}l6jnGjw!>h_hAzddw-LUA#uoA>DAIV(#rEjm$Z(dGX0lJveuvfR1 zcpsJ`%zdz7`MEpFJS2N7Yl-Iia&Mf~F-#1w}`pTV2n9qJS`0cs)aMyKT z+mQ|sfz8E9&Su~%J;cD>3%W#6Asj`hY*vT(PhvGQe&E09@;BhnSC@m^j~cK;!ZSyR ztPD)fAGBc1mGgWf$CAU$n7?q+o}zvABIGJZ)Oos_DM)OCgi1sC&(8w zDq)RTFRGR=mqZ3kz$&i0CNenfjtO3mnrqOU*z%Z(fj1>^W>+QpPD@eLo29OJG)t1* za1}j;y(MqAjU+{SxGCazY+sI7#n^ausW-e61ji0gc2Q~H;62cO^(dZnp3Co2VLNPR8dCbk6)+9=i~!BOA=Is5QAEs zj6`Hpk#R@Bd9F9mN#8iwR_xr2)zMn0^C^N^6oZ~f*&zL`P0~?{UuD~3JU>KtUnc+8 zEZ9MMcJ_Vmya#+{G`ZA|DR0d(tSDC+RQ;7wm&(6O=7*4GFI=P(Jzp^lPpR6!%zi9@ zonDrIp7=3fC^v7nl@bjih5xGxzY2}gz=?Of{^9dQ0YwHn+;t#%9rfo1D^w{m_;do5 zJ#28~W;>#me$58CMD%jsGtVs{; zzPd4XE-t(R0;QXZ<~`^n<6%kVYP2|$T_Z7 z89BCufgj^7$x*-A&z(~%>U&c0B4^$r>;jhJ76S9uTP`FpNd^F#dK-JslcWUdB1i_x zbw@>*EI_SfZ1bXv#eG@|VQJde(q3icJHOoLd-oTRot&IzKMv{vY4g{eg~Q0eSC*OS z2PX*tdltbxZo;Fn`W;Qv@MwNW41TUlQ9V-ESDP|)^QQs$2FtoBA+iy_`y>DumD1Scph z(EAMMTp$#Ao(+B52MIk#`yb@;q4RWahqgiFOr&69m1@t|xfgW>zZ zPlfgtn@%mP`NOdFvYZ|)IKuntf`|2=k>2L7+fHxoXD5c;YIWfeMv?E>3^X$Bx~Xud zu$eZpJnwbE=Uv8u6gpkU?SOSw=FI*yar^CsC*A@AQ9?N$i$Sj^>8x-x<`HJH*8Z}GmV@&qxz;SN zF^Y9oi01KhtaWy7UnQH5_a!B1cO^GGM~`<5#9EV^7N;PZC3a)Z-Wah_Zp{zfEbo(I z*SY$++0!^hS>4-bljWjsRh8VkCO;0-pfR&;`)n~U+&~-LUi!z?AH-I>wDw9HQr4ak z>%)mWVwtz^3br|=YP9OU$#re60{fqHRG;TK{Mv)Zm)cdbG{K3Sf3GGKKh*x;p-1YkJRRQAB;>zD^0u+I6Z2i6PfZqK7Kl}gC3E5@O bejn0ctvE_lPD~pd1DCPBgdT@} diff --git a/images/windowslogo.png b/images/windowslogo.png deleted file mode 100644 index ecbbd20d049c4e079a2f43f26d2e821aa9da9619..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5452 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 diff --git a/imgui.ini b/imgui.ini new file mode 100644 index 00000000..a4fb8de6 --- /dev/null +++ b/imgui.ini @@ -0,0 +1,35 @@ +[Window][Debug##Default] +Pos=20,20 +Size=300,300 +Collapsed=0 + +[Window][Scene Hierarchy] +Pos=20,360 +Size=300,400 +Collapsed=0 + +[Window][Asset Browser] +Pos=20,780 +Size=600,220 +Collapsed=0 + +[Window][Console] +Pos=640,780 +Size=840,200 +Collapsed=0 + +[Window][Inspector] +Pos=2626,255 +Size=300,700 +Collapsed=0 + +[Window][Viewport] +Pos=660,350 +Size=316,350 +Collapsed=0 + +[Window][Debug Info] +Pos=20,20 +Size=300,300 +Collapsed=0 + diff --git a/screenshots/bloom.jpg b/screenshots/bloom.jpg deleted file mode 100644 index ff2a343966d37b283d1dcfd4b321cdb37948f93e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62715 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 E0DVbbfdBvi diff --git a/screenshots/bufferdeviceaddress.jpg b/screenshots/bufferdeviceaddress.jpg deleted file mode 100644 index 0f146689c8db8c9775453438cfa3919201fd5d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60864 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¨KY>;joI8 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�M~a} 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

t%H^Q`sOE=cJRxrmqtG!KjOIT)$(r z*UX@t5wwoOTwHBkGh16!;*F>xqDeeUloe~7#>}najz)I}BX&=7fzB%~>MgkPut#0J zIrHcw?^GRc!YyB^PcV2KKmAGmJjcR;tvp91^1-|0=Q^>t0e~^8Hi@WeI&MpvUa6%t z!bX)@fo+3xDLY3FP!A~sC0`s2YHA9YDD7#xn?i3QxCTQD797K_YJ@R=v>fKLQbf{`u zWF9lFH@4sv0X@n6PCMWn1C1bDs(Oy@GXQ!R8Ap*@bCZFbwg;#@3~+FB-&CmWeN9DD z)pS=n+G>VR4W)`_M=Owak`8f@4nCS@H1BZnI0C*u7^~Ku;NnPC;_=XZ{{Zf_`(?W2 z)7P5n$sQVtYIy}Lj~X+`9hDtGMHv|mqrY-EIR{bvNt>x@?9~B$R2wCc)kVtlp zNq*aXdjdYXL+JjtzjTGib>arOQA|8KiE>3!p%^7($Sg(|8}op;1#(WAdiKAjjjD!Q zMDIyYb!C=XL{ADgC<6lMa6$dxVa6LFj&P(cRg>*rBibY=7=;0Y8|*;@XL%o5=yuk& z%(M{7U=ClK(yi*{EXrxRf2ZkE5Z)-Rl^@4>2qBqr+$TIM1N_(r3agS+-=mOCRRNkH z!KIIA0l++t2N~`8>rCm2XRKtBb`eCujA4#9Z8&Uwh`}C*zK36Gjk;tNDKu1*rugNS zGKj$XbL-o`zu!$*ya->iLIFOc{CfFO{@u}@BS9)I5p@j<&m>J9JTdJw;fOLw2JNK! zC^*JD@J~1jX2oKPTRag@B~t8A5XUiHn9pIJ3C=U^_R#x11)8>o?^uQwksg&{V$0@} z#9~5%c_l$S4B<~8WEUy~*-olGwFTS$%|6Y!5PyJwn&#i?2B`YpZ6#TW`N#1}Jzr;G zZFo_n@IgKj7S6%63=hl``RIk-ioUz2*5~^gYP)=}tg?7sV*xR%m0)n(2H}9g7-7L0 zFM5bcOu&y#C?`}^)6qfG-W+kqP)#*uY~(R`M1-tijy9-Mw*M75<&6%jG$44#SH$*vGE?vs#U!*KDV_+T)=`GQ||1H0>TFXZ_lYlAwW*{Bg-A z0Ao&7>XK4CSp@AoXJ4J~omSQ>`dzG5;1BQ&TA_AeAa(@p3%K$}YykfNhprt6ciIRep7T2&;;*{3sA=MO=j-Rw=R{kp ztGZLEE*9zt#bnaTd|1wRpelClbC9HD91z?A$2rw5x9UEq>Z@{FIzT*fCf7Vlic&FG!)d3)CjM()uxJ0 z5@_-QxBS^BgYW76nXJ7zTU%E4N(5TU8I-(fGhjg6bGx`+dB7(mU~+XG&f!TdIF8|% z;*|uEc&oR?LNXb1+z>(h{SKeHO5f4;9*c(YHAFQ{^8PWXmR;`v>~=Eb#>>hmToTwF z&jH`xINM=Rc!_{F6UdC>w?bsaBm8NtwDk8)L(;aIc+$F7J|wYD(wB?y$QaBz=VYWVNJ0)s$pb!vJag;YLMSDz>8_wk>sIeF-ODACP-%g0G9-CJ^g>aztz_MkH2(O z*O+P{rEiw;g+x;9dHuJs!I7JUVA&zEJC0B-cM3?U%3Jd!QK6Dw33qr%21_1%V)M{JIIW@6y0Y>`eu#_SP}5Af%n zx5#CXr&$$V>{dh8@D*XY~Y*#yc5C3bh}-3zh1JI<$5YWe5|<= zxZ{>Sox2Qo$2z4Ao2VkVLvc#UzHSx`9IQ(+!B%dgXb~Nmz_q_ z>0w1l0FnsLaS}U@Rr-qFsU0FkkO)!6be(y%TPkiBM6E>=OB8=ABFC8(zj&tvDI{f+ zj(Gs!jO#6Ktm~=?g=JKbR~6ba@G2duNdw3LILRY$ZKses=02Y4x4vH`y7djgX(WQ2 z#0-uhkIdO2M(%O6WcT}a(gu7#aL8M}&_EownHU4>+KAF^L$3qJA74J7IyGmauc7JY zx&FqQlHU|9EPgMP&O~hCR1CKvd15dISnv*q^mU!TXCzMYcqD@=D`R-tMi1se{IyxS zqOMvQdU2K+Rs-_XRgf?auo(ll|UYvVQ^o!LcO4ZqmM2} z=TtBC32s>y*d%05GtNCS4*8(h%M?*sCyEJbn|ht*Ntw6gj^jL$jAz^LpwL_*>DmOE zIGRd%quMFU5@V2!+d=+PbDWMjBbJP{7T_@YS zzY(I}4QA?D?9fr$EN@S3C>biHkVbZ%K~DR#Y#U2@%LfvvPqj}D$(lT^qC0ckQyla|iRWHAg|0O?w6-+J+7w1md$BLa5Zl1axw z^`I_oZJubMM@%F4@@G1U?UNN91we1Y~os5;)}{xMSZVgRIuF z*VNV3D_2`{t*NL~@YY#ocW}8UXe8$uB;)I=*4mDvv<%L#T>`1{Gi`8iapuAGBOvF2 zfsXoQbnkFOfUDu~n&-NYgUBUTYsW+P{{X7d1PVqO+>c_!>R8q|qX`;?QP=^EE84oI z389Iqk*Um`go7F6jt{ROjQfo-($5s?3NRx}9%06?u~b{DNA1*7=prh0ZgHH_@AS`Z<3P6B`f854mCYMh#afa0cPfgt&SYg(@Rxy| z#R=Pk*x>P=@BKY&p{II#+ydhDa(J~h=3kdI@nkMjx!Q~7U=9G>PjjtW)LU)j;GUa$ z^X1UN+ZEGvPYAVOq`dj#aC`y&Pjm3B-s#a7YahwdQwmClbv`(bP!3^*#>;S;qGh3{FY@2<12)#*jDWe|@%0=L-#Q(7>RzGh8-+s9*H-+AkoYYyK`>nL zmPHunCj;LZI;i$;p`Kw;HFZ>yg$f;{V!#8q43pR%e*Egp%P*xM+&GZ0U!T+OSK+HL zhQdfB^VobaX}0xmwhorL)Y0yMtt-Y%q~VwXxWjXjcpqGM^%K#yx{*f0o_2y}h=h~* zSd0VtXav^#jk=IiN@EaIvO-6Pf^jE_+;=$#zxiuQ>8f!RI$(Ng8-i_EATNqf+PUxg zhpeIC&}lJ@`Onh6Hacikr;V!CK!gF~oGI>4e^Lg0{{USrS~_yeG%?1=WuNf42eW#O zKgu&18d|o{TIA}gQ+$m@2aM_&f!giik{AACmf?BM;4pK_`+KJX7D|s?Z;n-6@Ga`s zbmL1i#6E}onrHYyXj&AjU4cT?%B%kXbU6e14LkK*QPx;1Da3;<3k-q@83Y6U59z9g z6|~X**MbM|>Y|Atp>{}C22~}p!y~ZA8D0P!9O*l$u8D5BO+g(*{uN=w^3*hVjY9I9 zm-s;^12_en9C4%vj{fV~r00Kz{{Ro4D(Y6+TenW|ppTtRS=JRa7P#g@(fD}@8m7?X zFdksTByf9Uf^q0|m32*ZUF!KcsKQG*EX6+xc@zc%IpE-T&PhLM`i7u2?xHJn(bZGR zk;&pChYA>+fye`pSpCW5_ZLwz&{0St4Kqiy)HH0lOyCy79tj!1=O z4g3l0J#kjYpS9BgX2C!Bg@fOKb0>UaVm5;j5=P5I^S7!VpP`m0Dl4TbB#DMJTMp<`cD2zlBv;bcN~mtngb*v1F(VRE_0c0Yl$bd=pAGu8L+jOrWBRqaz!Q*cnrI}SwhKM$vRte4enl>lW*nI|Oon33f*UY5;tZIUGtGVpNA zkblQS=;&uaQhF(9qy)Tk!66EaF^r7mPalXLIQr@ednNT}YM83(5@%hlBD)47jtS0j z!2L8%m;I`w@WWKKOh`^rL6XDj-Ti;i<61oFR?AP!etOU-cqGWhRqLB{)*72vQo_*+ zd*_JKQ$xM>n98f=m<`NJ6P`KGaB_a3oh$9}&#o@F`U_9N5RJm}>$cS%FiT}@4Q zqN0YndUN2lM1Q^vRf34eAmUF5;c>>|e+kBEY^kZDpKZEmrg_)`j~P(su4i0-y!=FlRwA?HDYN9a+N{T1_+baS# zC&2(8xG3lHGwA!3SMrP1c9BrvLhX9f9qbS>W-MAh<*XdE;iB0r>asy3YeuU8WKQOk~|i{Vg>_a zuq+Djh4CGkj@Jvq=QiHX530I>AiFH>L=YFrEyj#8?*4NtLQX`zY z7Re2{K_HCw$v>~qV8mT#TjECdD}+l|3$S*0&<((m$i~pXakvcQjO&_9>{XS*MvLm(^=}H}D&07lGeog4AS;Eg5 zT!zL15X09Vqz`XwynHsa&9)ggy0OPO(k0)hDsQ(>mZglrZ+XB}U<)t=5xDvuGlAPU z=f1Ztg`yEsMdj=UnCkB?+RZ3Xf_ZNPr{vbR*FUIOSs9G?J!r*4Dc6${e4ceFN&Qrb23%kDR)m!Wp5H!s1GD6Fg*Fe$<6>9ytv638QsAB z-Zh?lLb_1e>1!antz%m!QR1aG!o{1*pEo(}j1lYf)n`e+xECO&J-c=vO3t0_RPO^@ zs1f}C02P>8x>nm18LrHYfODNN*zDBSNk#74O_q#p^NfspRbGjSBt%L z2`B#mD;i1Rc%)}=IL|m4z&*3yoN9eJVX@O)>y}QL5W^Bj3P~%9mfzwrG8H)?iSUl> z9E=c1ULI<Rt97)l6{Zh*8F%=j@*Ogl8K8jFysb{Ou}z%d z5*&g7!Sy3{+^%wUjMlZz_8Dm?5|)&*u!cPIw|3IR@=h4@z&-J;JL2z6S?TDlw+ae+ zM20Cg15SV=M#XWQA5497zqr5}T&KI*=@Eo=F-7PVm; z9O|E}_-U)4Txdb*_NJJsBA5fa2i$6L3Q72aocfLqvg>vAsn{WvfCrJD+GU5Rtr_+e!UZ_RPN)D!bOX>g8k!`EAa*Y#LZwCSRE{NrxE+9Gt9c#g^HX%I`x|x(le-WLqM;Cd+G_HiL!CuxcApL z8p=z3IG(biI8d-qU9bs0nu6Tzd!eY7mO98$G2zVcm5unxF zjiBCYYL-MNnvUP(`B3YX;+l{(JP+c?0UJYQQygIb0GHEA6&9L%tz5NI878P>xtzBo z{{XK|7rG-)W@KASEiFC8nx^MYy9xpdx-$+~7T7}`*zQ5#<*%mfP^3(h?OqC#zIk`| z?~L)zJ89ESBF!8?nLnLzjvMmMDlf#h9^Ru&IhDxf00dwCTX8Y$STCYIR zgrkcq=ry*ddXZ$6An*D0t#x!3?o@&@KR%RdKA)+pxKmAAJk!v}0Yrr)E8)mbG9E@r z9DP4CtncG}1wB9PTRaI{Gb05O)JR!yaxlF28%}uaSMuBG*K9y*E%@tVsJY8(s-~5r zdUtj5j&j>bIrPTg>#DwudQI{;%mF_`LE5}qStFPu;v$-UAJ7VGY#^=(QLDXgb(Ym+ zH)R7lG%3a+iFD|S79_!uF)LuBL*19CjpP^uG0N?j)mg8 zffNefEV9Ifx1V;vEO0T&C%2{sh+dGjTzge1Dig3LBc~(qtRa;J14TNE;vL%kXsEU= zwp(LWCxBO&wl-nI?wA${JTLS=B_dVBPQ<@lkI{BIOO~3+PXMv`gR+{ z5j1eJ84A@z40u!AlaxJy#!uV6prExS*0wIBsG&5Wl!zekwnPqc9y5|a#?gbwBTcOr zZ)$_Ujt~yr`ev-UYo66!$OtE&U-Mb@{>f3(_Ej^)#;T$4tiE7L{v3cXarX7q%Q>ht zN-9{pj19!+I0HY|J^9pqXH+gdO$97WXQsC%*CdA8=ac5{-Gb!vLRPB3K&C> za;1T0&M-(GQOLNBzj86h^co9({pEBJym84NhQ|d# zA-KW8?~H#BJnL!rU2*7|u8g6#R8-MZ+$D*cscIcW%^)N>V#jdL2RRw%2ZN*@Fuiil z@V?M0g9n}?j|=UVLPG@uxnM!# zwsYz4ofo?!NkrDzrjNOa_9!ic;w&Eb~`BORqx`)~6)lbpXs1iptx`L|TTC}xRW*ezyr9zu9>;mMH8GupB;C(x5@~w3-rkJ7at%Kkt z!oCVwA(G=F(MqIjj}Gd=)MO}DAYg!a2L~sB4z=+XnxE4p`*Xrmx5t-pPa3%WY7|}| zDI#b7S=jf^J{786s&2HC#Y1hNq^D zhMr2zyQlDQss?xtN3VPjzt>)M#V=WP4W(AsRp2T@1dAlWF>br7OEnBAx7X{dlDZI2u#BQt;eqrGuyu0-b4(AOQlA zs;Xwmi+Kt@y8RjOuc9s1PMe~++-V?=;Upq?C7wUtAt0O`#Q1wdV~l4RJ@7v|T_f=7 zkJnqbR@8`XZy>wYTl@pKOk@uE9hGu0q~{($q=Syf8sEeo(Z*Xy7AKxq<9jULtr(Uy@n?hK?3#rBYS1zB5=My=J=T;{tP zTKZdbw!AYt$xP^DP}l==9jr+9A70wbeihyN#B`m)s*c(cDt^#fzs;Pt^f>ev%cAoE4F&)GA2dao-w+J|o^~-h|ujR=P;&l|*ac zGqd9#iI|g4+2n6Z&dj_G%n{59zB)9I{JDX=8{aMrcpes2o7_AI&Y? zz9se5d>##5_G2DFA~<7+pQcek$LFX%9rYC*lc`?mc!}!Nz+;d1O4}KvP4A43c6fs$ zpEuO$g14t(3UH(J(zWi&X~B^OF{4@i3yqdgfRL_$52blGPNf9_L<7HnKDDfTCb?RE zcBIqE411XQ5N$;B6NLw|dXFgWfHZGLk2 zRY6sAzDX55#v51?r9afCGtA&Nt| zhVx*#I0dp0t7KsFkx|#I+HUG5XXWdG`RhX~dh4p{?a9vb<|i8fJbPyzg#Q4RoiE)D(~kworMFdDD`_JRRVNZc!HezK*cst@1x7QD zd+Wc3zYnf8^fb4M#)wlvxW^kmnWSC;#?m%}pKd@syPK!EZKpC__J`B~%0xjk@vV@8 zuiH@*O7dR&X03)fbG}FAjacQQdo=RGF zjbfe$RhCiQviW&j?IXBgz~HVluU%hqeyXYI;U!fx5L5VxQnGKEWZVEMNZ6bf{3;Iz zoN=pnKDp|8cv{PJ`3i{IG)U!%2zf9(=0bdC@x{6-B$V*D z^AH9;-i$lrjdyZtuA-^}p^}v;t4kD$3 zQs>xyXOs2Ly03@)JknA6n}md?c$^=%8~N z;RlsfM{|w`)3>i06<0-gtgD)m=R{!KsUV3|$t@waC zN?TkuH{Xq*>fVl=*ne4*PNCUD(bz9QzFDeWRr*dT*#| zKW@iW1d!7isLd@~Dpa^CRYR)e5=iHacjvh74xjnQ`I?lCSd1ibrXz`c6-S^SsqRT6 zol;%)4nC(;vQj|X0QpkO=Y!61132qHo>c|IucE`Hvd&L$4qAu%XDGjz{9j+DfmQW? zP){p5%Nz)C`?%m;qmNC%pX;Xe>8O1Z`fJzes4@6Irlo277WPE>=j%aYkZUh>Rg|U* z$fA+3aUfl-ociOB=rytYBd6)g+XwA8tBQJ>Gzm~@)k8tGKwb$UPzB+$&JQ3Bp%UrS zv0#N!pSG5%-loHOig@Rq3>-7WaN!RPkFGl(LxaH?)VEAGs9ToVR|((zR^dbf0g0#I zi#JOIeO{H6ww0}&t>!68Zi-cugS2hn=bSc1n7`OteACNENex8P#CE>Z<&U@GVn7@)I42}_2UV|s4!W~-nx3xbLe$?d zw9r20kW;gfv~CI?ldum@!<;TiD@4}V(^8f~r+F~6-dZEPtiZNtK({?!e^ zna?u}yL4c0SyEx9?2?Y1;p4zX`)~I8ksF__R6%`LIP=Xf# zCNc>J@aOZ#rkrf{uAQ+}J!L(ChTxCS|f&BexHfyzw zRlur*6k?=1Y$qP0INUk>Lm>OiEqFw$Hs@)+eiVw}gY z9&if!j32rRYm+^HyP+R7Ms`z9|jjN>O}0a1+hB;yAI(Cs?z-9!aSRo)}Y zah}mu5t?!MZ)y}>7fDlbs**ZWOeJ)vnY<;&8#@__s9m7v+!8i}&Uf)stF0GHv~k$n z)?1~78ng=}Nb>Rn1vtY2k2c(p0M8n`)pgR{WEPusM9o6LG?KiT+z%lT3@B0vB#e`s zXSg+KdR_!T0uR$f*|6JB8>@nNjKJ6l>))o-V14^*k7+*m@;`8CBAV}4OC(a#QPIUD zKm~J5vK$Zp04Cs19sB)s>C*jW)K`m)LiJi|DXF7j8ZA6=?am3|6ld^_S{*kJ@_$W1 z(v*?4WRt`JjhvJrNXq&gAEq?I<@;CLQQ#6e#zgn3=Zkd}Ib@*mlB|3xdxhhvsOr+D zdU&O!cE`)prde`$^ApMX@IHfGMR)2ueDGCW7Me;KWmJ(B2zWyr0uFh`MtIX{WeJ%f zBoE~qzw4@(I#1dVig$}-JA*Mj+XNhAN)vL~gsHFxGxqOXsTYkZ5~h*{#P;pqzVxBh zb#*T+imXEVX-}q_2)c#EbEt_SW=+Qfab@@X^!d~jVCMr%T{BBTAH)kwTtHa^aGQD^wmh;Hb_=v{{U>~&SE3;yiYS}AmcUS_j{J8Ye_ax}S?jIT5mUiBLnuAV8Q8`S~XL(4(2;scN_uO=vTw4OR1%+q=xTal+iMP zWGd_=3&}#^f}zU!K)@r641&4TwU^)ap6L}-kkl*@hJ$jEfZMV$oMXzrr#U?73N4iF z!kgm+Bx9(U80cxuy@)S=lQwxBkQGZk+gL+@~m||u)y^e`N~BlQ{2&GM z3=^K+&mi(I^-JIdS}D|=NWU)+e``5PzC%S z(?>8Ztjds8#1(=$NApSaUk@WoaZUyC6OE(!G=)9p4_SyIXpQk zo}g35k)6xALll15&dQ`{5!5Bo%bv4cNz-hCv)-0O>)xZWwD}j)GdJjz;jzah2R1 zuoR8tDCDkl#xt$#(%0%%@|QctF$X_A{OOI1CD66{P^n?0rs-Rq$*pxSB~cW}QbMSk z!1Iz0-zmsHQMBVHQ1tadsJC(eF}X~EfWx>W@*|xz_5F_hVTQ+Ss;Hp(c7dJOaX-9| zXe{RgD~DDC11BK*5V2vFIp|tgPtGfzqNp_X?<#_1aD zO(AzJ8|(+p2|Ikp0Cwc<8Nk$CMJmB=@ryLt}3k#+&Qx z_4hg$Y|5224OGfc@4PfB2`ulBMsc4qjFNfIN!LBROJAx($y{wcAZO*johiNnPL-q9 zgj%jvT5!nK5e=h&7-anb9Fz1M>5*RAZNlKv#T_mIGDzD1$;Uay4tT-mJZrv{vDrFS z;T$#HF(TE;Cx;zMR0@dlakY04rC6T8t`8U_kTjw3dQ`o2k+)momI`WU<7$;FusqDU z&fND1RdPT%-RZ3m5qERIi3y3~#@>hd^rhRH8Pt7mMIn-&)j(w(gwq@Y&!UblENI&Zdg%svpxgZXHO`!@(5gZgRHcNTWzfhnIn z*6CSH!QPdm>RPMUQBYOg)|LSnRh8m^N8&hAz9ZxKh`>0&!0a`p(bv+70Z{||%wxBJ zLzB)9G2b6fc+%ta)C7JH>81PkOauk##vF6ozMyq(((5(z(t+L(lT_>QB=_sAOOEl-nw8G*xtP)TLEljHARt zRig+&ZaW6;oqf{1Q`B}V9UZfzY3QojXJ!<&QMq(E^MJvZ$_{+YG0)g&e^*($md9Lr zqUO-0MSQ^}dRHup=a2|1<=_B2_8BDLkXW_<0C8=pNlbzhB*`PUE`0aeoOnmVc2*a_ zxb&5d5y%a)OrOKIO`d|TwQef~rjp()`GhD+c4m=S;m+kdS8qHKud6H;>k}mO)Y3I3 zWtvc8Gcc4W5s|@Q0(go59PY;?jHGn+W%YWp+XWo3!5qM=2rLTzA7>7<(UaS&EzBFnlG~sql6OtH^I3o_txPAFMN~zka?G#2ho@i48 zoVXa%ox0CmaiEr-dE=T=yQYdduL#b+{ZbHu4g)S5f?2sEzJ~Ol!~T^^Yh7+})YQ)t zvda{$j5ur+Dmd@QbNBVr+k2*3VNJZUVB!W$?ewI#R_Kn6tah!VUJJi8+7RxiAwT7 zFsHC#?mYv&KLnGdCbh%eZOok}(Fb4>r@Dva3e!2i(HVIZ_e{AQ@Vk7gt z2{rlOs#*zpiZMd!V|Z1cVaVJWzv-r~ldGzZXw{tU<2n`96x0%YLc2AxonWO|5mq7D z7$WZj>++&{_vG~U_!RvSs+5_!fqPjGYWdEKtf{wENhK(zX;mraZwa%O&c$G? z8*w@JAf3Q)fm71(IuKBDJ+*74t0lEaZC4tJmW5Qba;%uz2PIq(RHy)uP6);`*ai2D zw(G*`+@2u>jlqtc`tMdk^Y=F%&>wvH53m(N$1S#kiV-Z4p}@{a8fSv-V=92vLdDo9 z2;0cdsm6AWKM}wl`WMrm51_qQ&2p=us#_^z;BT0u!ZDm3m?efW+=Gmq9>+IHZ2EwL>Moo>q||6!i%% zT!0&bn8_dy#h=R_x}MunPPFady3Emx6h<4fjP2xr4{rGHrs}Sco?fYnzNWGxWu;#d z8Z?gwfBe||N)IFa`PUbFPfp)zW38yS$wxIjUoj1C6+EDheL-G(9tiKofEVql2|*-{ zZzS%EwBJXF$!`(8*!fJI-B8qcbdBtjBmA))4fB<46exX6v^5* zFky^f@#;9^Agy0xs{a6Pe``xoy6FqD#*&@!ssqN=$SdW?)6PlBIn+xb=T^Xh!{$u# z&O*}K;q}kk;TXTs~u@TI7Irve(h%wxHd!#Agj^j}sA!Lk22bEAM-U6I%MZ%Ta zxrQ(X2O7${hV-|ZT3UE1f-13RWt1@h9AJ!h_5T3F>!O`)*4;yX64Hpcf-grNke zp1AFv`x&l)?xJ*?aKlLoeOZup7>VUOh?x`dpdzeFD!A=56V=H~hDI9*P*{=xcZznupU~XgeU^Pa=@aL-+_SH2d){Eu7 zVd>|Pv5ZtQG2Ay5Ey3Imrwz#*n?s>(@YF>|R}Dlp5}m2Ijjnn8EC~mNCjjIQ;A?Gf zT4Pd5m@9+<2??132ICXAJ-bpT7QjlIL6s}_DDF(oJ$$E~GyGhq>Dns+xuslCRMsI% zb4sdOead%m2n>WbEuJztAniEi9qOizltD)vQMNgduIA2tGspBg?xsqc_2yZmo_X?c zuQAJ%94>yi?tM;AB72;Y$WUtgw)r%gkT!TmMy>8N{b{+fl? z>|EU+41FtHS9bfKP2BFZGs}jgY2y;5Mp7`xINhHsZO(DX`=Ds5PUNjIph(JF^zjaPyCzw%`!&J1|B&Va!S6c!_*(tcLWd)qr7yT zFH&_CKkO*#r;=)6Jz=V=W=dxziz#+ok_bHWp8R()HS8OGE`^Yg4*3Jli?Mhz=MgcE zvH9gn^i(%`N~o!Av}S#(@=mdwi2%tb9n^q8<2m4S+eGd4a9k-oXn`nvtgO9`IUipA z#~PC9(-QR6%6dUlEhSSgok?&8+^HO;rJ{L9 z3`YcmpTAF+C^EV+beJLqTqr?(xZ|FGNdPPwO zf9X2AbkvH}wUqOP^0AIU*gnS|y3Rf&d_1_&P}N>&+9ORq(P$po7i5Uw{P`GLa$ z-EtQ`ow?JUxo9XUW4F~=Vxl<1nsyUK8QcPm*bWW|Y?KPPIXqzA z=VDsO>SD&mh?WtB9N=Iv;Nb51XF%vSO*wTF5%cxPtrNXh_=(h1cfO@tot`J8Uj#=d z?v;E-Ngre5C!L-c$>+=T8lKtlFRrcdcodiEygcLyA&yc%H&Su==xvh9Y5E8nS6Igfz;D{G^wxx;r9v=o-;t$0?Ab`fSzUC z0U&KB9QOJgb{vb}6>j}x-6e0TtE-yav&>ea1*nXp5}6B-ihFPJ62pO#PdciNCFI>D z4Y&eTf_9zxW>3d@EwG!YXl`h?#Xg9)Uixmf=WnEfON4NlT$2xAZRn>Tll5wDRx?F|h zP1brqF_Eor-9ecZ%{4cWL6KE1-9>e(zZ*4=!H>W|XP#8umLoU=J-^rAirc?b$8e^k zh6s`N=0|AvMCa+qBkiBpzKm1ZK2GlUI%EUrBP5)dSQY+d#+54Jlt4lO z5zl$&G?jN&?eK=%3IKE7dC#x-tKAn}WU0D}TkQ=xR&Ct}jfPS)$U&6oO_q9_ ztz`1h+~_G1fOgsN%hjY>kHoz<8O43UCK z@1DT^O&t$O)?GbmWx7@P>hPZpW1U`9Z$POg)CUCeTNwi&jU~RaLYpq7Kof{3Zt3|? z*1I2vk{Hr$ilA{Mm>>I(*XuOPQ%OtG6g0N_C8eaZDBV>(QWxKs0Iop+ScW{dN7KH) zK0jI{eM?z%w^BS5Qyjxar(!Q8axy?908TP9+fM%gAFfw9;f5G%jF$M<4zjq8O~u$} zX3@A{5It}*G7A%sQB$=*5`=aY@Gt-5mxYAS@z}_R3Sgw0#2ad;Bjk3U- zRI-g6gQs9+2Wb5@zO2Q)%0d#NnD|mhoKwpyS(0mTyIy`q613D6RnwIP5+pkgPC0yG ze?299K-kEi)* zMW&Z%%EF4tBe(gjQ3*3v*6~LZ2`eg+IKja6EOI*m$?iL7y=*XCD1~LRQq7FCY>bRY z9ED&GIUM%Rtn_WGrXsmYDY{a5;iIQnY9^L&(uN_tv8FuWW4UENPT75GdtX;{{Z+n- z+f{6uIK$IRQ39b>AcbWkcOAjUIBa_BTg%qiaB!r6B1wn<_7Z+{dLernQRqIPwe=Og zqog{HGvP{780GNU1IVXnR(xZB2%zW9fDTFX8Tf~KkEN{@QO!X`J+hS&I4M~Ol2$58 zki_8Ohd3DnC(~ZrEf$`L>Bua#v=d!st40#VPZG(Ij&c}|JN@*D@uq4D+l*G_xztS` zhOQ@e-yCEi*|od4AmoxyaC6*&t=VSs{ChBcktFsp5=Tjh`cn%AL#~7<$LBttfA?9C zqLpdtDrQP}o@qA4AYh?^^-uugz6ZCqG-BDX(-MFJSneR_C(!5DjB1_E5l2r&NNzIb zAX^6qb~x>Ve~SQncI~T{#VbfM6l9a-B>w>I&VTpUV(_wx?#j7A>(o_?#z{+BLK`?C zW2gK{<-X*6H>hit2dzbx8LBdvF(y|a=L8-GbMAQVG3xp7AlWH;!si3Ver8COsm$TT zg@$*kZsm^Ao-?fO=_N<)+k7>(pV_92*%)n#-2^BWaPpfQQLk%lCe3a1! z7@8I;;Ry9p_4Ne%;Atm|8cogHw(4IG9y#{q&*e1*idlO?ZL)GRusqLCK6U@uwV~-n diff --git a/screenshots/computenbody.jpg b/screenshots/computenbody.jpg deleted file mode 100644 index d238b6d80a81c5c7c8bba4e1ac9b64316e4451b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55445 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

5yN__4=v*BIt71H?nwYe%ltzZG!LCNzA zwy0bwbFlXS-F^llZWc&Dws_gIX40ny_PpUpqT_&xf~@p)uo#|DvgT?4wC2_-2?3Uj zZ;4dmF5J~KdFr=WX|UFqn@*l z4K~kKWFPtL|0XaA`nN{=ul7=^pZ_HLif)GOHW)$esAId1NL|MTFHG}D3IMVinpW-|?8(!q%vR-@H+`9p~WTe^BVe)_i z`JH(VW5m-=IKC-*#J$Ds^6xTLbMc)KX)dF@yVkW2@POMqcT@AiuyjaUuR>uV9r1XF z09=F*)w6^nW-(zJj$RDkUj`niyms`62lI@Cgu{isY#%g20>pX^U+^*gLdF4Oi+46s z0e3UOF@&X!5L&XWj;hcrpn_g^U}DCVB1R|?+#YXvXkmy{$Ao(Ey&jwQ_qBEAx-hMW z*QBJzoqPZt5oGu5n(g=Z;b9&7mtlj5ZHp74XfdBUHwaTE6Jq~>& zfQ|z_C~w+AWngK238Kma?aibImbT1wCc$uZ5ZY@TQs2!Nxm4fV-QL6fV+p6L9~&@) z1Eg*;AO=4+13uGZtmNPyDX&w+NK9Yko0_Svt?90`TY7uH%@*!T{|k6IPLt}#uL1%W3pztxp-Jmz8G}!wp=A| zdy8DX(Y17{xhvuW$ac=s{-Ab382sFzFZS`^%BtRW9Q_qhMLI4bjN%(rzNgKJ$10r3 z>SI>r66T^TP3d)Op)@r$6Ofn=k9%+JPv#ZM+UV~{3cB{XT>7420WIppM)z8U#^LeA zTb50oKO8{JcFRWs0m>mzzx}xCZ5jo>>stQ6ML7(UxK3a350@;Y;;yw*Lm?bl3GnzX z_q$I`VMw}R8B@3AZvpn$zXjv}sgv)LWZVOx{`8ORP3~dd>?6b6FdE_h1YE zAJ{Z#AJKiH6kP2F9sCw2r@H%c8{N~PmOO8Rpo@{sgnm{P`nZVUXWliKSygi^Ez(lcD#XRD|WxF;m{un$;2!FnC*f+dD0f$szTOAfKH)_f9-Z!>#Y9w70lhZOew@%9EdZb!L&#HMAE=*dw0 zWFK=C#z*YnyFZ?C! z984e(nxmnpW67@qs?Msd3gvYRi5KTRGnQcP^&o<2_Fzc*D;l5*C^wi|IjhLe59uQq z!^&0H+XSSgfBUZj!>fur*@StiaP5zV4n(>9*iXMKXa3dIueHFF=n`=lejoikZ5&WV z&4iQe6zghM#t|iWF(fRW4Y|AWu-$WMzsFT~YDG$-&$)le{D#|9*Jh4XKu=$qeP*<^ttIp8N;g(3m;PGpYo|ezYWp)b-lvuNJ&nMg*IE)* zzqBW45M*iY{YYiNkij2JCp0(etvyCtEv~8Hs-yT00#&qvo0s}Ox~K0@&y7pIMyJRc zrCQVvOvv+7$5eC2TL9gzeRAJB@-xL*r`z0u3G#M5QQ9oNAxRN=&b=zXRpdY1R$o~B zYvP<(;+usnO=J@6zP#h8?<=y!FFk8S$|sAny-JGPICXja(4N|%WwgGI�J{$t@p$ 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%0DB&}G?n*^!xqeo@bJm?`lVuErBPji8i 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

PYmC*CkYTPk}S%0Eu&UoJ6U zTPoLfOW3*Wx+koSwwVG-xq=ut*%{AAWICY+&Q{QvT9q%0uVfkjhZ;dghx-$yXp;K1 z^mYCo+iro0<$iKf_CsxtBfK@eo(b8=(&BShwcRvD zYh*!9vO3&9>ibtIkRr+78kIaX_Kfb+-(VI zM7<@sUJ;kz?W;3m7+(A^mN{d%p5NoJC{~weo|!D`_sbDQ{61}ihTNpwfT`<>pTwpA z7HTM#x25|&TLDm2`PENF6AC@^;&k|aBA@10Uh@%x)zFaJx)sf*sRZgSmDsD)?Ij!7 zpYrom!G>y{0!gHum+Py4N?bT=+ges?PDh=X0Bf$c|V99G~GIT9>wWq4eH^|Q&`(Gf=jZE``P5UVrv1|B!DpdaH zBO6{jg(4HJN=}6*f4e*qo{7mF6aZ{B6^X44-4~@r&bpxAVSt&=1yL?uycLbO5b8V_; zu$nQwqIuevYTx2bDA9=*ArJe10k_%S!q|H*zw%TG0kjbp6teLko_YOr8f^qKoSi`r{ znU6(vne}TM4wK);6Lgo2qlgvB1vQfKMZn97U~7pw-zi-w!zcU{d#@-YLr7f<_6_eP zm2}>J_18x6@qDp=uiLs8<`;1=dR$gnNoH@N4@}04r+jMcCXv{7#UhVi-TH);u*M_S zkLFox9eFZ+;HU|Cb-qhvCDhrAvmAiF7;WKjS$cIE&Ef@zcBj4ajI3xM67yA(w#@IC z(p=E!+yma`c^-F8Z>fB13M;7rVo*oyMtX83$-*ZMitj6mdOHRk-N3Qqc-U-BX!H_D zu6kTVC;nA z*udGrw&YB8LBmk%0JLY*)&HBn9jjG-Vd#?dvzOVqxu)^ciqoT&!qRNI2tqHsEj2lT zB*;tnHV0xja(G-sVwOLaQkXYBbdohy{^am+4rc{HlTGViQ-x%amX zVZ!Qe$=&lUW5OzpCb40vjtZ;M+1`;$FF~bWDt|ye0!!5Yh9^gqcuO3bI%=+PHd1fasA*&K|BTIQ>DL*0aXM(LI)gVuT&^1tZF0?Knngg*&WQQQyyno0bN$*JV%#ib z35}v;<8=o)x+eq^c+3@Y5M)v$pLOPnu>nFik2iaZA+LWf6Ne*={I&USs-4s!tX-(! zeZ34Xn|zw*F6(=)*(fkPxI7IozZutA(;2lZUXTBzZ@Po2N zG9d16&jE{Tkj3Ty(9m$v_$hojp1OjPv-{S(qbn`%l>xq>@h|&5!#WQJZR(~U--}X} zPb@V2dKVR{12#x>g6*z|5+iKG7HBI|PBKP8AHIh8YS2bqhffEO$$^Wdt)aj{_7=$l z8mDH3$k3U_(g}_i40)<@%0ksPRiD=CG!ogwmQXV}aS&5ZSS*>G^2d62zzqL0%Nsz$ zw6nW6cGD)yfUZYMu17-0@V$ZK+Z8X2t1AZ4oz=tF(%`t0B;D_BDR15gO$PrlAl!1c z|JS9F-VxFp)lwlA-v0V8-NxgheBaQePCEgY`fA{87@hh+W4xKdIAr^L3Cw0t`u4|u zyej#)h&Avk8uX!I-4Gnpb358IJX>FUUFC%V(JUiv$Y|q6Kj76tg?eB~?hdClNC8Lf z+z!Y%1zE=I)LEE&4F>*@WP-B#%hfGDPnr`QtH{ch&i*)i=@Lpf@8}STFW2^RT_z** zP^Q8CL0$Z>$Uf8Q=fyB$!z+i83j;pU1u@fi)h)hSxY-I9rn=(PJW9Cq8$_QUg0z<% zP0dd#bG+caUgN2Rt1VQn3P<=%!QI(IOMV9oI=@>W&Jy`^;dk+2h5oPwZeOl_wkuLaez`S&-6M9qaA6-k?AVrZ_xl;0C&ez>hM zI__-x_sxXwj{B5XH7AmdhesMcG9TcY25?v+VkONwZwSEuvj= zI<;;7%rn`w?@yTk(LG9KQZ6Ue$4t%jhsLI!{WV;LyniQOr2(b zYw))ea5gAVUjMbtZ&kyjiHJ5`e$FU)?I)lKyJ~YweM#MmPg4Dh+x}Z_H4jUS)e2J; z%R5sJ;Sy{0a5p@AQePKVR^mM!K6)9W6iMc-)x7tLe}Sr(*k57pTt5Xc}yuzk;IyKGf_O$*`_qFgv& zPpkoLC;>sn6r>VBM>xRU<)$BFixrE%^<=N^+SXSa5axEZXyl_@GL6$D*Bze3O?rl& zz3rxd)bY`(-3SPc#9pA_Gf(OilL z=cXIy8o@tuSSxkEHFo=5Fxd%Et9~ic?XYn0JA?5eGg5V3g`(IO|1cDGTc6L{Ty^5C zp!}uso#yiIKzS>yT_zv2Zyl9ce)c!l{o*{Jq_6%;u{;)vl&S5ONG=Y~o7%K4(QsWe znilDc1{7C6#ZcA2V=jUBPh%f{J=MY49|tQa^lfi9nSZ~o7)$>3`)SD(r2E;lut#22 zyck_dvA7FX5{MNVdcFTX01)ah)5z3OqUJlEv+LK`!Ma;1(>~0yYXqQmo;$CtH?4g( z{lcrO?!q*Hruim6i~iq%4R<8L`T%y+8u2lhOrA>ympX0##S28gd`>;UasB)E-xbCz zXdjAIL#r+h8bAFzSQyothD%X%qp8|qQ@i=r<%tFxOlfsXzsX<$XYQG>C-J(2nY<r|uSTrTme9FHO!RQbklC!uDn@On#_X zgT31;g9EcAPH=Xeeo;so2?lhf)Ho%50|q0_vw{@0VHd8b()Y5rLX8ue{hw!Bk?wRF z_2YfZD+j#0xzFjc$lpsg;uRu+e>V+;yL`=#`Z_GKNfbc$&LX1_G766riu|9YpNDl7 z_+;e#2D*Qqp-3jEGLqOIZP3BD1l&XIb*7w}cu9`6-~4KU+a24e2s0OqwBY?@jb#us zG`V=t|L|f#@#WpSlFL#D#T0#YABLa2TjgYtA%2N2*Yg<}xr}7lyJv%1*1Obuo8E#A zII8Q7+dHWeuDTsbksK*P$%~D#4DU)<_!&PBS_4ctocE+=UrG;B=ZobpNHiP|{u=p; zJgk!sv?oVC4g33aJ~0I1@jj4>^{i_qO5U4@2NzP26f-SST>}+~@vo zh@P4KVybvghwClewr^%#q`}Y4pKQLt9wgs;KP%6x2I}jm8x6JZ?PznsRm8t%mHOU@ zB1VMDOP|knz3>WhtqAHq?2d2-P0E3{$fPHp^nez$U$9edqDHYIDQ=F{X`q14bgb;1 zFl2l#2Zn$C_4=X32ZqMb*)Lf2xf=$5_fy1a+2ayakFfpj1wPR$_9r!NmojLc`7J8G zAOL^fG&8cQEd@z?EQ!vKnS*^Q#4+}SR9or$Lw+~(J7btO?Il0HQ42Qy9B(fN1U$rA{4%L_;j*$ zT2{*Jyf+U<(nLGyGnne`d5VAlux{E03Ewe~A-t$Kn_GaB2b@BYR*HxEnC<^h9{c|; zevjksE+}QROqPDefyx_D&Oc6X^IX9$SC^J)#n({x>P#zjgZBI$YrfaUu9N639@_

uK;_bL4M$WuC@ER(dSmlV8)~N!~FHY{@mi7v5Js z_7!wcK3yWtN(KB?kXFU{y+PTxYvCa8oDO)y>i}`RmfJB>s(Dfj?;jnB*;_$`Xwi7PoTZ(jgd=&fY+rjKypr{qLN}uL9oMEg-d-5 zl#ZJ<7b_n}!_kW4$XMPDAazH%Gzx%)<*g`N$Qkg^A_c^imu)B4=Rl?opSi}3(Q^G1 zI4FnwcZv&rlyBGa-`zD+bg=s>G0diakhwqno0zU<@}&Ev)CGan<)~Q-A_noI_cY-T z0moE;8&3zLc1ziYf0)m`$r$A`X7q@gHqq0mZDhYq*jk;NsZ#Z)!D^Q8ANXsO&SCaK zvCH3$+Ex|KoHc4}c5|?W_7Wq^YUVDq2)4<+S`dRlG=5ofjvyTf#^@%+ncV zU%eAPu3p@Px^J>L-2#+l?j}XPP9$>f^jwmh0Fl&j!B&&aVn@)hMa6%re6q-=s@p10 zGt(S|8D~rr$>-2M+WcLnOZ`av4AWGaxRk|Zs(S5bWiH^<)J45k#K5p1Nz8#L>yuGa z4+4=_>&f+9l=T$3^9_(nQ*JfnI}FC?wt^nuQor_zWmk65bI%~}D(Yo?>U{RH?%Z!r z{@%g!J4}LmgaEf~`_!OY|B-+AH@!|TW^!`q@#XH7sI^Z9g2O&;!ITE@wFC0ug@+zY zx)Mqs&iNmji1hDK@XgVNJ9M$LPSOF`)Ir_BHcX~?LD}Wv$N1=uyRSWSl2p`h?-E=q zKLXZCOUOepMHW`aBOcgAO55upX`yW_ycHjI9BZ=)^-UqEEjsF^yB%3 zVAIvSplb}yw)xoZSq>q55Gw`3Lzd`zoaV>kBM#dor^`6w7BQCB+rt_8Tu*kAnT2r&~Pa4EHzV-yt zmFBI;QkBD38^$8zv%fQ$bk*?HK_lw~AUU&7x25evoc+RV&eP2!?-Pqp0PViDiJc6m zb9p{KRkBqm2Z)d34s#;?uvJU*9)~mQd0OI>%!A=_U`1OrV8*KWtt(8C;(4yUnjJTM zh5fb`+oQ|;MOR;7mn3CJ8Yi>Qx}uNNkM{zShU+tsAwgNli<0ZaLi8Y(^4YMaZv#pW=U{c#olufbA@S_G zUcpSS(RL7*4d;&;hhAZJn2v(gHdrc#!7=SGjlQ(0*LEU>rXNvoz_<1;&S;qJ8!VX) z6&)2`q>+n{30ZM2&eO;bygr<&&)V8k(A=Q+b!o<<8xdJAsYF&Ce4QIXxo?xUJ6nwdXMy$Q)fD2FTHM>3gNRCFH$wjG1h!Af;sEkU%_p z9%{v?p;#u|Vm)UrH5Ey0Jf$N5ERRs*Jaxy?sBxmY_)?IPLPk}A&{fwM%GRa&yn`LB+yLBg8tM@7t#SoqDIAg!Y%Xvc-A@*QS#%#2pJbTqH(g z?zsMIWobxSw(USjJ#Yp%tym_l4Z8_UdEQd?gqSi|(g9bfXJ|c7Bl6920fab7bvSBk^ON zeX~)zr%$_NTTeHYv_}gmz$AVwb|4=0vaR+h(k-_MR#sRnwImSc5?mY~&u{xH5v?|4 z-6LD?*EE>O0Jwy&mcb(+1z=~9(x(`exMLt`ARS5GycFNUC6Wg`Fp^L1 zrd!~;ru?+0%kvyQpGZhQFbBPMTXhT@Hm_TE=zd}l;)|FAau^+w3M+ZT$vl#1hZO0%%y)E;ido!FY%3VR2zWg9 zD%Q(Vbv61#4L53Bg6hJO5S_|M2jBCpv2N4%+fIj<4&@YrhQEx{i6^44F^|KH)qm{6 zsYZg=jk|Gzl_o+{wQiu{8RRJZzIAr-7g}C52Z#>4V}3efsjyrK04EAOk`G=@Mp+99 z1)$Ise2uNH4^gzwD{aJAktb^M*6+bmrY~2#$kZEZ3@=5I{tH8PsDKy1cNvaaTGF*y#Cv0r1ia~ z*{axG!Xb@qOmw9QY^w@iq@FN2gnNa-VM2*iM>Gc6CpWL%65Qr(}BqPQW!bf=*yhzH$soW zW5b4r9f@$ng4gpSOZqK%{*bcTtZYu5G%8Ek9#H&9P*D6?_Zh60Ew^bBZOp<^eh;b0 z?RlX^DI|ly{3p}TQtB}2e{voxN%^k?)O4o3(%x|)}fvf-<8<0FtRXKBYPBi3qq6=YI zPVkfz`U*XO=cih`cxk6&O|!n;j5O2mxC+RxImX*o+|IZ8O6#s&hK~*<*kQ6;ximR*&Bjy0LU4MVe&coUyiIX?IUAD=Zc^{RE*`BJY;x|by{&TZDyow*qY2ZNqDs`LwfqDHK8HO}g# zLP9*eB<@mEfz%E$&1gXjnC|eSW65kc1rKf{Ad}#B*3XSzpXFLC7nn-ZKN$kl%S(ph zijQy;&mf$2KT6iVBH~=ps3J%71RxO7Fc!Y5-IKxUG6pJn#t|Cj@aSqwu(yPrpM(Oh zRbK8^h>$E%?y}I@qrA$JfN(Zo?ZG(e0MF-5F|Lyo*dFms28P@9R^l-6HQ|pt?Y-F= zZ|kDecB?9{$#YT5EP1O@{sXB81oALD{cECrm$=;56}USsqAGIYg2DrYFR#~-IIX>5 zvP6|*k$alsp$TteC_n_L4l$F{1LagmT31nAEx0Rmp|l`jCmlgOjxcHvwX)PT z-v>(CycatRB$4(KZVseruRU+&$%eBUd zc1)L0gbysNsPF+NpyMRg7m0Rnj=n{hyOE4gu!Sut30?}lM4aUCf5NQyjR&T*vo_n^ zs^qDUIuei;=qfoQ90SMG-jw#3W|lWQL9KMHwJBkYB6jwJ_kP}EZMN&ymHSaEQ#(4^ z^td7nWFR%>G_9)TJc6!qpKMhjc00AI@cr&fteGKu(%X)NIk`#QxOeGOmaR>Gyeuiy zEpcPIosTFL6cL;PGlQCl@dn+fS-D5OHy!22bwkZTNI=|ko-@!_F>Qs!v^;C2{T5yU zl40NM&n#_%H0RE}Z&fpQlW4uOYm+ugU@0p=LjkS(R0ug%>;s(q{i-L%s4_I(iA$E$ ziS9`lWlB#(?dJpY1R9dN*jl{X1>WSJgj-CYGSYIeta^Xu@C{IPmDb>^D^OXFE>c=j zP}`~p3CKR2WaB*km7D&V6Ea9#bUPmeVesseE=dS$Nz6KQqt3lmQ^U(vp?T#c`)qj< z-`ZPnpp~SMFaYB`3>w*f1ZtVkwQbVhdo09jB_Sz63C0N+0DFqh@V`#p?-w@Ta&nN& zRRUZ>iAf*;Hs=R809MO)NLZmuc5>sPFFchcWmxrRJPh>b+O5P+=J%f64>5Wb4l%^A zv4>!!CDu7=Z^r!(&1;_!wCl;-7Dkzs@QOoWatH(j z=a2~b=97+7BSRYPv^=e3vx(03IyM}w0)zQb{{VNMnuTY+L~rf|LQ`R9X4b(23RwI+ zeQJT>-k#JVJ=zAKKf&HiVlt4EleFW#Timwa7kfkQPE43>o>J!- zt|L#S_gg?J!OlBUb2Pe4gJWKc9hjLg&^eKi>!L`#3cw$DM72k0F1D4U2qi=S6Vs2M zf7Ya%VUqgRg_S6b5LKKC>(x-@%G1&sf$5Y}mdg=~tpj?+%Bwunm!1`Fs~u;si^qXCZlw7e)_2qLL3BRcB9I?l7C5ZA&u*T@~TXZ{f_o)3N3Ii;yi+k&QgS|`sT&Q;W@mwNU!#dG(A0^$)<+Z5t zk}wFsT6MiN(@#}P@y_iY zLH)wKS26&2p*;;_ULkqb^)`16#V6B@@N>p{)KtAGBOd0qu>3sZ zFm92|AiDB_$s^i!Zv-8bTsEHa+Yf#qJxAQvbf#TWNN0jND5p5C<$tW(k5H_Lq0AlW z>UgE_92yyK3~y7o4fa6OE3UCvS)!IODIY0^TSUS`^;Xj% z#2DUO+M!0Uvb7O;y_5it$`R1&0M!t-iw2cl_MFhw6dZ=Ioc~Cl)St? z8D1Xc(Bc9RF^pGJZ4|7MbM5r5+EWr?*&~|g4VM8S4>SV<0+g$0U9Kf5O6n4TU}BW6 zP`g4>x}TF7hScNFwAvCfr7d6pK6M2*UFeuk`4csLhBLq1cHq)a=v7plQ~>49FU&5{ zRw`8!QuxQ=ex1O&>x(KBPBt?oFD!jh-bkw8`%8Ezr^_hTiCV|^ZSsiyLwKon?95s< zBcLbbq6jU`dM&+5(~bS4JUDjlyL&EN+`szKLzM zN0yBRA;(`EkkgkCfJSf#K2gtp-KsrbY!y3cc1QL~JPA90goh=m^pla)grJTy`&Fk8 zD1J=5uW?^BS>h1n&f6mEKv%5kUQE*e0BTFE*3CL>_1Pdk**^}_U^=v@Z70;C@WI`I z)4BGoV>j(<;(NC@2gciDM+z8BXh_BfPzv`Re>$a^o_3*iX1@3ER(n%(7Jx|t_WS6+ zKxto_Z+)fwYn3#&8(S^3DBuBZi^u-}+OF7I`^C3zNt1ckdYMs!fI1`0Q(zO$FoFke zmDr)g%KB?Xb+i8f#S`Nqfw`V_>CJwR(!3C_5cR$8{cnY3)iK6o%S~D3b4Ozc4=lx z9o_0UZ9yqG>CWu%KDB<*cKt1I(-T^_++eTanJPk^B`fp+gp3TG#O?jgF`BjvF`75L zyDna$^&J2R@Ego`AWh1hKQ^* zeW-j8TbmQCTqn2wik5`o)s!fasL3c4<*+IQk6k@IRs>N zPqj>H4;VZ?u-jO;-0!GrJLukmmejM4ViE}lo`)6UEqA9Pi`Amrr$cVcOGC{qFK}9h zc?Uk+_OF_*+j+V)%!~LbVQcalb+m-HmcR_cvo_Ku^isQ`VH{X z0sQyz`QTTauDT~e!|?f%+9jLAjRmZ!NJ0_#hdodCPdM-H4OM(9i>0C4Zc;AK!!k)r zOt$x#k-L)M+A-GXyYBF&kc#caTUFf}(=h{2N_S$Xb z$G#<}52a=*Fn)iJIL{gRRI5HZcz(9fwHfa&%y17#3xR8_lHt-*Gf1KC8H8usa)C2<@I{{W+mn(MWF zq5LViLw;mi^W})(7Z#{5CmA^Y?>zfgfqYkxCh8CL8J`rDB|M-MkHR>?#xc^ewCk2M zSd(vk=UAAvkfNjL1B`snf7+^!2ICFkos@sorTBOid6IY-9W#kN02+K3y^q=h!l27; zt5OggBq1UzrAG%JgeN2O>sB%1W`{Omb~=JGVql%h%18sQK^YjY&n`p1~N}P_NQ^UTs8=ih$XvL#?=^n zMlTS+=o}AQ8+)mKg^wEiD=Em=HZ$`0#=#${smm6=(YMx=?VnNGrzJm&;onP``t8kr zY3L}iu2NNIx~-V4zT!v4Iu6#(PpFUzv5vy8dOgN1tA9?~-ap1F9$-lsPZ{agr$fa< zhhZ@IxqCAD()Q=kM;D04;o4l%3Gr#KJsiJ5#pc#f7xAraJ^8PX-mQ!j`LfWR!y_Q` zUKH_y(W+uI7fm*mJ`|sbVWwOQ+qnaBic`>(q-Q+fb5LF$HFTdXQS~Oay4h15y{Pi! zItoBeQ@~K*^y^efhRZvljApI-+_`e*x^Xz;hZ42Tv(>JzgNpqIYWrz%TTwN$`ijVr zafHJ@Ym!>q+X@`l=6&N_YU?A9i+-}ayRqLw-I(Kt1LUGpirAKa7j-n>5B@F9R!z~T ze*@9k+Uf`x0V!-?f(ay^axv1X#d|&(%nr@1Yyqq5vV$e#PL0F}$>pnu!jH{+sNAjU zY#)kmYKLRZblRszbgj1D+*8jcyj=^&JxvLgp1E4>@}s4&TvI7=1m~QCxPiyYtU5Et z$hQ|(^ou*t;kK+g^K!$ANIAg>DH-YSTGH^QM8j|8dwmu`;+%7}W6l&1H>VG&3)Ty~ zMiP9KWzU$d)@LC>#VBn`26^pXcVotD92+l-Atmo5aTmlR|1$B>s255J_Ev086&jHRvD z%SmxrDaRB|fGs43=!^&IrpOrCWC6tLDyVqfD++E*{Agufl@owIbs<$Nv~)b{bEC>h z%7NsLYS@w#kffY~G7Tj>ZD46{HR*UxHf%(avQhg;AoT@(uZ2km1b|OLn)_WqElO75 zw+DmQB9{Scxx{R~NRXB>xhf;%I#+5G7LcTzgVMSjX=Ct^ph(CfyA*ceOFKa8gT+XJ zS}L0N<|W&Hb6m*)fJhnXUuzkM*_5?}kiRVd0J*G;jN>Cd)xy#=Dsjr$24UDvG*Xyk)XV zVu;Vs`_q$r##b;)^HCW?khG;-a4Ob z#>=h*o}6N)>^p$~4HB?%m|W85$=(EeyaJs$!N(`HacrL~6@Y{7o|T1apYbvGJOgSS z$>7&h^4xMlQaubSI5-}vTe5YQo=a%)2uy92ror3!=%mt8N!mg8#dXWa!abgw6`#_u z6!eBAZBMC`oS$$yRGA4{2;ikd^%ciAAq8EB#EG*Cq8EiUMz~F`;d9eY#7eF2Tvn$Z z3}s-Pjip1eKfga+moH6eog(8mCaub59Qb7qNqw*mRC!WIJoX3k#c0hLbB7DX3-y-a z1in7-c@4C!-i2-{1YqZI?bLD2Zoe3BqWhpfvsU+=Q!w4ympqZVV01t7ah^s_b6$rG z*kzJjGb039aR+XHA_95rWRO#KI;|Ub(NL~;3%u8^43ilpZMI%c;xaG>I6Zm_szkoA zZPS-aT$r*8Zjc&2W)hMW!BEGmf=6z(bJ1E4RI2FUVYjfzPsAlzSmj+vPi~!SY)~HX2&VnpQQf4%ArNlJ!2AhU_~O zc+R`Lr92RN3X*J%pl{qq1k`1!N{SeTC7rJX4B+v=tY>bDg?jiv&RTSUkUE7Q@TK1q z4JqLpLZ$7X4w zsn@ttViKoYF2`v;hca*z+$-l*R|^uFf6OHVZgQXkusY-Rt+ccu#1t(lQcnaCkZQ52 zbfYuj{{ZOli0-G5fx=bC?OPZudjSoTPZhhv_?rO?mroU~ZLn1$a=i4VQ9uNocF*rw zab#K|7nFxeo>9VwJP+EtUbo7%%nhx_SXcLPlk5okkZTIvY)rRiHd0t^T%}GPcp1ii zb?A%RATBQM>9X~PMoJ6Z?(Ge~K1wQcZ$WIzkjh+)wFLzNPCDd!bKbG<>_K(*ZWd_i zbU0DwBsNl%k~(L%JuBv-iG8?RWZK|9hgO7ewXY)pcF*`$u$QRrdO5VSd2Q!jFD#bA z)CLANkWaDCTB{v1poGaDu5P+?K77`j9@bQkbyvEM);G0my4#)8HrH13>yo*s%3It< z)hOhSdJ*nDtEShd-j-2uzB1uqX91<81fXM#p6VaHby{zFuT-Wb!sTj8ZR{fmH~^gf z5`J8d)}(c9`%yerdvXr}1R)efX(w6Q)?Ky1vyu=F5+efa;Rs@?1FfBaG+s6`LfP z1f8Qh&eRRI@e3`_AUyLN=fTj<`Ls zSBQFU)2cin_(&>)5^9_ZEtKKS=h73C{J6=lImX-^S%_&mk)R~ib6M_^Y($ba5-VLR zbw$yZ8!EKK%iQ-pO|^*oT02~D5K0tB{E})1!o53bzUrw-y0+($l?Kp+AL}ky691JQfH8%u0rFg>?g- zefXjYu~I$c4AyK8KrG_MM(E6p{kw`#TGvfX)6?YuLroQ7DJsDr=b-L5{cDba(Q|by zkd|BsIRu3(9DDP|E9(~-_si=n-<0!#7)VIYKpll{uLv#B=Q^myDq_fGl_dkp{{UIe z?aCzJah`pt{8X^RPjRN}x!cB+a5xM;GZmX=YoXn1xr(_e8Z9wxWpBn>LvjL0P)NwY z9XTW%_2Rf*Y&y#2b!pqA$JSB`)>fr)&JN?>>(aNchPsBwKUT_w>KcxpJP(F#Ir9&` zPaiMNxp=>AP5Saz0ma*G>1BRe2LW7xv}E&@b;nO`D;7+1L;Fz6txJy~;ruOkaK=ap zX)pK<4Og0^r$NrV=(Sy0fc)u=gofl`sBK6&BijIRp4G1PTpeY1mmP^qa!Ocjy}=Fz z>~8~TBy{79SFCNi!Um7m#Ckfljl2BL)tq$OGKD}36mqh8C)tW3p3PcOE zRDmNs6hX>~&miL*R9}o<9LcncOv@aBEyvNj2nTreAH;jBH9g`Kd6TcA=U`KcjDick zqIW2DJa9-KwOcHf$m7P}@v86A?sg`havN(? zfbGT;o-#Sd(PxwoeU3vit&cewNF<>-199LK zpME?0)7(TzD8T&918)M6#9)i%_BOn|tr>%Fz2$p&s$5Gw;ea6OmC0((hQg4_-XTg( zasqh9eMMb8%|ss$Ef(9OH`P7nT1)FlTH7f~!f<##<@~o4c!-Hrnf!^^Yl* z)UJIg9r4G~s`krrT>2aBuCM{lFTk9xFk-9tIY^ zM%mn6w0c*t8TS*AkMM*v- zcYYKn9YT-c$Uf4jjStzWfO^~sm(d?pw$3Rz2PJBobFdP&dq6_plRqu%2s`L0ZLCm|?2 zr)j_(j2;DB9|(|^&eAdiWOKg&s(cP}L&&QHkUsi>=CtfB1E{QtV&i+bF!U&DLW>J@ zV+7}c)Q@~suTWg0YTI~fYYaBZn+ZyrPF1rf1P-U$o@!UbD}0SrbTCsOv9keT0Vzq_ zz#xtQ$6A~4_rmD*&niN~)+9Do(p+OrF+j8d zIRjx=*F02riqIoluB&E4e+=7c18PA45I844k*S?9B0$r{*{YD-hX^}V0z#5NJe=mW z@q9JK&4M`vk?K{B!$%eoj>4Rv)eZG0f6Wyy#tmC`pe=NGJ$KQ_eH$JdkjH zl_hw-MpuWn_gHDTtjKvSf}9PzQ<6F49-TNfc5h2r?pkqkrXnWt20Y$eP|4(Hs2%a` zRWB7Zb+zap18#SF!R)rvEnomTpO7Q9dDr1&F;ko|E9p;7`j-;hHBh!p1;A5$%bX#t| zAUvqk6t(Fntbe_}CI8wIS zN2$&;`2cgzdRj~?_-i9LX=&u^de^-zfWzWq!@2J{v%i&{4^Ji0rR|FGA<0o@IkYJo zT6Oghq=0Y-9r1yRuxNc3Y|&F>FzcR)PWp+rZfeD2a zrD;du%7MXG^AY_kt6k}xVCC{eWw@zId0t{21oNH_)bz>Y^saFOpA6T6a~%)gPHVV_ zm*QQ9Gc}FN`+1G8%KBQdS|#b0t+VcrLPDfAvgt}yvw9Cq4`4@6onu=rIzv)C-O29<4TN*DqT z)R5zhhSZ`kJs~IMOJMj+fUB9eAUNd~t*8g%^8Kx?fG_Mp5+mwS%f zc+Mx(o%=$F>B4yjrxnXiXlq<}vLM{uks-Z=MkjXAqsQ}duGOlQb_cSWbmA4Imb0?l>D2;)!1z?gc!vtY&hQFqCw75e=d7|bzz&Q z<4BOi;#P@Fa)kr?d)7+hsXyrqw1D_V60Vv0!pia7N#mZzspGA_=v-~&V7?c zsOSk@Hv3!~S#fb=HqzZzRk1kj)9LcAcL*t-hSabLLc-Dr<0JelEdDH$gPQFfIIRe< z5y;aP%pAG-tm8ZLXD42&Chuip=><_{WVWtU1w%s8k*$|ADlR;?5>lj;@}HRez^i7} zl#o@_*TlE!3080^Ie0W~8De1;H@<#8oYnDTriq1t+8#>*$SZa`jD-{hf(ac)de*$T z>1{;@{=@c*f>9hOm8_udJ@%__Uu@#Gs50@3ew$GeavCVhVopuS1b6oC5)N>Wqv=Zn&tCG`^#d4)uFw7ah>Rht8sQ|W8 zIO2_hmcma_T&YqJv_ClBsTRI$PI5pe-oDbfsc28)T1V8^PiGh;&_<YSU2L-IV_TqdK(#>`8M6^b{H7xs9XbH=#%Os*TUaPZe%1mn8UusKSqCXDyNXAb~-A{FpsY@P2EQ-mCRQxPjGa zA5&0w-D#vOD%*S27F%Mv;#}t1KlX^P$@{hT_jWnD-YzrFcVsk8iR@S+kzm#=J3=b(!uNI#={sutz;i}0o#Ga(n*5`B=|7m`)E?GCn1)c`S^9(n6lFAU#J z(#19bgv*O{TT9$5-hhcZS^5>&Erlh0pjqsh~gAjEQOj(Ki2u#gG$5_8+zkKVQn8|}YJ z!ez^Q$(bd>pz{v1lp~xD{;ZGEt=Ib_TH{9A*|Wz(68yrKlu(p=%Yl1HQ`-|L!!iEotR z3+PD*LWbpgSERJgiM2%2@}pj_XUVTYg zLV!ue2p=QYtxkRr_JHHlRMrReL=rvlv;DvhhV{`?B3sbp$ZBTh*l|e(Sy4zq=hAcZ z?N+U2W45Nl65wVt_lKtpB_|~~{ABd&Q8sNCKU2RpZLvQJ#B?dZ9e4r~J7nW=`3mT; zX*<>7PpOxR*A;juD&*i2NFOX!aYpdc-pccB>CH7f(K-!EtThL^7*y7dfe#P~4e z7-?yy_OzS=0U&()_o&Tj0{3qbTf9eglBQ5w3qeUJBPk=~LC7_B)Usx?U_*XtLN)VR93oLG1+-{Q zcn?)QW`k|HXjf7^w9i9{2m>3I6b?sR;1D_v^+{d2Q*uKNG_;)KB__FAq{xjmu-epv zxN)3T)YW>QO|_$2wi{C>>{69{QXMKhjGXrW04lMJixn1U9Hpb)qb#_L1o;@!@ge^J zx5vqA>6R5+;`z?VN+cyfd3p78&VH4!ygy7;TLh+-wa$)`@k@XN0FP1mo-taO%X!Dr zw-!_Z=uZ{3?x?rI@cuo?r&IB=Q{{xVoq;<*9OHsdfA-THHO$WG8Ynb=dZqZwBr(Sm z&6k?6J|14HUfu7LQK+7q8N$nfWQ~O5u7Ad?-W{$+r`9aHdzeBRPDh)J z{{Ra{SmSma`!#0*f&Ny>ovHLSzS*c^wQWkBQizJR$R%w8td4*M-~u{#t#00-wJ%IO z$|L+V!T20XNiK4DJn_Nn@)cRsD{V_mq`XV6dYnhG&rh9U+-ET&BP_Qa4XL!@N=P|Z zIm!Nlm91$6r6D_sBc6U< z^?J4G_To);;|}cH8=_A3wkydAJ9iQka1)M$6u`qth>XVx8O6{gBPKRHEScm7DFV$Z zu{VY+Dru(@lF}515|B3pritRy&208!{Ncom0qojdPGQm4v^ zU%v*g6XP((k&iY_(P@^XA1$Dsc8qQ3*z@{Ttz+?V5Oi|~T`fV*7&nTd9(rn_z7Fqc zzrM@25QiAtlHhZ2%5%Ue&V7L#R|^)ZxHokS$#gv0lmd#tI2j#1zO~h2W?jM*w-+%8 zY;dBrfu5ufbJC+7w798^vu|IX>x6zD#P&5oWR1hI&o|7UvS4;jneCy|{H@il?$%qq zxkBEi(LGVH^9`VcrD-@j2q$->ZY1D!$7<2AnUf;uW3=%~l;#Fkv>cxOt7~cx0rl;N zLMKN0D;aqPH=v8 zQU1`}WiZ?)WGl-m13Bs1t`{pjI~}&$eYM`(kuWqKbtqW{qqu;cdaJ(~;Qp78#^SR; z3%|;>YVj!+n+B$4Ws&A(ARWO& zDoThwcBo_zk*dvwQLlQbr*1M)4V5|y-6=a#3FP5DzCxn!lb*Or-s5E_0Br*u1}p3t z3VHYGr_lyJH`1`6|la(bt)NeAb6O42I5d_yp%!mQy!0oatP9o7W02W`nXb6fKm|ECN$HQ!3g6(*{{T&fLnCZEVd9g;#qBO= zY1D47T4z>j24|;HPnL-&h&>2-TSI^l3V6Xh9(m3yuFa{fx9Jk&NlIf&lHyQ@i=_>= zo>7y;kMiw?q)( zq@@D|SUpd@NNCv+gHsIHE10D=pn_CJ)pkFZwL}EUVshMSGKN%eHs=*{aq>!XJXHV* z>ZTzL*-Tlj_e-UcWRWciYAaz&N}K@Wa7e%>D&+JP4-S0WcJ_jZLWu|GS$cXvOxGHe zr&5E}3G4gURK&S5Qd>fQgp;)=^Qpwy^_0nWXn!lTE~!dgHC)*iHtkKirL}-aAaFp& zGuIfdy&r33*-3s%+S`R4s&FTeGtPh9)MS-45@G{PbD8rsCE5f~tYH$|Lwt4M9Ddc8q_s5540&*xwl?DAH|42HVGH1>EBS#S z@zirrveh zt!?2wyG?jw_oA-M{pscuNQSYs=F|=V$A0<$0DMXPm3|LQ>WCLhgX;{uR)sVaGE@_g zasA$z`Bb=n5{r#B!XcP8!P1TLBd^s*hVYyW7>?2?fFxF$d1xqZz11q~1}@Kuc(zxJ z0+M?4>6-6qTxVNt#^g4nx%dv@&1?N}tF6fq*pKBr9VysCPI*74&Y+}tg2Wp!gzRS2 zcn#oWkW_PzZ_ciZg3UB-b6RY#OwI`-;SvBr=Y1C0etcG6ZH2GazL*?Wd(9!#Y*@&z zKFa&f_OX9u_aVFG6(`}`^3noEI0G3tAD=ZqxcK3&ZE_ocB_dD+whWd+lAtrm+5(3d z@17~Q{+)PcW=Je=zW$c8aeN~chii+=Tn@u;L%-2_>O2-$D=6G|9Gslyx^90El7%sm z)Oveo=U!Et_JP%!VgplewicajN={W4S!{qtf7PrKc;~HN^cUKfL24GtW-4vD)bIqD z$0|SmSx^J&YdaHwn+qmakWWwYyt|zv8ep0SMbL_H{{Y)$dVsdQ{i!9P=A&}4!6QAZ z785HmlGfD~s2&L8G_-h;qHlmV0{04H%6BQYol4Y!^8n<2m1DL)ZkFEYNZGUvAhPoIWQ>A;!m}++$B!|!gr6xqDMWs?JvPw9 zmy4=ef{!(cKGh@2495H?sm>~X*4YM9cS#@9^I+sMv6q)_Wb{%FM2#V^<0L0{LY@Jv z8-oop8$P835!$8~6$Jm_n5%tg)F3rbP6wv}yKR2A>){J5+3l+^b7 zzlWDEBHglU4DOQY!=#hIR@2G>&noAVK2-zuwzA7UD%?ysVM~zOB{=eT6>TZj`YR7337D&YorpNgOMtdXH|D>=^Nbm929QXPx&HPMdNb zKC54c;tOJHK;KWIw%cwpEtXjF9=R#Myc<(%WtAbMje~1tTTeXlcMP6-=QXUR>)#RC zxJ%O3`OZGm?rlO6cjwekqvS~^irUvj_<0f%CQiGpa#j|W*b&lGxS(xUTy@84*DrW) zf4*v$oUy;<#gfmQ%d$dP3vaIARB#4C0|&J)jltls4R#O`Uz;#Bp#1qI=EFq;iygng zd!372!T$gvO6HbT<&D4$o<~0_=N*)@c`hjlSy2Hh1e1^tPQtM?)$qGvkMVxDcBKj^ z9nWvlvmveM&wLxn^BhnmDp|@%=N)Rv1dT3hxoEu%qGmble=8j{Qt=}fJ_6w{j)oZyj>(vb}feK}@^20os?hlqb;g(*t0<@=iM zsK^kXVnKdfzc43+4nP?nI+F)9NE!}?(JNZh-8QhU*2tGQ<*6-#f0bZ*l6}FgVXEvp zj@c+U(}mb6SSfiUB_ku1cjC6?d{7EWN`N4Y5PQ>jE+Hlq=qz+UME4KklHi@74sS#B z=(J49Q46g&+^;E3l9ekTi|3E_)q3run7!|jaZd_L_BfScK_NLK<_>$-*4LgI#=Rj= zTHp9b$=*;@b}3jmJa?*7&~**IAhcZKE6#6fP^70QAZI7#`qum9j~xgQyHU72{67l! zSRNsV#Kb!n3+4{seN|e~3S$-*qdK>i(<00XTs%2QNi)rht%3(ku zq!g4bNl-kFF^=Cn*7Lo;<=Nskh52zefcg-wnZ|y3sxOEZJt=m$u@)>;_o>!f_ zRm!oBhdcvV>~FJ10Tm|TUC^_qkxsfvXJ_=q8#U8SHT)}DHKsMBtBlLFiE4=z#+2(} z!$ z-$KaeXG>7}XI!n@L~DQC2$vu)%LGP3fk{Y6`IEq{hi}m)Gk@9$ailXHZ25_d7mOTj z+($qO2DnO(Tzh_0^t>^cXq$(OXLk9z1IoI2t39MZ?#Yq&cP_91_wUzD)`0O5 z?N~1&EGR0uTh_nfyXBQ`NXR%o-THQ`&9lRMRQUG!LfqpItpN`uAoc*Lg&&vot)Jn8 zR+y5P6XC;;v~=(fv<>cCj*g>=FWL1=}^cQi`&A@ zXO>yF6>e}ZI5-D`jN>Mr;(w>{Y{Rs{vq;Q3^`)m9XY9vo-2;27Bc9q?uWEfGXw+Ig zGOo#Q;+o@Pa21ZH9{ifh@Rr8ZC@HtH+;jz!lm&o#=YF2}o}FgjW=oDfv$O)7sW=!10QUVkt!bdV9Mq9MD*15^Np-?hkm)%fp2I%Ev1h@1 zbBiV=q@B)!ycT~8#s=Wp{9Ce{BXBwN=lN>m;UAMK#dV17y@ncXMQ!f~B}3TbJf28D z?W_lM^Y;0Px;G6gLXDkOP+Ncar*R6!ij@{Gp=i)TgS!O}TC7l@I5fBwm*NnD6qf|I z;@aX`q_}H<;toZBo16d6?9S}WUY?tCX3oW#^L^jX=XtU@o)NQh_PdgZj05gx=TA3R z-T}ExqaX&W?d=NRQvuu7q z#)3vItsO1pwk++9GWBGdjVNEbHfWxb*l2}3Fg_*+(Pt*;na8(VQn3|gD>SYb=@B*Ic0+0~m~slSYyNUU z*jfA}3W!Ni`e9d9zcPX1n&jV`1!jYDs`^)hE-I-&|JBGbYDDbv>}BKB9;F3H*Za0)Jq`26FKQ$&&ZpJ@k zo!z=qeQjAZUs=zOI%NG5B*T&zP@fl^?)_Ih@o2xoKtaA;P5NKX?ykk_IBu%4>esTP zWfLPCO)ud-#gin56njfKpjW7!)&_-~8IQ?dxK2ieRnL}FrxU3+n5f8=CO%c`FX1%5 zPP0-P2L4qZCgJ-EdXW15>UR1;m7C_I^#ry!Jgti<-R;UBJ=TA^GmPmU*6ip{`X=(M z2RlPfHx#Zl~k%=}1H&uKP0KBPIz|sA$lDW&2wHJ(N9%93N9c>tikCY7VB@@~b-V*N+N% z2VH%icofeIVDE+9j!BIcb*pGHtlh&taLGUA^?wt4wvzIb_I}2bxcqo2S^LQGmfx7w zIi9bwhfa4H!9Xv^;nDj<2F)GJNm&CY#S1>>TaSEmuOjjM4?{|5cAm=|KU0~L0pWqY zeNkmAs}&RmU}ZALiN*YA2hoK!Y~ahVv3+*%kP5L(Pm#=?|AjyKXpfNY&BW^ezYRkFe5VIZ-~15 zIN8(2iT*XYskP*GVCOYuC5y+s?mu1vPVRjU9k^7gM8|QU5P)>GEct2SiMD>>pBP>- zY{#3>8V-00u#jqy*BF^#yw_ZCtM$zE>%JW28feOGL|MgtxWpa{98Q5g8$iJs5A?(m zmNY+t_Jkk#i&>f$c=5AYE}m;%7B+glWsIC&+>0e%=DjG&r8?d>*cDnjve@F@CEQv$ znw{`{E7Vbo|M+=JZ)ALtw%?(S^}Fx+dKj6~ouImsRJpCn!-b+x{3t{5A>ocbcIi#v zyI-0F7*>f~SR1}|OOtw%2Y8%1)9rU!`1=m$A=_0#m7|2tTjf_cyvPXCg&`oF=b)ZJkfKbOv_R!oP}l*4-r=@xY1R z`5)fG0<7_513QhWAGgwC(u)0uTA!}NEgAnNJS)7k1?WMp%02W>namo5_IFfqs;vEK$Pkcx=4;K5k(^*a$?wOti{t`Jj_yi zk}@mcT{oyN;6<{b%uDOkb$ zhFRJNkf?&6bp8ejI1o8p$WwqV{+5R|;F8NlN__GD%de}tvTD7$-ZDiGSv87=7x)0K z-nJr507LOhB+a@J{p19HMbFe?p|NdsY$rQu;s7<8&$a-U4Ww-&9BB7dmExFMJ&CDQ z!QP7UXj?7_##gnBshz>4x8gLpYCV6Ts-uq`P6LLE;4+)mWgz-~swX({(S9IG35Q<; z+@(5{R~wAyo~yBQ#7}*E78hkihV^YoJZU&y)7KnIZg)ExZ^jlFqP_g0@ZG1ksm&*2 zK@&Mev#V1Xv%fRNJ4B$tlwejB)o~_YUsMW-D`>zz^pCI@aP1=~^KqD6Lw2hDSoVI4 zW!q@zmT$&EHOe@bwi&e>Ynd=EQt|8~?HeO`FM3{zJq|XJa{9jKG1!arA9F6-1B)|h z-|`Jw@-AOWC>*{-2x&x4Oi(U^Y(3?kY`LJY6eQZ<9VGVTQROicvSJU5%#1!yVn%Wh zC&z`s@FmU$EKK~lXPdYrmX1qv`<$+DDk|kIa|4#G0z53wR1JzWNH>Dig5R(Uexi5z`){w~ac(w6sF19vkdFXYBozhBZJcH|`q&@}@b23h z-r_vV?8|+?5jNmTLI7XJu&QT+mLcmc1B*ThS{-go5z zxf8DgE;h1O)H&~Z41@=XvbEz~PD+?^rCzt5&qxzZet53c z!JEI}Q2dq;`0M%U-i2(u!B{}fe05TRYV49uw=OMLrxtxbLDvmRUq5fkKviL8UZAi7 zltShz7F}b=L~BJ|W#AclI{Bj|yO)ZjILrKVattW|M%}LGwzpHi-8(=hbCx+eQhE77 zbsAbh)YH1arhNaWmm4esch45Fa68FOTmO%br};#1x&S&xMlOl2x9;7U>}&vbq(X2g&_h0t^gdt~)omRqa#b z*%jYxC`H{3?>zBW=RVJSTsCz`Q&pS__-5p~_OJAVfq!;!MH9UtAvyzUp2Hs~OYZ>R!LCj(yZlQB9QT@=J7#x95#zqI# zy7>-27-z!o^rL^cH7x_*;~J+Tk$#PdoZ~MzC$%oH1VUTBbw}pV%7?Gbv zsLH8^%2O7g$2|4*w?DEt#lEp|=<`8?$4CPjAm6{lNuCy)fs<%*uk0l7{A?cVOiTot zC|O)Qazy|7aMhd(7z)yB&Nu-TuH$B#R`_|kcsHJ?!LpQE-8w^(va)Zag*B^4uOVMFrUrL4u^Q;b*3S(aHSx?S{YrmX^)>!vRMc~^*o10(!h z<4HWPkosUcp8hmN6Vk!#4bNVAC5&lZ*6hx;6yQ$ z-^}a4+}E;O99P^->uwq2E|BHq7uqBHOhI@)o%EuYCG(>#JcgQFMl)A{Dedm-UrO_m zwCwnF9UI^|E7Phi`%jDaWpdlAWm?2RN*|MM=d4%Yc1tDpf}eCff-11vzKl1QT`h$5 zvvcP=Rbms0oUGQ?H$0X*^=$IIpv{Uy__$ahP%p-wiF;H!DvC_>N?W(=A>l#xRsMH*Rfmjpfp|e)Wqtb~I2Y*UGror4d`;A-t5+Y%4DPukbqE z&8S1DL~h4RZa!FuE4gpL%$ci|$+mDe9EnD}*ikD{CI}8$@22eSQ;;}hsyoLKZu^{( zT$BL$DkOERn-O@PjoB+0*i2TJbh;V^9m^**A3p2M+f!H&=T(QrUMfq70ML0Hg_S&> zHQo_(ZVo2rWj>h!3CXVqKf7L~r7|Yg=20C;k3pBGPunGF5y@i>PGF80O| zx|i(x+l;u-mi0I`?+woCDJ8vdR%Zpdm(xBIKU-$t{2KN{bWJEzz{r}owwO}423?ND z5(s=bS@s%iT0qZt(=g(0oTDd*4-KmDr7TQw8gRxp3Z|xQHxD|0{~)VKc>5sz;{J7y0upjuCE~>3 zpmGIE^xk@l$X`*5+6%c1uMcAnvx*n>kdlFP3D7Y6skUu71vpgIDmYd$(AteG8~06) z?R}=$NQ3NKApmiu%Q}OD;-3l%SG7qlgATN}Md7?hSQP?L#5)Oq%pvMT`@Yd_7 z;L8mS0i=c8lb4V|0v=>Vz(ij^{};}BIa^bQL0EVZ1sV-E&7mop7sLw zxlsqR_$LIwxqx(#;qN$B$3Xf4iO^}+^1{bG3I(4Z;>#9QE`GDZn zy%hs?Ym=K!DypA)msZM_AezwMCNhLyKHp34ThGMEIjV!&3~2GMlnpw z$0E<3tAM9UBgVUgg<;v=^JIO#>zl88TW~uky8|*G@JkY9WsS?-b%KA}^ zOW^O@H{r{Isi}pY$JdwSn1dtE5|zjJXN!fV4@}Dj)Ag5>oycEXTMQ^02Zg!H2GI}# zvivNob%?}a<_Oa6K93e!QYk9i-?J-@9V;7(x%K;`M2-exp>O1)zASl-E`uX<2KEfm zEPh2JbE8ad4wbKSeDNS0l9m0IYv!LV9;eRSN-JC@tknIQOjfZ0(u8#f)YhxK5yIPO zas4@-URY4_9ciVn3E*FX1M4K2lvlf6cR#5Q8~_%lzSI@R|JSLCWl6>uE!b=+V zZ~^Am5brUW%FK<_=AeTYMfLC(&{DFy-38Ho}`5Ok|U!&x>&E)%Pzd;?Kky&Ty(UtUfM}VRU#!9 zGdw0lG3$@U>3scWJ129*NAG_am?{;s^@gPN;Lc{L4lOG}VdR09lkK}-Iy$~&VrgVY zEUP9PlT*-?azf>9hwz!@?=8=(d}S|qMoSbjd$oBan#+F!QAnj+eatQB-l*!U7>BIa zme{fm-}f3v=pPuL4eZ2HXWv(`Y|@2l0m(= z)tekIshIQr8$fhz>eK~iXSa}BW{7CVu6*~nW~{E84J4NzF)|^r>p)~BCFX#}rz#!N zitV#sf{Fl9*GQz#)AMpilz+b{B!penZ$0#9#)DLfLG6~|;Srqr(k-B}I_YECHnTiC z#XyssN%_UD{A0eqN{l58-3+YxOzBI$Q8 zT$2pPwYj3A2%X~d%&OL0=lN+OZ}X&=fF-Gz`sZFjSL@YZb)Tve%yR$TErA1j-NX=N%}E|E6NBO!ImaHyp0X-v{G{Uf+yBbDMoz-)ub$^UrSn5ick(D?s2!TW&UEp zG_eLGKzu~5k1{TbqXQT}X2f3I40)&l?KyG0_5)1s^v8Gv-|_UzEYE(EkkF~CR~D0I zckGxmZ)|uy^IENxdCn67w=4?CwjcLs66AQd4_Z5ZuhyPIb?}mfd>+XZO7inhb&duOgk6CC z9uwYJp?zL_BcqRyLj(|MnM6DdO*d0POF;p_ zBm@(%A@+r7^A$z%xZ~MJhoD49_DJf?=O6Y1N#2S%nE1WBzQMiycA*oFxX1O=Z%uPf z!R&giL-J;Q(m#x|&`K0rn2 z-oEk}NuU{*vng;Sj##y^z5tPz$ILeE>XwyJ^>*CDVLLg_CUDMLefTI^NyjSwBTlXm_*N)T)a71tjFLN;rWYI+)qvu^CsE)|M!% zO;%7P?I~(_qZ8#H-OQ?|zoaJ=AzBR?Ty@&KXzP|FuS&UJ$O^qN3lM$I1)y>o>H@9% z%`_8<>vZ4j2#b52KHLD!Ui9O=VH-ZPZW<4;U$OtuX`_Hp2M*@ov%6!|cwQb1VnB6| z#EK7FLm_x?-|a7gBate@(p{A?v`QV4`=wKKCZ36WOVT!=t)z^RaHpoFlq44|ci)HJ za6s}DM#A8nm^Up&VSDe<`j5=MF!ejGD$RV<-i{(p) zR$LC>mSF3DpO5Hy_3${&_Hy?oFdz@~*t`#=FhTB|H z@v5S2^8`xv4bl-D;^ayuR|6WH8b=OsU##*`>s#R@E&pM>K8bwApP_0Cxeg8@ti_*9 z_5|!48EIJO99oPn2@Qxm{ITm$C@cm~6ZP@N4HEbg)USD^R>hD3)|#r1wJU}~-S z*h9HU&_Txj2{C=k%KYA-dZaX6kfNePdJfMX9lNb0XT?W)|7y%U3{f{dF5-B~TY}s- zFI+}Mlp$Y`6esb|%GEr$ejA%j2qLwyO zU6z~t8d*z?C{cEYqXLr!U>mt_VSJ1xNNek=l&v6tR=5&)+Xih1SW(O|%p`$>zoRh! z=tD_?4w^u?+-%rz(1jbtPHX<2xYdiPjJq~2jeb>ck=N>4&AXfs0eeLr!=&O9Dv0cice5`?M@V$3szx4N}^Oea5 zJEHp3Od7DSfoNC2g{_4Quc>)`;RKH?=HsY^`plvk<5OJ;u;kWXR`XJ5z;wUL`Lz$J zN7!00q%^wQOs5OqH(q7gp|zy$!Q%8M78dd#lSMiU!;0uB+rG{NO^p+C`I2vhB>y6q zKKc^>NShbmwdjj?@_oAdLGh?Fp+PoJpd%@fr80ZO=%l520AH7)=0tSm=)Hp-9QMZc zpzLg-5=As8n9!+x2yp|V(I>w=_U{a$cfU4PWzL+6zNTKj^WTdelVa9ex>@4wx5& zQRiL`OJ#x!gy6uus?R?20saPRT1`H@gN#*rcfp2Z%S=MK!j7NM=G;nY9uLk7jyAo% zrJ3=(Uel5kSs(vG5+EI2rP^w;LvFonJqSRhnL+CAGQ^$;g)nwB^B5t&40%LP6pTY+ zk3LwL1FVgAng-@FDpb#QyZ)6yOy{m$b>=L?18-zT1577dQZ;W#z0PCj1Fv)MBmcvo zx|8y{*Yy^3;Z*gyb%Tp3hHT(pXknGzxHQ^C^4BnZaJlFhhcB52lvU$E;z!qxrATXc z?YoL(&h8Un*9|>KO*R?|zhpGMCck{liMYMPDUU8Ukz9IR>Xd}7@FCsITW6Ba9rKbl zu+j$%_hk7ld2wlSRDNAgs(QP4sW5&Nx!GIs?YG@L%YPVMVGWG;4nn%^kBA4GmRz$s zUjeu7PyN>UcVW9Xl?q_7XJeZWSGRc@XCKrJq%m*e^fq@l|HFtj0C2SU{)eGmO1O@s zn4On`DVOz*T%9}rhY{PpY<2Pw;E1gDnq$j|M-oQ}AO1Nu5gc}2_BaDWQtV6r54%Z= z@ZvH^7Mw7s72cn6C5=eTlcA6E|KH>I|7$w`|G4}BJ(@%*nnYx9I!UO1(jBeSUIt!& zr=F#jBgY^CFi>I+j!(?ErUfs*u4-&4vFz$XQmO!QiZk?LqCRTJZF#9(eB_X&%$_?w z3A5br@@Wf<%K1L-lFDzWg=(WhwGT(@qpPX*d(|Zv6}WUn=l0G%Jip)R)iTX)i{6$R ztIzNs6Q;5L5|U>?Tn!A@Ro)^0xvs#-QkwJs8bxw2pThpcLXwxKVLu=G&C{I{DJhtS4;XX!{d}ksw0m$>vPUSJ3rXaI zIaI+#Wj7QI=QHLE4LZ0R*74hPi`lJL*RLUZhg0w)`dh5B_{(iD*;fG{4x zU=^MhojBLKFu|!^qf#AIDr7D_4}Q+)YhYzP-aII8)!sy17@VA8Yw0RZQ7%1ywO_Z^ zlXa@>0m@e15#?B@FfnhR`E&E~^!N$arUakr^Q7poQ>*F^8O;~W*_FLeLc+1Ha*QKrr=rE{aFkKpuMx*W8p3%2k#YP=P5eFU6yuFplvgz~T9O*l=<;Ai4&InV({qhZy?K5Cs73kw_9 zx}6<~JJ8<bN%LZrU6$`Yh^%nN^w;uCfI?0m z`TBV>^CyruE1_J_S@IZ(;8bXz)m|nLrZ7|rsp=V$3dgv+J0-Q~yxkJw-J*hx*t9qP zHdt^L;egiPuG+^qv$@I`US<*bxdtloo5;i^D*lyzr~IZ|!^}Qbm}+f_>)T@7qn?tE zC(ZQRD8wXv|2qhmi3UkK>E zLLZOHprwoSv4up`5g#{ET=-54P-A&FZlX$mPs9vNsX^&B6@Bm7pb@jJmQ37l*)~iF zlV*`9+ohhtjB-dsy@ye6suDs$xWrlIdCa`3LB1I`X%T+>OSrF9Xpk-lWt?SguZbmv)7NUYWt7r z>lADD3;vF;zDEy)pL2kGnW;9UJBkO#0z4*~^KXF4BD7>{xb}epN}d3$VC@V-=RlL< z32sI|Tcks~c4TSR-x&!sZ^+5vS@@PUjH|0g`MJ(7s2(_3<22^gD1+AHpWViDp<=`i z@qU|B9{R#CFS9NyQmfp!&$D%gE}jqdzdZC|-2ITMax>ykvGUqBRW;w1Q$wfO9VGrb znRzIZiD(kD>;M6U9cNyk(ae)avqMn%{^hq^3Y(bNF3e)_el}F{{8_F>3l_Lm?6HK4 zzz@#vOCVOVT&@oLw6J+x7BBIw<+JF$Nl&kO(`}0CKMVT$vKcE@Ocd; zIb$Yjg#=W|z_*@|%}K8Zq;Z4s;h7`3fhS3UQBwLkW{8(t)^)$Hp63(?+3`1j7GLrz z{NbvmY#`6IwaI!ZXZ-wgLnY?=Ec23hj3Mj;J@(Wn7gHkTk_%H67J@4qhWP`q-z)N# zkfC?a+6vMgI~#|SW?F-mdE$rSh)bMW(G_tVbYkn(5V`{Jv>!*<#jwHPOAo)BP3hA( z+QX({i#11GN+gU(vFsVI49(?wipW8~m}%223Q#29%^d5;ggVys{A!Q{qzErLxW3V^ z?1U+<6qjCxK#w`w-CfODESfM#GMw8e7MPi|_seVFjV{jF-tTM?qI(n*`opjV0v6EV zxOP(XT(pcU1QrX^>vM^-*pxF0?HO>=MD%-*AH|$?j%?H8ZZVM-<>+|tp8F~1fIac>)4uLhI8L~eRyR6pO83G9 zeX|ys*uEJQ2lvSEq8T4GW*3Ll8-A^FUeI62jg5r;pRe&7Q$H9Lx&%l_c64CIT_;Pq zDg1}kiD|tU*bogAi;#0UYpDwtlmw&{putu9Zs7$=cyH)@T+W!S`>o_vxZCrHo)LS_ zMvlHP$Gv^}_YCtpc?C=JdX1=-7hpK0KN1nhCn1?GHQ5$P)GKjTKjDlzs@((F|2Q@% z743G={;e;cg2T+(7$R8jgn_f}Ht;(mHU-JcZARBX6YsBM&u&Dh%%KHhO7LC$W%HG# zu&U%KEyR1`E5qtx<;qjo4{HR0mbMi%)E0lP)|q5nk+?FoB`&U*FmK z;09ggB)SdeG63~`@K7w7A-OfMj!opT@jS!=X^s-$H;| zs&=*jN-`z%(rvG;XjG!qz%N!lUk59OctDJN5)JKpF$_%#3S7d4PR5VP5h@(D&}IYk z8^rc$`U&v!MAjc2xQvUgoFf@}fDe5^n+d6DvXhIC%=R=}StNez+M#Y|S`lV8abtx! zSI2}sC8D`i2a+%kL6atz)^-0hC*rRt0K+YnYQSxd2pJA>c)X<9Bj->R z&!X?=v~SZ2KifhGo2a-c!`7XYX~vX9M2m9hoIk45<){0}7X%d}y z!G4i(BUf(Sq<+Kei&^x^iV$Zu+&A(Nzm%-!hM1qVzspc3sb5i3wmR1&py4kXwOf_C zWLiu?M^yQ6=q42w{K(b2W<4rX`++4^l!7isPDdteqe4otwtaYbS&z?+xe zzC&x|^;KU7$4H2HXALxn>gbZne8%fhV!O^W_cKrd)=8XLqrVx{9e-$!Rgsgkrc-`3 z%xdIe(mntM^PI@Fh$T;7p%05uu7{(4)TRBr6*q>qrM=L~+dUAXc9WKeK}O$Qj7*QG zzj;tu{93=1dQ|r)yHfpC+Gu{UveR@i2D^Du+ZvVN=ULa2TBgM(AlbZJGi1728|7NJ zOjvKM&^@~G?%OC5$tulk(G4104GmomQn2TeyB@I$Q-g0@l|<5F;}GqJs?o{Me{Q>L z)I7#ci_EBn{KY7C7P{v(FcDJ8*rJ|8Jnq^?>!vQFvcIkKqWF_<^?3DG9>kJ425ZR?CJBr-Pc?a7>{-5WUmFx2Cx!Mh_n)^)ZCsllzMtmz;#6?R{Pxw*lz%Ed zFn-_ji_&mL@})_oSEgL`t^E_AXp%CIQ2Eukc# zt#xYyoJt(bGT_(~Q}@HERmd&hF?#3qf9+J!!k(EzGxKH}nqq%qm{38R?H%aOPD>fg zT>y7tS-VJEizT$}Nk@iF{pS2kCi336dTz)*n`#5~S^RzbN2_HlfFg$yoH~9NPzpF+ z>O`Si$bGf{)Jb+X+SPRsI|aKw7GRJ077>*J7HkP9n)c*+h7@A!E>%<0>~2=(0H2bG znV$<9lXj~s1)MgH@Te&nK_{wN9a~QaA$75A*_}>D6Grp-qF82+1*O)+n zvvUe~*_vL{IwzNWwg{mt~L*kk%pa-CWhfOpAuVNG3&d z0>KM8ESafQA+trzCVIOs#hX~LyUFon@K29WXQ~m6r$ce?v3?)tCZ;7C3jkjJ`nDrA zie;pm$V$xl$K>Vs3-fwV^dO1vu7HfW3ab5W$sT1k4#i0eM zQR3*|7_1t9&HH*u#c;XC)Kty0O8upYHWBg>&iMQe@kl|SF(U=?F79ZKpx0gp+r;^H1W z_8ZS8C8~%EEoDUyW)!N1+fe;QKNBMDFj*T9%D(3R(_}7j1a=qbzHIm04^XU~D+8V` zdTftdOe5$^FGLuYbvYYs161Hq1dad;@sQ-4wWeK@sPrcKwtPiuOA~h&cSoV>*5>)G z()4Gfc|I+}&GZf(yQ^}43Ct_2Vpz&FrxpvD?8V$>r`24I?Evs2d~_?VD$@CfBxM8w z`P?WvxkZKM(GDA@%7@4PH^?rVnb$?gvN{ay)ZNTKN;4!jmd(x@;ut$I+{(#zI8;}0 z_*%Q06UKJm_W|c#kByi$!|q)PNX2nV1-nZnd&Gy+sVfWx6k0M&MC6aI?EyuaVcBK) z(I+~GthCb?cA70(SmkcY@5Q`K3U0@%7)7o#EDyN@4>F29XK6?$)a~wT-zpI?< z3#(PXrV2EU942TF%h37F$j){q1=^c<4|O+IDl5}q=WpeBSB6oBZ!@dtgIGkWfJzp~ zl*S%g?R`iPK{0dRwHmMz>_-}Z$l~U@uMgZ@k8yhjRpwmI%MmjWN<%m)_CG{Yq)=q7 z&xuTI{dTbmbH>ge6V8R2P`q6usOKw%E)viv8|N`DPaciHt?5y9uBcb|KIY3GoO%4y zteCq)5J>zpt~|!sTHv=96sOT71x$HNh5l#CoRs!FQ`63HTeqxI;5+uG(2UYmajOaH zhNQRWv7|dR1doH|G1lA#Ymn?1g4kXq?QYX8rJY7%E`>G8X$HC_gNU8?fWSd-iaWsT zvX!TKUy+Dua~WUR0r|s^Qr|K3B%)}OP8by^y7_^Rx5hg=3Bkf065z9lyn7q*LrA7% zF0{B~mK^^eB@kS^@^dcZPFHK%5L(C`c!d33s#fW$%F6cXBzHk+p2g)PJM?J_4St`L zdyEEc*{Gz=Vfdq?~?o_did+?zS6&G`B4wV8&-u!ISk7x z`jtnn^X`C}8J%V{nY1d_5)aZjFhQp)7~i@v5)ZUl0l#f=+&g=sbirEH@M`&`#5=WA zogn#g$rWI4+Z!6oYKP9>aCS%?scrzI-&`{^MW)^aiSvZ(wA6ME>eFL=Z0K}YBD`jfPcC@`tMO^RG0UceF8O4Izh8sMACm4D~s$>Kd1^h zWLUAksd2H}R>j%=o2kr)o}u*!--p=U3{+(`tuUWN%4SA-zj{GM(#oo0{=?YtE_^!X zECuh}S23`}l#zo-v6P0CReqWw%Kc;dy0-nQz%!5P7NQ4Lmqpxt1MOVN8^UQI<>C+!qoA_ z`}WNh51ZvIv`))PX_K4zdYE7@JHVES^X;gQQ$W@r{9an>Sin_Dl@jFvFFa#fh>!BAiQ%Z%`^LF-4ZOxNFgD_!) zU@ZH&=tX=3MqvuYv6}(^IADkBk9_cu=X>-c!SsfUt|EJ;$u_F#GKqJ~ot;A!_Ii!F zuHk9+sm(Cii9{9ja;Mg~;r+6LeNb@A&b26Ykna`UG+s^$HT|FB?OFzFw&~Di8>FXw zRL$;6b`qX|Wi66Cby9`k)jF0BsBDed`;cMD`MPBxcA=}F(Yfx-883f=jGGbK7x$Ki zS%6k?rksrp`s9*gf1fg{!y-Hh!|RION*?!(M{1BU#SYNq>H57*`z%(mll*LHT3>G$ zWo{YkwY%E9WFzvz3d@u0jOePgoboxvU4Ze0H3tPbJ}f9UobDnyhflJ>NFVDPK8v_E z1D9Z1L5w?upK#9D;6OcRD6pxG^&mzYaT`TkDD;_9OpSOXc2$dn?WWU-b?jwHGXMDufSCl)Q}G6Y;zM^DryV>iCL8gt+`p~M zr|PHbBP=*2uH&-;KO$vJ7^X#$H#SnO4hd{fqf}kQMJ!D0EdJ|$liN(wNXqvZwU#Q8 z7fE4EC5gGk4aV*K)8LxD`_Lnk5mF8UWgC1_jG79DWinF9nQUIN{sBz)`rQyYe@yY2 z${vN>w!EFZZkGufU!wOW9FV~1AZ6^V#II+D847Ko@2S+E>&U!XtyMn!+Hq%KH+vIi zTC6G8E5;2<(R%8lx^t>>wL(P@DpWYzF;4)|`W4z+ zxR#E_jMa6{%vqz+<_XUKJT0(@A| zJ(jFl=EJJ|{-P`lq-|%ha@CRuNW(RJmZk^dXurP9V0DrBQo&w~DOk zW9ET^nyFw8LLH|t8+%!|0k_y(w^146>X*-bJ^K_@-cZ_josPJZfi91@ zVJGJg`h$0z2mbX+IwYkQwoja36URu54Ur9h_uUZt7?j4CF^DMM_O*L)okie;@QQa4CIX9zct8lnnHBKzP~VKhe|vXs(nXNnd0y-TQ+lK)O4z8h+vRY zL`1AEQ9-)x*^n{d$6w;;j=RU{^!4}J+;&TFdT?{?cKT8M&dRoulTm|Gp{Nk#VnvIYE_AGtx{JP*IxPG;8lxV6|g~*c<93dQ$pp3oYZ`(*E%0e zclfDU0<6gDVHk$bHOMs)>}0NjI+-yR`NW49VrecO6Z)5Wp+KMwZ_igbJaN^SBt>I{+@U)`Iz-J$b#7La zSqbS9oP<-((f!q*d1{>IH+IR3321fUSqzaabkU*w{K2blzWo-x)jxo*gp5r2zM&iC zasG20SMZ5Wndl^Z>qHV(?7h3+|vbqz>vj23?}ZtG>8UpC2m>d@S8!1w`U1J zxpZmTb<4G^Yx?{W2*5&1+rvp@Zpzjof3j-+SlH?EbjasvL?A4`ykeA0DedA7M9`!pO?)~x7Yv==;=qDh0I@?pB~BI^^U5@z664a>ydOWVwx5i)%F}2 z&l-V|=b_mz+`i;67dQMm3kbSm(#D$BY_E0t1o3aYh~eu6WG#nnT;Y+BaWFaWYIy z8o=RB_Vy@r5&bVSH#s%pB3@_yEXkzeCT2Dj7GV^&r3u2MV{mmP5q009ddZ_OrD2wy zDN)TQ3U7^=8(gfCdE|1n&HnItHUpXDSF5Po*R%BXzMnE;8|Xww3}`n8RE5Sc&{LekTQqhX#_$%(#agNJ;apLRD_tEb}%erLkN zZ^Lf4HI8|{2#(WPSrOJf=GFf;RC`Zt^=*Q(*JQwfvDfWiZn&}1b5!}v5>|a==XHBJ_(?QVv)qEoDtN+b>&eTgdp09Nqewcj z$EnXrhRaZKA~=U++0sROk713o>174AI$2Tu^z|FF!un!spv%#!1QRodgkEwR8qGGH zWqJC~h5+%q8?X3_zZ90!pN>k_{-xq|W6i!JsRH-etftz5?wZ9_{aHF(74SDqlP7Kz z=BNX)W#KzBq&)j+z7HuppV{d}ID&{==UQV)%3=Z8wFJ#5lUPzIK^eSY} znb%*f-rbdjC1K64PWY!Nn31dk%Q9ZarpPZO?!&%HoQv%?s2d}UlQ({63khI9Z>B7% zul(*_?kQq0Xp@j_=x!5}&;WSvhb}txI~EQb4Pww8<6LQvJ=0jS^V^A+#)v&Kanm|f6w(@=@@V|xs1;0e1y8r+H diff --git a/screenshots/computeraytracing.jpg b/screenshots/computeraytracing.jpg deleted file mode 100644 index a8ae316883341e5bf5ba0ff7a8ea68d024bc9d03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57014 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