├── .gitignore ├── .gitmodules ├── src ├── shaders.slang ├── shaders.h └── main.cpp ├── vulkan-triangle-modern.code-workspace ├── make.bat ├── makefile ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | **/_project/ 2 | **/_bin/ 3 | **/build/ 4 | .DS_Store 5 | cmake-build-*/ 6 | .idea/ 7 | .vs/ 8 | .cache/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/vulkan-headers"] 2 | path = external/vulkan-headers 3 | url = https://github.com/KhronosGroup/Vulkan-Headers.git 4 | [submodule "external/sdl"] 5 | path = external/sdl 6 | url = https://github.com/libsdl-org/SDL.git -------------------------------------------------------------------------------- /src/shaders.slang: -------------------------------------------------------------------------------- 1 | /* Usage: 2 | slangc shaders.slang -target spirv -emit-spirv-directly -fvk-use-entrypoint-name -source-embed-style u32 -source-embed-name shaders_spv -o shaders.h 3 | */ 4 | [vk::push_constant] float4* vertices; 5 | 6 | [shader("vertex")] 7 | float4 vertexMain(uint vid : SV_VertexID) : SV_Position 8 | { 9 | return vertices[vid]; 10 | } 11 | 12 | [shader("fragment")] 13 | float4 fragmentMain() : SV_Target 14 | { 15 | return float4(1.0, 0.0, 0.0, 1.0); 16 | } -------------------------------------------------------------------------------- /vulkan-triangle-modern.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "*.cppm": "cpp", 10 | "*.tcc": "cpp", 11 | "deque": "cpp", 12 | "vector": "cpp", 13 | "xrandr.h": "c", 14 | "xlib.h": "c", 15 | "memory": "cpp", 16 | "array": "cpp", 17 | "bitset": "cpp", 18 | "string_view": "cpp", 19 | "initializer_list": "cpp", 20 | "span": "cpp", 21 | "__hash_table": "cpp", 22 | "__split_buffer": "cpp", 23 | "queue": "cpp", 24 | "stack": "cpp", 25 | "string": "cpp", 26 | "unordered_map": "cpp", 27 | "__config": "cpp" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | set PROJECT_DIR=_project 3 | set INSTALL_DIR=_bin 4 | 5 | if "%~1"=="" goto BLANK 6 | if "%~1"=="install" goto install 7 | if "%~1"=="clean" goto CLEAN 8 | @ECHO ON 9 | 10 | :BLANK 11 | cmake -H. -B %PROJECT_DIR% -A "x64" -DCMAKE_INSTALL_PREFIX=%INSTALL_DIR% 12 | GOTO DONE 13 | 14 | :INSTALL 15 | cmake -H. -B %PROJECT_DIR% -A "x64" -DCMAKE_INSTALL_PREFIX=%INSTALL_DIR% 16 | cmake --build %PROJECT_DIR% --parallel 24 --config Debug --target install 17 | cmake --build %PROJECT_DIR% --parallel 24 --config Release --target install 18 | GOTO DONE 19 | 20 | :CLEAN 21 | rmdir /Q /S %PROJECT_DIR% 2>NUL 22 | rmdir /Q /S %INSTALL_DIR% 2>NUL 23 | GOTO DONE 24 | 25 | :DONE 26 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | OS := $(shell uname) 2 | PROJECT_DIR = _project 3 | INSTALL_DIR = _bin 4 | 5 | all: 6 | ifeq ($(OS), Darwin) 7 | cmake -H. -B${PROJECT_DIR} -G "Xcode" 8 | else 9 | cmake -H. -B${PROJECT_DIR} -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" 10 | endif 11 | 12 | debug: 13 | cmake -H. -B${PROJECT_DIR} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DCMAKE_BUILD_TYPE=Debug 14 | cmake --build ${PROJECT_DIR} --parallel 8 --target install 15 | 16 | release: 17 | cmake -H. -B${PROJECT_DIR} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release 18 | cmake --build ${PROJECT_DIR} --parallel 8 --target install 19 | 20 | install: debug release 21 | 22 | ninja-install: 23 | cmake -H. -B${PROJECT_DIR} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -G "Ninja Multi-Config" 24 | cmake --build ${PROJECT_DIR} --parallel 8 --target install --config Debug 25 | cmake --build ${PROJECT_DIR} --parallel 8 --target install --config Release 26 | 27 | clean: 28 | ${RM} -r ${PROJECT_DIR} 29 | ${RM} -r ${INSTALL_DIR} 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The meme is dead 2 | 3 | Render a vulkan triangle in ~380 lines of [code](https://github.com/fknfilewalker/vulkan-triangle-modern/blob/sdl/src/main.cpp)! (~370 without the shader code) 4 | 5 | ### Highlights 6 | * Vulkan hpp headers included as a c++ module (c++20 required) 7 | * Dynamic rendering (`VK_KHR_dynamic_rendering`) 8 | * Shader objects (`VK_EXT_shader_object`) 9 | * Bindless rendering using buffer references (`VK_EXT_buffer_device_address`) 10 | * (Resizable) BAR for device local buffer access 11 | * Deferred swapchain image allocation (`VK_KHR_swapchain_maintenance1`) 12 | * Straightforward swapchain sync (`VK_KHR_swapchain_maintenance1`) 13 | * [Slang](https://github.com/shader-slang/slang) used for shader code 14 | * Modular code 15 | * [SDL3](https://github.com/libsdl-org/SDL.git) for window handling 16 | 17 | ### How to build (on windows) 18 | Use CMake for project configuration. The included `make.bat` script can be used for this. The Vulkan SDK is not required to run this code. Only for validation layers a Vulkan SDK installation is necessary. 19 | 20 | > Please clone this repository with submodule! 21 | 22 | ### Notes 23 | * `eDeferredMemoryAllocationEXT` can crash the app when apps like RiverTuner are running in the back (deactivated, see line \#107) 24 | * Visual Studio (Code) still has problems with module syntax highlighting, possible solutions are 25 | * (VS) installing [ReSharper](https://de.wikipedia.org/wiki/ReSharper) 26 | * (VSC) using [clangd](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) with the argument `--experimental-modules-support` 27 | * switching the module import to `#include ` 28 | * Linux with Clang works (LLVM version >= 18.0.0 + Ninja build version >= 1.11) 29 | * On MacOS use the latest version of LLVM/Clang from brew and install the Vulkan SDK 30 | -------------------------------------------------------------------------------- /src/shaders.h: -------------------------------------------------------------------------------- 1 | const uint32_t shaders_spv[] = 2 | { 3 | 0x07230203, 0x00010500, 0x00280000, 0x00000023, 0x00000000, 0x00020011, 0x000014e3, 0x00020011, 0x00000001, 4 | 0x0009000a, 0x5f565053, 0x5f52484b, 0x73796870, 0x6c616369, 0x6f74735f, 0x65676172, 0x6675625f, 0x00726566, 5 | 0x0003000e, 0x000014e4, 0x00000001, 0x0009000f, 0x00000000, 0x00000002, 0x74726576, 0x614d7865, 0x00006e69, 6 | 0x00000010, 0x00000018, 0x00000008, 0x0008000f, 0x00000004, 0x0000001b, 0x67617266, 0x746e656d, 0x6e69614d, 7 | 0x00000000, 0x0000001d, 0x00030010, 0x0000001b, 0x00000007, 0x00030003, 0x0000000b, 0x00000001, 0x00050005, 8 | 0x0000000b, 0x66756263, 0x5f726566, 0x0000745f, 0x00050005, 0x00000010, 0x74726576, 0x73656369, 0x00000000, 9 | 0x00050005, 0x00000002, 0x74726576, 0x614d7865, 0x00006e69, 0x000a0005, 0x0000001d, 0x72746e65, 0x696f5079, 10 | 0x6150746e, 0x5f6d6172, 0x67617266, 0x746e656d, 0x6e69614d, 0x00000000, 0x00060005, 0x0000001b, 0x67617266, 11 | 0x746e656d, 0x6e69614d, 0x00000000, 0x00040047, 0x00000008, 0x0000000b, 0x0000002a, 0x00040047, 0x0000000e, 12 | 0x00000006, 0x00000010, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x00000023, 13 | 0x00000000, 0x00040047, 0x00000018, 0x0000000b, 0x00000000, 0x00040047, 0x0000001d, 0x0000001e, 0x00000000, 14 | 0x00020013, 0x00000001, 0x00030021, 0x00000003, 0x00000001, 0x00040015, 0x00000005, 0x00000020, 0x00000001, 15 | 0x00040020, 0x00000007, 0x00000001, 0x00000005, 0x00040015, 0x00000009, 0x00000020, 0x00000000, 0x00030016, 16 | 0x0000000c, 0x00000020, 0x00040017, 0x0000000d, 0x0000000c, 0x00000004, 0x00040020, 0x0000000e, 0x000014e5, 17 | 0x0000000d, 0x0003001e, 0x0000000b, 0x0000000e, 0x00040020, 0x0000000f, 0x00000009, 0x0000000b, 0x0004002b, 18 | 0x00000005, 0x00000011, 0x00000000, 0x00040020, 0x00000012, 0x00000009, 0x0000000e, 0x00040020, 0x00000017, 19 | 0x00000003, 0x0000000d, 0x0004002b, 0x0000000c, 0x0000001f, 0x3f800000, 0x0004002b, 0x0000000c, 0x00000020, 20 | 0x00000000, 0x0007002c, 0x0000000d, 0x0000001e, 0x0000001f, 0x00000020, 0x00000020, 0x0000001f, 0x0004003b, 21 | 0x00000007, 0x00000008, 0x00000001, 0x0004003b, 0x0000000f, 0x00000010, 0x00000009, 0x0004003b, 0x00000017, 22 | 0x00000018, 0x00000003, 0x0004003b, 0x00000017, 0x0000001d, 0x00000003, 0x00050036, 0x00000001, 0x00000002, 23 | 0x00000000, 0x00000003, 0x000200f8, 0x00000004, 0x0004003d, 0x00000005, 0x00000006, 0x00000008, 0x0004007c, 24 | 0x00000009, 0x0000000a, 0x00000006, 0x00050041, 0x00000012, 0x00000013, 0x00000010, 0x00000011, 0x0004003d, 25 | 0x0000000e, 0x00000014, 0x00000013, 0x00050043, 0x0000000e, 0x00000015, 0x00000014, 0x0000000a, 0x0006003d, 26 | 0x0000000d, 0x00000016, 0x00000015, 0x00000002, 0x00000004, 0x0003003e, 0x00000018, 0x00000016, 0x000100fd, 27 | 0x00010038, 0x00050036, 0x00000001, 0x0000001b, 0x00000000, 0x00000003, 0x000200f8, 0x0000001c, 0x0003003e, 28 | 0x0000001d, 0x0000001e, 0x000100fd, 0x00010038, 29 | }; 30 | 31 | const size_t shaders_spv_sizeInBytes = 916; 32 | 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | project(vulkan-triangle-modern) 3 | 4 | #================================# 5 | # Dependencies # 6 | #================================# 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_EXTENSIONS OFF) 9 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") 11 | set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} -Wno-deprecated-declarations") 12 | # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") 13 | endif () 14 | 15 | set(VTM_EXTERNAL_FOLDER "Dependencies") 16 | set(VTM_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 17 | set(VTM_EXTERNAL_DIR "${VTM_SOURCE_DIR}/external") 18 | 19 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 20 | 21 | #================================# 22 | # Dependencies # 23 | #================================# 24 | # set up vulkan C++ module target 25 | add_library(VulkanCppModule) 26 | target_sources(VulkanCppModule PUBLIC FILE_SET CXX_MODULES 27 | FILES "${VTM_EXTERNAL_DIR}/vulkan-headers/include/vulkan/vulkan.cppm" 28 | ) 29 | target_include_directories(VulkanCppModule PUBLIC "${VTM_EXTERNAL_DIR}/vulkan-headers/include") 30 | target_compile_definitions(VulkanCppModule PUBLIC 31 | VULKAN_HPP_NO_SMART_HANDLE 32 | VULKAN_HPP_NO_SPACESHIP_OPERATOR 33 | VULKAN_HPP_NO_TO_STRING 34 | VULKAN_HPP_NO_STD_MODULE 35 | VK_NO_PROTOTYPES 36 | ) 37 | set_target_properties(VulkanCppModule PROPERTIES FOLDER "${VTM_EXTERNAL_FOLDER}/vulkan") 38 | 39 | if(WIN32) 40 | target_compile_definitions(VulkanCppModule PUBLIC 41 | VK_USE_PLATFORM_WIN32_KHR 42 | NOMINMAX 43 | ) 44 | elseif(APPLE) 45 | target_compile_definitions(VulkanCppModule PUBLIC 46 | VK_USE_PLATFORM_METAL_EXT 47 | ) 48 | find_package(Vulkan REQUIRED) 49 | target_link_libraries(VulkanCppModule PUBLIC "Vulkan::Vulkan") 50 | elseif(UNIX) 51 | target_compile_definitions(VulkanCppModule PUBLIC 52 | VK_USE_PLATFORM_XLIB_KHR 53 | VK_USE_PLATFORM_WAYLAND_KHR 54 | ) 55 | endif() 56 | 57 | set(SDL_DISABLE_UNINSTALL ON) 58 | set(SDL_TEST_LIBRARY OFF) 59 | set(SDL_SHARED OFF) 60 | set(SDL_STATIC ON) 61 | # # subsystems 62 | set(SDL_AUDIO OFF) 63 | set(SDL_RENDER OFF) 64 | set(SDL_CAMERA OFF) 65 | set(SDL_JOYSTICK OFF) 66 | set(SDL_HAPTIC OFF) 67 | set(SDL_HIDAPI ON) 68 | set(SDL_SENSOR OFF) 69 | set(SDL_DIALOG OFF) 70 | set(SDL_DIRECTX OFF) 71 | set(SDL_OPENGLES OFF) 72 | set(SDL_VULKAN OFF) 73 | set(SDL_OFFSCREEN OFF) 74 | add_subdirectory("${VTM_EXTERNAL_DIR}/sdl" EXCLUDE_FROM_ALL) 75 | target_compile_definitions(SDL3-static PUBLIC SDL_MAIN_HANDLED) 76 | set_target_properties(SDL3-static PROPERTIES FOLDER "${VTM_EXTERNAL_FOLDER}/sdl") 77 | set_target_properties(SDL_uclibc PROPERTIES FOLDER "${VTM_EXTERNAL_FOLDER}/sdl") 78 | 79 | #================================# 80 | # Main Target # 81 | #================================# 82 | add_executable(${PROJECT_NAME} src/main.cpp src/shaders.h) 83 | target_link_libraries(${PROJECT_NAME} PRIVATE VulkanCppModule SDL3-static) 84 | add_dependencies(${PROJECT_NAME} VulkanCppModule SDL3-static) 85 | 86 | install(TARGETS ${PROJECT_NAME} CONFIGURATIONS Debug DESTINATION "debug") 87 | install(TARGETS ${PROJECT_NAME} CONFIGURATIONS Release DESTINATION "release") 88 | 89 | #================================# 90 | # IDE specific setup # 91 | #================================# 92 | if(CMAKE_GENERATOR MATCHES "Visual Studio") 93 | set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 94 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 95 | elseif(CMAKE_GENERATOR MATCHES "Xcode") 96 | set(CMAKE_XCODE_GENERATE_SCHEME ON) 97 | set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_GENERATE_SCHEME ON) 98 | set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_SCHEME_WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 99 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY XCODE_STARTUP_PROJECT ${PROJECT_NAME}) 100 | endif() 101 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef _WIN32 3 | #include 4 | #elif defined(__linux__) 5 | #include 6 | #include 7 | #endif 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "shaders.h" 18 | import vulkan_hpp; // modules should come after all includes 19 | 20 | #ifdef __APPLE__ 21 | constexpr bool isApple = true; 22 | #else 23 | constexpr bool isApple = false; 24 | #endif 25 | 26 | constexpr struct { uint32_t width, height; } target { 800u, 600u }; // our window 27 | [[maybe_unused]] constexpr std::string_view shaders = R"( 28 | [vk::push_constant] float3* vertices; 29 | 30 | [shader("vertex")] 31 | float4 vertexMain(uint vid : SV_VertexID) : SV_Position 32 | { 33 | return float4(vertices[vid], 1.0); 34 | } 35 | 36 | [shader("fragment")] 37 | float4 fragmentMain() : SV_Target 38 | { 39 | return float4(1.0, 0.0, 0.0, 1.0); 40 | })"; 41 | 42 | [[noreturn]] void exitWithError(const std::string_view error) { 43 | std::printf("%s\n", error.data()); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | template 48 | bool extensionsOrLayersAvailable(const std::vector& available, const std::vector& requested) { 49 | static_assert(std::is_same_v || std::is_same_v); 50 | return std::all_of(requested.begin(), requested.end(), [&available](const char* requestedElement) { 51 | return std::find_if(available.begin(), available.end(), [requestedElement](const T& availableElement) { 52 | if constexpr (std::is_same_v) return std::string_view{ availableElement.layerName.data() } == requestedElement; 53 | else if constexpr (std::is_same_v) return std::string_view{ availableElement.extensionName.data() } == requestedElement; 54 | }) != available.end(); 55 | }); 56 | } 57 | 58 | std::optional findQueueFamilyIndex(const std::vector& queueFamiliesProperties, const vk::QueueFlags queueFlags) { 59 | std::optional bestFamily; 60 | std::bitset<12> bestScore = 0; 61 | for (uint32_t i = 0; i < queueFamiliesProperties.size(); i++) { 62 | // check if queue family supports all requested queue flags 63 | if (static_cast(queueFamiliesProperties[i].queueFlags & queueFlags) == static_cast(queueFlags)) { 64 | const std::bitset<12> score = static_cast(queueFamiliesProperties[i].queueFlags); 65 | // use queue family with the least other bits set 66 | if (!bestFamily.has_value() || score.count() < bestScore.count()) { 67 | bestFamily = i; 68 | bestScore = score; 69 | } 70 | } 71 | } 72 | return bestFamily; 73 | } 74 | 75 | struct Device : vk::raii::Device 76 | { 77 | using QueueFamily = uint32_t; 78 | using QueueCount = uint32_t; 79 | using Queues = std::unordered_map; 80 | Device(const vk::raii::PhysicalDevice& physicalDevice, const std::vector& extensions, const Queues& queues, const void* pNext) : 81 | vk::raii::Device{ nullptr }, physicalDevice{ physicalDevice }, memoryProperties{ physicalDevice.getMemoryProperties() } 82 | { 83 | constexpr float priority = 1.0f; 84 | std::vector deviceQueueCreateInfos; 85 | deviceQueueCreateInfos.reserve(queues.size()); 86 | for (const auto& [queueFamilyIndex, queueCount] : queues) { 87 | deviceQueueCreateInfos.emplace_back(vk::DeviceQueueCreateInfo{ {}, queueFamilyIndex, queueCount, &priority }); 88 | } 89 | const vk::DeviceCreateInfo deviceCreateInfo{ {}, deviceQueueCreateInfos, {}, extensions,{}, pNext }; 90 | vk::raii::Device::operator=({ physicalDevice, deviceCreateInfo }); 91 | // get all our queues -> queue[family][index] 92 | for (const auto& [queueFamilyIndex, queueCount] : queues) { 93 | queue.emplace_back( queueCount, nullptr ); 94 | for (uint32_t i = 0; i < queueCount; ++i) queue.back()[i] = getQueue(queueFamilyIndex, i); 95 | } 96 | } 97 | 98 | [[nodiscard]] std::optional findMemoryTypeIndex(const vk::MemoryRequirements& requirements, const vk::MemoryPropertyFlags properties) const 99 | { 100 | for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i) { 101 | if ((requirements.memoryTypeBits & (1u << i)) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) return i; 102 | } 103 | return std::nullopt; 104 | } 105 | 106 | operator const vk::raii::PhysicalDevice& () const { return physicalDevice; } 107 | 108 | std::vector> queue; 109 | vk::raii::PhysicalDevice physicalDevice; 110 | vk::PhysicalDeviceMemoryProperties memoryProperties; 111 | }; 112 | 113 | // Every resource has a device reference 114 | struct Resource { std::shared_ptr dev; }; 115 | 116 | struct Buffer : vk::raii::Buffer, Resource 117 | { 118 | Buffer(const std::shared_ptr& device, const vk::DeviceSize size, const vk::BufferUsageFlags usageFlags, const vk::MemoryPropertyFlags memoryPropertyFlags) 119 | : vk::raii::Buffer{ *device, { {}, size, usageFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress } }, Resource{ device }, memory{ nullptr } 120 | { 121 | const auto memoryRequirements = getMemoryRequirements(); 122 | const auto memoryTypeIndex = dev->findMemoryTypeIndex(memoryRequirements, memoryPropertyFlags); 123 | if (!memoryTypeIndex.has_value()) exitWithError("No memory type index found"); 124 | constexpr vk::MemoryAllocateFlagsInfo memoryAllocateFlagsInfo{ vk::MemoryAllocateFlagBits::eDeviceAddress }; 125 | const vk::MemoryAllocateInfo memoryAllocateInfo{ memoryRequirements.size, memoryTypeIndex.value(), &memoryAllocateFlagsInfo }; 126 | memory = vk::raii::DeviceMemory{ *dev, memoryAllocateInfo }; 127 | bindMemory(*memory, 0); 128 | 129 | const vk::BufferDeviceAddressInfo bufferDeviceAddressInfo{ **this }; 130 | deviceAddress = dev->getBufferAddress(bufferDeviceAddressInfo); /* for bindless rendering */ 131 | } 132 | vk::raii::DeviceMemory memory; 133 | vk::DeviceAddress deviceAddress; 134 | }; 135 | 136 | struct Swapchain : Resource 137 | { 138 | // Data for one frame/image in our swapchain, recreated every frame 139 | struct Frame { 140 | Frame(const vk::raii::Device& device, const vk::raii::CommandPool& commandPool) : 141 | presentFinishFence{ device, vk::FenceCreateInfo{} }, imageAvailableSemaphore{ device, vk::SemaphoreCreateInfo{} }, renderFinishedSemaphore{ device, vk::SemaphoreCreateInfo{} }, 142 | commandBuffer{ std::move(vk::raii::CommandBuffers{ device, { *commandPool, vk::CommandBufferLevel::ePrimary, 1 } }[0]) } 143 | {} 144 | vk::raii::Fence presentFinishFence; 145 | vk::raii::Semaphore imageAvailableSemaphore, renderFinishedSemaphore; 146 | vk::raii::CommandBuffer commandBuffer; 147 | }; 148 | 149 | Swapchain(const std::shared_ptr& device, const vk::raii::SurfaceKHR& surface, const uint32_t queueFamilyIndex) : Resource{ device }, currentImageIdx{ 0 }, previousImageIdx{ 0 }, 150 | swapchain{ nullptr }, commandPool{ *dev, { vk::CommandPoolCreateFlagBits::eTransient, queueFamilyIndex } } 151 | { 152 | const auto surfaceCapabilities = dev->physicalDevice.getSurfaceCapabilitiesKHR(*surface); 153 | const auto surfaceFormats = dev->physicalDevice.getSurfaceFormatsKHR(*surface); 154 | 155 | imageCount = std::max(3u, surfaceCapabilities.minImageCount); 156 | if (surfaceCapabilities.maxImageCount) imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount); 157 | swapchainCreateInfo = vk::SwapchainCreateInfoKHR{ { /* vk::SwapchainCreateFlagBitsKHR::eDeferredMemoryAllocation */ }, // causes problems with apps like RiverTuner 158 | *surface, imageCount, surfaceFormats[0].format, surfaceFormats[0].colorSpace, surfaceCapabilities.currentExtent, 159 | 1u, vk::ImageUsageFlagBits::eColorAttachment }.setPresentMode(vk::PresentModeKHR::eImmediate); 160 | createSwapchain(); 161 | } 162 | 163 | void createSwapchain(uint32_t width = 0, uint32_t height = 0) { 164 | const auto sc = dev->physicalDevice.getSurfaceCapabilitiesKHR(swapchainCreateInfo.surface); 165 | const bool valid = sc.currentExtent.width != std::numeric_limits::max(); 166 | swapchainCreateInfo.imageExtent.width = valid ? sc.currentExtent.width : std::clamp(width, sc.minImageExtent.width, sc.maxImageExtent.width); 167 | swapchainCreateInfo.imageExtent.height = valid ? sc.currentExtent.height : std::clamp(height, sc.minImageExtent.height, sc.maxImageExtent.height); 168 | swapchainCreateInfo.oldSwapchain = *swapchain; 169 | swapchain = vk::raii::SwapchainKHR{ *dev, swapchainCreateInfo }; 170 | images = swapchain.getImages(); 171 | views.clear(); for (const auto& image : images) views.emplace_back(nullptr); 172 | } 173 | 174 | Frame& acquireNewFrame() { 175 | for (auto it = frames.begin(); it != frames.end(); (it->presentFinishFence.getStatus() == vk::Result::eSuccess) ? it = frames.erase(it) : ++it) {} 176 | frames.emplace_back(*dev, commandPool); // create a new frame 177 | return frames.back(); 178 | } 179 | 180 | void acquireNextImage() { 181 | auto& frame = acquireNewFrame(); 182 | currentImageIdx = swapchain.acquireNextImage(UINT64_MAX, *frame.imageAvailableSemaphore).value; 183 | /* create image view after image is acquired because of vk::SwapchainCreateFlagBitsKHR::eDeferredMemoryAllocationEXT */ 184 | if(not *views[currentImageIdx]) { 185 | views[currentImageIdx] = vk::raii::ImageView{ *dev, vk::ImageViewCreateInfo{ {}, images[currentImageIdx], vk::ImageViewType::e2D, 186 | swapchainCreateInfo.imageFormat, {}, { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } } }; 187 | } 188 | frame.commandBuffer.begin({}); 189 | } 190 | 191 | void submitImage(const vk::raii::Queue& presentQueue) { 192 | const auto& frame = frames.back(); 193 | frame.commandBuffer.end(); 194 | 195 | constexpr vk::PipelineStageFlags waitDstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; 196 | presentQueue.submit(vk::SubmitInfo{ *frame.imageAvailableSemaphore, 197 | waitDstStageMask, *frame.commandBuffer, *frame.renderFinishedSemaphore }); 198 | const vk::SwapchainPresentFenceInfoKHR presentFenceInfo{ *frame.presentFinishFence }; 199 | auto _ = presentQueue.presentKHR({ *frame.renderFinishedSemaphore, *swapchain, currentImageIdx, {}, &presentFenceInfo }); 200 | } 201 | 202 | Frame& getCurrentFrame() { return frames.back(); } 203 | vk::Image& getCurrentImage() { return images[currentImageIdx]; } 204 | vk::raii::ImageView& getCurrentImageView() { return views[currentImageIdx]; } 205 | [[nodiscard]] const vk::Extent2D& extent() const { return swapchainCreateInfo.imageExtent; } 206 | 207 | vk::SwapchainCreateInfoKHR swapchainCreateInfo; 208 | uint32_t imageCount, currentImageIdx, previousImageIdx; 209 | vk::raii::SwapchainKHR swapchain; 210 | std::vector images; 211 | std::vector views; 212 | vk::raii::CommandPool commandPool; 213 | std::deque frames; 214 | }; 215 | 216 | struct Shader : Resource 217 | { 218 | using Stage = std::tuple, std::string_view>; // stage, spv, entry 219 | Shader(const std::shared_ptr& device, const std::vector& shaderStages, const std::vector& pcRanges) : Resource{ device }, 220 | shaders{ shaderStages.size(), nullptr }, stages{ shaderStages.size() }, layout{ *dev, vk::PipelineLayoutCreateInfo{}.setPushConstantRanges(pcRanges) } 221 | { 222 | std::vector shaderCreateInfos{ shaderStages.size(), vk::ShaderCreateInfoEXT{ shaderStages.size() > 1u ? vk::ShaderCreateFlagBitsEXT::eLinkStage : vk::ShaderCreateFlagsEXT{} } 223 | .setCodeType(vk::ShaderCodeTypeEXT::eSpirv).setPushConstantRanges(pcRanges) }; 224 | for (size_t i = 0; i < shaderStages.size(); ++i) { 225 | shaderCreateInfos[i].setStage(std::get<0>(shaderStages[i])).setPName(std::get<2>(shaderStages[i]).data()); 226 | if (i < (shaderStages.size() - 1)) shaderCreateInfos[i].setNextStage(std::get<0>(shaderStages[i + 1u])); 227 | shaderCreateInfos[i].setCode(std::get<1>(shaderStages[i])); 228 | stages[i] = std::get<0>(shaderStages[i]); 229 | } 230 | _shaders = dev->createShadersEXT(shaderCreateInfos); 231 | for (size_t i = 0; i < shaderStages.size(); ++i) shaders[i] = *_shaders[i]; // needed in order to pass the vector directly to bindShadersEXT() 232 | } 233 | std::vector _shaders; 234 | std::vector shaders; 235 | std::vector stages; 236 | vk::raii::PipelineLayout layout; 237 | }; 238 | 239 | int main(int /*argc*/, char** /*argv*/) 240 | { 241 | if (!SDL_Init(0)) exitWithError("Failed to init SDL"); 242 | SDL_Window* window = SDL_CreateWindow("Vulkan Triangle Modern", target.width, target.height, SDL_WINDOW_RESIZABLE); 243 | 244 | const vk::raii::Context context; 245 | // Instance Setup 246 | std::vector iExtensions{ vk::KHRSurfaceExtensionName, vk::KHRSurfaceMaintenance1ExtensionName, vk::KHRGetSurfaceCapabilities2ExtensionName }; 247 | #ifdef VK_USE_PLATFORM_WIN32_KHR 248 | iExtensions.emplace_back(vk::KHRWin32SurfaceExtensionName); 249 | #elif VK_USE_PLATFORM_XLIB_KHR 250 | iExtensions.emplace_back(vk::KHRXlibSurfaceExtensionName); 251 | #elif VK_USE_PLATFORM_WAYLAND_KHR 252 | iExtensions.emplace_back(vk::KHRWaylandSurfaceExtensionName); 253 | #elif VK_USE_PLATFORM_METAL_EXT 254 | iExtensions.emplace_back(vk::EXTMetalSurfaceExtensionName); 255 | #endif 256 | if constexpr (isApple) iExtensions.emplace_back(vk::KHRPortabilityEnumerationExtensionName); 257 | 258 | std::vector iLayers = { "VK_LAYER_LUNARG_monitor" }; 259 | #if !defined( NDEBUG ) 260 | iLayers.emplace_back("VK_LAYER_KHRONOS_validation"); 261 | #endif 262 | if (!extensionsOrLayersAvailable(context.enumerateInstanceLayerProperties(), iLayers)) iLayers.clear(); 263 | iLayers.emplace_back("VK_LAYER_KHRONOS_shader_object"); // always try to activate this layer since many drivers still require this (requires Vulkan SDK though) 264 | if (!extensionsOrLayersAvailable(context.enumerateInstanceLayerProperties(), iLayers)) iLayers.clear(); 265 | if (!extensionsOrLayersAvailable(context.enumerateInstanceExtensionProperties(), iExtensions)) exitWithError("Instance extensions not available"); 266 | 267 | constexpr vk::ApplicationInfo applicationInfo{ nullptr, 0, nullptr, 0, vk::ApiVersion13 }; 268 | vk::InstanceCreateInfo instanceCreateInfo{ {}, &applicationInfo, iLayers, iExtensions }; 269 | if constexpr (isApple) instanceCreateInfo.setFlags(vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR); 270 | const vk::raii::Instance instance(context, instanceCreateInfo); 271 | 272 | // Surface Setup 273 | vk::raii::SurfaceKHR surface { nullptr }; 274 | auto windowProps = SDL_GetWindowProperties(window); 275 | #ifdef VK_USE_PLATFORM_WIN32_KHR 276 | surface = vk::raii::SurfaceKHR{ instance, vk::Win32SurfaceCreateInfoKHR{ {}, nullptr, static_cast(SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)) } }; 277 | #elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) 278 | if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { 279 | Display *xdisplay = (Display *)SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); 280 | Window xwindow = (Window)SDL_GetNumberProperty(windowProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); 281 | surface = vk::raii::SurfaceKHR{ instance, vk::XlibSurfaceCreateInfoKHR{ {}, xdisplay, xwindow } }; 282 | } 283 | else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { 284 | wl_display* wldisplay = (wl_display*)SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr); 285 | wl_surface* wlsurface = (wl_surface*)SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); 286 | surface = vk::raii::SurfaceKHR{ instance, vk::WaylandSurfaceCreateInfoKHR{ {}, wldisplay, wlsurface } }; 287 | } 288 | #elif defined(VK_USE_PLATFORM_METAL_EXT) 289 | surface = vk::raii::SurfaceKHR{ instance, vk::MetalSurfaceCreateInfoEXT{ {}, SDL_Metal_GetLayer(SDL_Metal_CreateView(window)) }}; 290 | #endif 291 | // Device setup 292 | const vk::raii::PhysicalDevices physicalDevices{ instance }; 293 | const vk::raii::PhysicalDevice& physicalDevice{ physicalDevices[0] }; 294 | // * find queue 295 | const auto queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); 296 | const auto queueFamilyIndex = findQueueFamilyIndex(queueFamilyProperties, vk::QueueFlagBits::eGraphics); 297 | if (!queueFamilyIndex.has_value()) exitWithError("No queue family index found"); 298 | if (!physicalDevice.getSurfaceSupportKHR(queueFamilyIndex.value(), *surface)) exitWithError("Queue family does not support presentation"); 299 | // * check extensions 300 | std::vector dExtensions{ vk::KHRSwapchainExtensionName, vk::KHRSwapchainMaintenance1ExtensionName, vk::EXTShaderObjectExtensionName }; 301 | if constexpr (isApple) dExtensions.emplace_back("VK_KHR_portability_subset"); 302 | if (!extensionsOrLayersAvailable(physicalDevice.enumerateDeviceExtensionProperties(), dExtensions)) exitWithError("Device extensions not available"); 303 | // * activate features 304 | auto vulkan13Features = vk::PhysicalDeviceVulkan13Features{}.setDynamicRendering(true).setSynchronization2(true); 305 | auto vulkan12Features = vk::PhysicalDeviceVulkan12Features{}.setBufferDeviceAddress(true).setPNext(vulkan13Features); 306 | auto shaderObjectFeatures = vk::PhysicalDeviceShaderObjectFeaturesEXT{}.setShaderObject(true).setPNext(vulkan12Features); 307 | auto swapchainMaintenanceFeatures = vk::PhysicalDeviceSwapchainMaintenance1FeaturesEXT{}.setSwapchainMaintenance1(true).setPNext(shaderObjectFeatures); 308 | auto physicalDeviceFeatures2 = vk::PhysicalDeviceFeatures2{}.setPNext(swapchainMaintenanceFeatures); 309 | // * create device 310 | const auto device = std::make_shared(physicalDevice, dExtensions, Device::Queues{{queueFamilyIndex.value(), 1}}, &swapchainMaintenanceFeatures); 311 | 312 | // Vertex buffer setup (triangle is upside down on purpose) 313 | const std::vector vertices = { 314 | -0.5f, -0.5f, 0.0f, 1.0f, 315 | 0.5f, -0.5f, 0.0f, 1.0f, 316 | 0.0f, 0.5f, 0.0f, 1.0f 317 | }; 318 | const size_t verticesSize = vertices.size() * sizeof(float); 319 | const Buffer buffer{ device, verticesSize, {}, vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible }; /* reBAR */ 320 | void* const p = buffer.memory.mapMemory(0, vk::WholeSize); 321 | std::memcpy(p, vertices.data(), verticesSize); 322 | buffer.memory.unmapMemory(); 323 | 324 | // Shader object setup : https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_EXT_shader_object.adoc 325 | constexpr vk::PushConstantRange pcRange{ vk::ShaderStageFlagBits::eVertex, 0, sizeof(uint64_t) }; 326 | const Shader shader{ device, { { vk::ShaderStageFlagBits::eVertex, shaders_spv, "vertexMain" }, { vk::ShaderStageFlagBits::eFragment, shaders_spv, "fragmentMain" } }, { pcRange } }; 327 | 328 | // Swapchain setup 329 | Swapchain swapchain{ device, surface, queueFamilyIndex.value() }; 330 | auto imgMemBarrier = vk::ImageMemoryBarrier2{}.setSubresourceRange({ vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 }); 331 | const vk::DependencyInfo dependencyInfo = vk::DependencyInfo{}.setImageMemoryBarriers(imgMemBarrier); 332 | 333 | bool running = true, minimized = false; 334 | while (running) { 335 | SDL_Event event; 336 | while (SDL_PollEvent(&event)) { 337 | if (event.type == SDL_EVENT_QUIT) { running = false; } 338 | else if (event.type == SDL_EVENT_WINDOW_MINIMIZED) { minimized = true; } 339 | else if (event.type == SDL_EVENT_WINDOW_RESTORED) { swapchain.createSwapchain(); minimized = false; } 340 | else if (event.type == SDL_EVENT_WINDOW_RESIZED) { swapchain.createSwapchain(event.window.data1, event.window.data2); } 341 | } 342 | if (minimized) continue; 343 | 344 | swapchain.acquireNextImage(); 345 | const auto& frame = swapchain.getCurrentFrame(); 346 | const auto& cmdBuffer = frame.commandBuffer; 347 | 348 | imgMemBarrier.setImage(swapchain.getCurrentImage()) 349 | .setOldLayout(vk::ImageLayout::eUndefined).setNewLayout(vk::ImageLayout::eColorAttachmentOptimal) 350 | .setSrcStageMask(vk::PipelineStageFlagBits2::eAllCommands).setSrcAccessMask(vk::AccessFlagBits2::eNone) 351 | .setDstStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput).setDstAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite); 352 | cmdBuffer.pipelineBarrier2(dependencyInfo); 353 | 354 | const auto rAttachmentInfo = vk::RenderingAttachmentInfo{ *swapchain.getCurrentImageView(), vk::ImageLayout::eColorAttachmentOptimal} 355 | .setLoadOp(vk::AttachmentLoadOp::eClear).setStoreOp(vk::AttachmentStoreOp::eStore).setClearValue(vk::ClearColorValue{ 0.0f, 0.0f, 0.0f, 1.0f }); 356 | cmdBuffer.beginRendering({ {}, { {}, swapchain.extent() }, 1, 0, 1, &rAttachmentInfo }); 357 | { 358 | /* set render state for shader objects */ 359 | cmdBuffer.bindShadersEXT(shader.stages, shader.shaders); 360 | cmdBuffer.pushConstants(*shader.layout, vk::ShaderStageFlagBits::eVertex, 0, /* for bindless rendering */ buffer.deviceAddress); 361 | cmdBuffer.setPrimitiveTopologyEXT(vk::PrimitiveTopology::eTriangleList); 362 | cmdBuffer.setPolygonModeEXT(vk::PolygonMode::eFill); 363 | cmdBuffer.setFrontFaceEXT(vk::FrontFace::eCounterClockwise); 364 | cmdBuffer.setCullModeEXT(vk::CullModeFlagBits::eNone); 365 | cmdBuffer.setColorWriteMaskEXT(0, vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB); 366 | cmdBuffer.setSampleMaskEXT(vk::SampleCountFlagBits::e1, { 0xffffffff }); 367 | cmdBuffer.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); 368 | cmdBuffer.setViewportWithCountEXT({ { 0, 0, static_cast(swapchain.extent().width), static_cast(swapchain.extent().height) } }); 369 | cmdBuffer.setScissorWithCountEXT({ { { 0, 0 }, swapchain.extent()}}); 370 | cmdBuffer.setVertexInputEXT({}, {}); 371 | cmdBuffer.setColorBlendEnableEXT(0, { false }); 372 | cmdBuffer.setDepthTestEnableEXT(false); 373 | cmdBuffer.setDepthWriteEnableEXT(false); 374 | cmdBuffer.setDepthBiasEnableEXT(false); 375 | cmdBuffer.setStencilTestEnableEXT(false); 376 | cmdBuffer.setRasterizerDiscardEnableEXT(false); 377 | cmdBuffer.setColorBlendEquationEXT(0, vk::ColorBlendEquationEXT{}.setSrcColorBlendFactor(vk::BlendFactor::eOne)); 378 | cmdBuffer.setAlphaToCoverageEnableEXT(false); 379 | cmdBuffer.setPrimitiveRestartEnableEXT(false); 380 | cmdBuffer.draw(3, 1, 0, 0); 381 | } 382 | cmdBuffer.endRendering(); 383 | 384 | imgMemBarrier.setOldLayout(vk::ImageLayout::eColorAttachmentOptimal).setNewLayout(vk::ImageLayout::ePresentSrcKHR) 385 | .setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput).setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite) 386 | .setDstStageMask(vk::PipelineStageFlagBits2::eNone).setDstAccessMask(vk::AccessFlagBits2::eNone); 387 | cmdBuffer.pipelineBarrier2(dependencyInfo); 388 | swapchain.submitImage(device->queue[queueFamilyIndex.value()][0]); 389 | } 390 | device->waitIdle(); 391 | SDL_DestroyWindow(window); 392 | SDL_Quit(); 393 | return 0; 394 | } 395 | --------------------------------------------------------------------------------