├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── cmake ├── FindSDL2.cmake ├── FindVulkan.cmake └── SPIRV2C.cmake ├── frag.frag ├── main.cpp └── vert.vert /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.raw 3 | build/ 4 | *.dll 5 | *.jpg 6 | 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(sdl2_vulkan) 3 | 4 | add_definitions(-DNOMINMAX -DSDL_MAIN_HANDLED) 5 | 6 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake") 7 | 8 | find_package(Threads REQUIRED) 9 | find_package(SDL2 REQUIRED) 10 | find_package(Vulkan REQUIRED) 11 | 12 | add_spirv_embed_library(spirv_shaders vert.vert frag.frag) 13 | 14 | add_executable(sdl2_vulkan main.cpp) 15 | 16 | set_target_properties(sdl2_vulkan PROPERTIES 17 | CXX_STANDARD 14 18 | CXX_STANDARD_REQUIRED ON) 19 | 20 | target_include_directories(sdl2_vulkan PUBLIC 21 | $) 22 | 23 | target_link_libraries(sdl2_vulkan PUBLIC 24 | spirv_shaders Vulkan::Vulkan ${SDL2_LIBRARY}) 25 | 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Will Usher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SDL2 + Vulkan Example 2 | 3 | An example of how Vulkan can be used to render into an SDL2 created window. 4 | 5 | -------------------------------------------------------------------------------- /cmake/FindSDL2.cmake: -------------------------------------------------------------------------------- 1 | # Locate SDL2 library 2 | # This module defines 3 | # SDL2_LIBRARY, the name of the library to link against 4 | # SDL2_FOUND, if false, do not try to link to SDL2 5 | # SDL2_INCLUDE_DIR, where to find SDL.h 6 | # 7 | # This module responds to the the flag: 8 | # SDL2_BUILDING_LIBRARY 9 | # If this is defined, then no SDL2_main will be linked in because 10 | # only applications need main(). 11 | # Otherwise, it is assumed you are building an application and this 12 | # module will attempt to locate and set the the proper link flags 13 | # as part of the returned SDL2_LIBRARY variable. 14 | # 15 | # Don't forget to include SDL2main.h and SDL2main.m your project for the 16 | # OS X framework based version. (Other versions link to -lSDL2main which 17 | # this module will try to find on your behalf.) Also for OS X, this 18 | # module will automatically add the -framework Cocoa on your behalf. 19 | # 20 | # 21 | # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration 22 | # and no SDL2_LIBRARY, it means CMake did not find your SDL2 library 23 | # (SDL2.dll, libsdl2.so, SDL2.framework, etc). 24 | # Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. 25 | # Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value 26 | # as appropriate. These values are used to generate the final SDL2_LIBRARY 27 | # variable, but when these values are unset, SDL2_LIBRARY does not get created. 28 | # 29 | # 30 | # $SDL2 is an environment variable that would 31 | # correspond to the ./configure --prefix=$SDL2 32 | # used in building SDL2. 33 | # l.e.galup 9-20-02 34 | # 35 | # Modified by Eric Wing. 36 | # Added code to assist with automated building by using environmental variables 37 | # and providing a more controlled/consistent search behavior. 38 | # Added new modifications to recognize OS X frameworks and 39 | # additional Unix paths (FreeBSD, etc). 40 | # Also corrected the header search path to follow "proper" SDL2 guidelines. 41 | # Added a search for SDL2main which is needed by some platforms. 42 | # Added a search for threads which is needed by some platforms. 43 | # Added needed compile switches for MinGW. 44 | # 45 | # On OSX, this will prefer the Framework version (if found) over others. 46 | # People will have to manually change the cache values of 47 | # SDL2_LIBRARY to override this selection or set the CMake environment 48 | # CMAKE_INCLUDE_PATH to modify the search paths. 49 | # 50 | # Note that the header path has changed from SDL2/SDL.h to just SDL.h 51 | # This needed to change because "proper" SDL2 convention 52 | # is #include "SDL.h", not . This is done for portability 53 | # reasons because not all systems place things in SDL2/ (see FreeBSD). 54 | # 55 | # Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake 56 | # module with the minor edit of changing "SDL" to "SDL2" where necessary. This 57 | # was not created for redistribution, and exists temporarily pending official 58 | # SDL2 CMake modules. 59 | # 60 | # Note that on windows this will only search for the 32bit libraries, to search 61 | # for 64bit change x86/i686-w64 to x64/x86_64-w64 62 | 63 | #============================================================================= 64 | # Copyright 2003-2009 Kitware, Inc. 65 | # 66 | # CMake - Cross Platform Makefile Generator 67 | # Copyright 2000-2014 Kitware, Inc. 68 | # Copyright 2000-2011 Insight Software Consortium 69 | # All rights reserved. 70 | # 71 | # Redistribution and use in source and binary forms, with or without 72 | # modification, are permitted provided that the following conditions 73 | # are met: 74 | # 75 | # * Redistributions of source code must retain the above copyright 76 | # notice, this list of conditions and the following disclaimer. 77 | # 78 | # * Redistributions in binary form must reproduce the above copyright 79 | # notice, this list of conditions and the following disclaimer in the 80 | # documentation and/or other materials provided with the distribution. 81 | # 82 | # * Neither the names of Kitware, Inc., the Insight Software Consortium, 83 | # nor the names of their contributors may be used to endorse or promote 84 | # products derived from this software without specific prior written 85 | # permission. 86 | # 87 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 88 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 89 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 90 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 91 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 92 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 93 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 94 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 95 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 96 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 97 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 98 | # 99 | # This software is distributed WITHOUT ANY WARRANTY; without even the 100 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 101 | # See the License for more information. 102 | #============================================================================= 103 | # (To distribute this file outside of CMake, substitute the full 104 | # License text for the above reference.) 105 | 106 | FIND_PATH(SDL2_INCLUDE_DIR SDL.h 107 | HINTS 108 | ${SDL2} 109 | $ENV{SDL2} 110 | PATH_SUFFIXES include/SDL2 include SDL2 111 | i686-w64-mingw32/include/SDL2 112 | x86_64-w64-mingw32/include/SDL2 113 | PATHS 114 | ~/Library/Frameworks 115 | /Library/Frameworks 116 | /usr/local/include/SDL2 117 | /usr/include/SDL2 118 | /sw # Fink 119 | /opt/local # DarwinPorts 120 | /opt/csw # Blastwave 121 | /opt 122 | ) 123 | 124 | # Lookup the 64 bit libs on x64 125 | IF(CMAKE_SIZEOF_VOID_P EQUAL 8) 126 | FIND_LIBRARY(SDL2_LIBRARY_TEMP SDL2 127 | HINTS 128 | ${SDL2} 129 | $ENV{SDL2} 130 | PATH_SUFFIXES lib64 lib 131 | lib/x64 132 | x86_64-w64-mingw32/lib 133 | PATHS 134 | /sw 135 | /opt/local 136 | /opt/csw 137 | /opt 138 | ) 139 | # On 32bit build find the 32bit libs 140 | ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) 141 | FIND_LIBRARY(SDL2_LIBRARY_TEMP SDL2 142 | HINTS 143 | ${SDL2} 144 | $ENV{SDL2} 145 | PATH_SUFFIXES lib 146 | lib/x86 147 | i686-w64-mingw32/lib 148 | PATHS 149 | /sw 150 | /opt/local 151 | /opt/csw 152 | /opt 153 | ) 154 | ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) 155 | 156 | IF(NOT SDL2_BUILDING_LIBRARY) 157 | IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 158 | # Non-OS X framework versions expect you to also dynamically link to 159 | # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms 160 | # seem to provide SDL2main for compatibility even though they don't 161 | # necessarily need it. 162 | # Lookup the 64 bit libs on x64 163 | IF(CMAKE_SIZEOF_VOID_P EQUAL 8) 164 | FIND_LIBRARY(SDL2MAIN_LIBRARY 165 | NAMES SDL2main 166 | HINTS 167 | ${SDL2} 168 | $ENV{SDL2} 169 | PATH_SUFFIXES lib64 lib 170 | lib/x64 171 | x86_64-w64-mingw32/lib 172 | PATHS 173 | /sw 174 | /opt/local 175 | /opt/csw 176 | /opt 177 | ) 178 | # On 32bit build find the 32bit libs 179 | ELSE(CMAKE_SIZEOF_VOID_P EQUAL 8) 180 | FIND_LIBRARY(SDL2MAIN_LIBRARY 181 | NAMES SDL2main 182 | HINTS 183 | ${SDL2} 184 | $ENV{SDL2} 185 | PATH_SUFFIXES lib 186 | lib/x86 187 | i686-w64-mingw32/lib 188 | PATHS 189 | /sw 190 | /opt/local 191 | /opt/csw 192 | /opt 193 | ) 194 | ENDIF(CMAKE_SIZEOF_VOID_P EQUAL 8) 195 | ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") 196 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 197 | 198 | # SDL2 may require threads on your system. 199 | # The Apple build may not need an explicit flag because one of the 200 | # frameworks may already provide it. 201 | # But for non-OSX systems, I will use the CMake Threads package. 202 | IF(NOT APPLE) 203 | FIND_PACKAGE(Threads) 204 | ENDIF(NOT APPLE) 205 | 206 | # MinGW needs an additional library, mwindows 207 | # It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows 208 | # (Actually on second look, I think it only needs one of the m* libraries.) 209 | IF(MINGW) 210 | SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") 211 | ENDIF(MINGW) 212 | 213 | SET(SDL2_FOUND "NO") 214 | IF(SDL2_LIBRARY_TEMP) 215 | # For SDL2main 216 | IF(NOT SDL2_BUILDING_LIBRARY) 217 | IF(SDL2MAIN_LIBRARY) 218 | SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) 219 | ENDIF(SDL2MAIN_LIBRARY) 220 | ENDIF(NOT SDL2_BUILDING_LIBRARY) 221 | 222 | # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. 223 | # CMake doesn't display the -framework Cocoa string in the UI even 224 | # though it actually is there if I modify a pre-used variable. 225 | # I think it has something to do with the CACHE STRING. 226 | # So I use a temporary variable until the end so I can set the 227 | # "real" variable in one-shot. 228 | IF(APPLE) 229 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") 230 | ENDIF(APPLE) 231 | 232 | # For threads, as mentioned Apple doesn't need this. 233 | # In fact, there seems to be a problem if I used the Threads package 234 | # and try using this line, so I'm just skipping it entirely for OS X. 235 | IF(NOT APPLE) 236 | SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) 237 | ENDIF(NOT APPLE) 238 | 239 | # For MinGW library 240 | IF(MINGW) 241 | SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) 242 | ENDIF(MINGW) 243 | 244 | # Set the final string here so the GUI reflects the final state. 245 | SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") 246 | # Set the temp variable to INTERNAL so it is not seen in the CMake GUI 247 | SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") 248 | 249 | SET(SDL2_FOUND "YES") 250 | ENDIF(SDL2_LIBRARY_TEMP) 251 | 252 | INCLUDE(FindPackageHandleStandardArgs) 253 | 254 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) 255 | 256 | 257 | -------------------------------------------------------------------------------- /cmake/FindVulkan.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindVulkan 6 | # ---------- 7 | # 8 | # Try to find Vulkan 9 | # 10 | # IMPORTED Targets 11 | # ^^^^^^^^^^^^^^^^ 12 | # 13 | # This module defines :prop_tgt:`IMPORTED` target ``Vulkan::Vulkan``, if 14 | # Vulkan has been found. 15 | # 16 | # Result Variables 17 | # ^^^^^^^^^^^^^^^^ 18 | # 19 | # This module defines the following variables:: 20 | # 21 | # Vulkan_FOUND - True if Vulkan was found 22 | # Vulkan_INCLUDE_DIRS - include directories for Vulkan 23 | # Vulkan_LIBRARIES - link against this library to use Vulkan 24 | # 25 | # The module will also define two cache variables:: 26 | # 27 | # Vulkan_INCLUDE_DIR - the Vulkan include directory 28 | # Vulkan_LIBRARY - the path to the Vulkan library 29 | # 30 | 31 | if(WIN32) 32 | find_path(Vulkan_INCLUDE_DIR 33 | NAMES vulkan/vulkan.h 34 | PATHS 35 | ${VULKAN_SDK}/Include 36 | $ENV{VULKAN_SDK}/Include 37 | ) 38 | 39 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 40 | find_library(Vulkan_LIBRARY 41 | NAMES vulkan-1 42 | PATHS 43 | ${VULKAN_SDK}/Lib 44 | ${VULKAN_SDK}/Bin 45 | $ENV{VULKAN_SDK}/Lib 46 | $ENV{VULKAN_SDK}/Bin 47 | ) 48 | find_program(SPIRV_COMPILER 49 | NAMES glslc 50 | PATHS 51 | ${VULKAN_SDK}/Bin 52 | $ENV{VULKAN_SDK}/Bin 53 | ) 54 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) 55 | find_library(Vulkan_LIBRARY 56 | NAMES vulkan-1 57 | PATHS 58 | ${VULKAN_SDK}/Lib32 59 | ${VULKAN_SDK}/Bin32 60 | $ENV{VULKAN_SDK}/Lib32 61 | $ENV{VULKAN_SDK}/Bin32 62 | NO_SYSTEM_ENVIRONMENT_PATH 63 | ) 64 | find_program(SPIRV_COMPILER 65 | NAMES glslc 66 | PATHS 67 | ${VULKAN_SDK}/Bin32 68 | $ENV{VULKAN_SDK}/Bin32 69 | ) 70 | endif() 71 | else() 72 | find_path(Vulkan_INCLUDE_DIR 73 | NAMES vulkan/vulkan.h 74 | PATHS 75 | ${VULKAN_SDK}/include 76 | $ENV{VULKAN_SDK}/include 77 | ) 78 | find_library(Vulkan_LIBRARY 79 | NAMES vulkan 80 | PATHS 81 | ${VULKAN_SDK}/lib 82 | $ENV{VULKAN_SDK}/lib 83 | ) 84 | find_program(SPIRV_COMPILER 85 | NAMES glslc 86 | PATHS 87 | ${VULKAN_SDK}/bin 88 | $ENV{VULKAN_SDK}/bin 89 | ) 90 | endif() 91 | 92 | # Note that the include paths and defines should not have 93 | # the -I or -D prefix, respectively 94 | function(add_spirv_embed_library) 95 | set(options INCLUDE_DIRECTORIES COMPILE_DEFINITIONS COMPILE_OPTIONS) 96 | cmake_parse_arguments(PARSE_ARGV 1 SPIRV "" "" "${options}") 97 | 98 | set(GLSL_INCLUDE_DIRS "") 99 | foreach (inc ${SPIRV_INCLUDE_DIRECTORIES}) 100 | file(TO_NATIVE_PATH "${inc}" native_path) 101 | list(APPEND GLSL_INCLUDE_DIRS "-I${native_path}") 102 | endforeach() 103 | 104 | set(GLSL_COMPILE_DEFNS "") 105 | foreach (def ${SPIRV_COMPILE_DEFINITIONS}) 106 | list(APPEND GLSL_COMPILE_DEFNS "-D${def}") 107 | endforeach() 108 | 109 | # Compile each GLSL file to embedded SPIRV bytecode 110 | set(SPIRV_LIB ${ARGV0}) 111 | set(SPIRV_BINARIES "") 112 | foreach (shader ${SPIRV_UNPARSED_ARGUMENTS}) 113 | get_filename_component(FNAME ${shader} NAME_WE) 114 | set(SPV_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FNAME}.spv) 115 | list(APPEND SPIRV_BINARIES ${SPV_OUTPUT}) 116 | 117 | add_custom_command(OUTPUT ${SPV_OUTPUT} 118 | COMMAND ${SPIRV_COMPILER} ${CMAKE_CURRENT_LIST_DIR}/${shader} 119 | ${GLSL_INCLUDE_DIRECTORIES} ${GLSL_COMPILE_DEFNS} 120 | --target-env=vulkan1.1 -mfmt=c -o ${SPV_OUTPUT} 121 | DEPENDS ${CMAKE_CURRENT_LIST_DIR}/${shader} 122 | COMMENT "Compiling ${CMAKE_CURRENT_LIST_DIR}/${shader} to ${SPV_OUTPUT}") 123 | endforeach() 124 | 125 | set(SPIRV_EMBED_FILE "${CMAKE_CURRENT_BINARY_DIR}/${SPIRV_LIB}_embedded_spv.h") 126 | add_custom_command(OUTPUT ${SPIRV_EMBED_FILE} 127 | COMMAND ${CMAKE_COMMAND} 128 | -DSPIRV_EMBED_FILE=${SPIRV_EMBED_FILE} -DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR} 129 | -P ${CMAKE_MODULE_PATH}/SPIRV2C.cmake 130 | DEPENDS ${SPIRV_BINARIES} 131 | COMMENT "Embedding SPIRV bytecode into ${SPIRV_EMBED_FILE}") 132 | 133 | set(SPIRV_CMAKE_CUSTOM_WRAPPER ${SPIRV_LIB}_custom_target) 134 | add_custom_target(${SPIRV_CMAKE_CUSTOM_WRAPPER} ALL DEPENDS ${SPIRV_EMBED_FILE}) 135 | 136 | add_library(${SPIRV_LIB} INTERFACE) 137 | add_dependencies(${SPIRV_LIB} ${SPIRV_CMAKE_CUSTOM_WRAPPER}) 138 | target_include_directories(${SPIRV_LIB} INTERFACE 139 | $) 140 | endfunction() 141 | 142 | set(Vulkan_LIBRARIES ${Vulkan_LIBRARY}) 143 | set(Vulkan_INCLUDE_DIRS ${Vulkan_INCLUDE_DIR}) 144 | 145 | include(FindPackageHandleStandardArgs) 146 | find_package_handle_standard_args(Vulkan 147 | DEFAULT_MSG 148 | Vulkan_LIBRARY Vulkan_INCLUDE_DIR) 149 | 150 | mark_as_advanced(Vulkan_INCLUDE_DIR Vulkan_LIBRARY) 151 | 152 | if(Vulkan_FOUND AND NOT TARGET Vulkan::Vulkan) 153 | add_library(Vulkan::Vulkan UNKNOWN IMPORTED) 154 | set_target_properties(Vulkan::Vulkan PROPERTIES 155 | IMPORTED_LOCATION "${Vulkan_LIBRARIES}" 156 | INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") 157 | endif() 158 | 159 | -------------------------------------------------------------------------------- /cmake/SPIRV2C.cmake: -------------------------------------------------------------------------------- 1 | function(spirv2c) 2 | file(GLOB SPIRV_BINARIES *.spv) 3 | 4 | file(WRITE ${SPIRV_EMBED_FILE} "#pragma once\n") 5 | foreach (spv ${SPIRV_BINARIES}) 6 | get_filename_component(FNAME ${spv} NAME_WE) 7 | file(READ ${spv} SPV_CONTENT) 8 | file(APPEND ${SPIRV_EMBED_FILE} "const uint32_t ${FNAME}_spv[] =\n${SPV_CONTENT};\n") 9 | endforeach() 10 | endfunction() 11 | 12 | spirv2c(${SPIRV_EMBED_FILE} ${OUTPUT_DIR}) 13 | 14 | -------------------------------------------------------------------------------- /frag.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec3 frag_color; 5 | 6 | layout(location = 0) out vec4 color; 7 | 8 | void main() { 9 | color = vec4(frag_color, 1.0); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "spirv_shaders_embedded_spv.h" 11 | 12 | #define CHECK_VULKAN(FN) \ 13 | { \ 14 | VkResult r = FN; \ 15 | if (r != VK_SUCCESS) {\ 16 | std::cout << #FN << " failed\n" << std::flush; \ 17 | throw std::runtime_error(#FN " failed!"); \ 18 | } \ 19 | } 20 | 21 | int win_width = 1280; 22 | int win_height = 720; 23 | 24 | int main(int argc, const char **argv) { 25 | if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { 26 | std::cerr << "Failed to init SDL: " << SDL_GetError() << "\n"; 27 | return -1; 28 | } 29 | 30 | SDL_Window* window = SDL_CreateWindow("SDL2 + Vulkan", 31 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, win_width, win_height, 0); 32 | 33 | { 34 | uint32_t extension_count = 0; 35 | vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); 36 | std::cout << "num extensions: " << extension_count << "\n"; 37 | std::vector extensions(extension_count, VkExtensionProperties{}); 38 | vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extensions.data()); 39 | std::cout << "Available extensions:\n"; 40 | for (const auto& e : extensions) { 41 | std::cout << e.extensionName << "\n"; 42 | } 43 | } 44 | 45 | const std::array validation_layers = { 46 | "VK_LAYER_KHRONOS_validation" 47 | }; 48 | 49 | // Make the Vulkan Instance 50 | VkInstance vk_instance = VK_NULL_HANDLE; 51 | { 52 | VkApplicationInfo app_info = {}; 53 | app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 54 | app_info.pApplicationName = "SDL2 + Vulkan"; 55 | app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 56 | app_info.pEngineName = "None"; 57 | app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); 58 | app_info.apiVersion = VK_API_VERSION_1_1; 59 | 60 | const std::array extension_names = { 61 | VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME, 62 | }; 63 | 64 | VkInstanceCreateInfo create_info = {}; 65 | create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 66 | create_info.pApplicationInfo = &app_info; 67 | create_info.enabledExtensionCount = extension_names.size(); 68 | create_info.ppEnabledExtensionNames = extension_names.data(); 69 | create_info.enabledLayerCount = validation_layers.size(); 70 | create_info.ppEnabledLayerNames = validation_layers.data(); 71 | 72 | CHECK_VULKAN(vkCreateInstance(&create_info, nullptr, &vk_instance)); 73 | } 74 | 75 | VkSurfaceKHR vk_surface = VK_NULL_HANDLE; 76 | { 77 | SDL_SysWMinfo wm_info; 78 | SDL_VERSION(&wm_info.version); 79 | SDL_GetWindowWMInfo(window, &wm_info); 80 | 81 | VkWin32SurfaceCreateInfoKHR create_info = {}; 82 | create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; 83 | create_info.hwnd = wm_info.info.win.window; 84 | create_info.hinstance = wm_info.info.win.hinstance; 85 | CHECK_VULKAN(vkCreateWin32SurfaceKHR(vk_instance, &create_info, nullptr, &vk_surface)); 86 | } 87 | 88 | VkPhysicalDevice vk_physical_device = VK_NULL_HANDLE; 89 | { 90 | uint32_t device_count = 0; 91 | vkEnumeratePhysicalDevices(vk_instance, &device_count, nullptr); 92 | std::cout << "Found " << device_count << " devices\n"; 93 | std::vector devices(device_count, VkPhysicalDevice{}); 94 | vkEnumeratePhysicalDevices(vk_instance, &device_count, devices.data()); 95 | 96 | const bool has_discrete_gpu = std::find_if(devices.begin(), devices.end(), 97 | [](const VkPhysicalDevice& d) { 98 | VkPhysicalDeviceProperties properties; 99 | vkGetPhysicalDeviceProperties(d, &properties); 100 | return properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; 101 | }) != devices.end(); 102 | 103 | for (const auto &d : devices) { 104 | VkPhysicalDeviceProperties properties; 105 | VkPhysicalDeviceFeatures features; 106 | vkGetPhysicalDeviceProperties(d, &properties); 107 | vkGetPhysicalDeviceFeatures(d, &features); 108 | std::cout << properties.deviceName << "\n"; 109 | 110 | // Check for RTX support 111 | uint32_t extension_count = 0; 112 | vkEnumerateDeviceExtensionProperties(d, nullptr, &extension_count, nullptr); 113 | std::cout << "num extensions: " << extension_count << "\n"; 114 | std::vector extensions(extension_count, VkExtensionProperties{}); 115 | vkEnumerateDeviceExtensionProperties(d, nullptr, &extension_count, extensions.data()); 116 | std::cout << "Device available extensions:\n"; 117 | for (const auto& e : extensions) { 118 | std::cout << e.extensionName << "\n"; 119 | } 120 | 121 | if (has_discrete_gpu && properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { 122 | vk_physical_device = d; 123 | break; 124 | } else if (!has_discrete_gpu && properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { 125 | vk_physical_device = d; 126 | break; 127 | } 128 | } 129 | } 130 | 131 | VkDevice vk_device = VK_NULL_HANDLE; 132 | VkQueue vk_queue = VK_NULL_HANDLE; 133 | uint32_t graphics_queue_index = -1; 134 | { 135 | uint32_t num_queue_families = 0; 136 | vkGetPhysicalDeviceQueueFamilyProperties(vk_physical_device, &num_queue_families, nullptr); 137 | std::vector family_props(num_queue_families, VkQueueFamilyProperties{}); 138 | vkGetPhysicalDeviceQueueFamilyProperties(vk_physical_device, &num_queue_families, family_props.data()); 139 | for (uint32_t i = 0; i < num_queue_families; ++i) { 140 | // We want present and graphics on the same queue (kind of assume this will be supported on any discrete GPU) 141 | VkBool32 present_support = false; 142 | vkGetPhysicalDeviceSurfaceSupportKHR(vk_physical_device, i, vk_surface, &present_support); 143 | if (present_support && (family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) { 144 | graphics_queue_index = i; 145 | } 146 | } 147 | std::cout << "Graphics queue is " << graphics_queue_index << "\n"; 148 | const float queue_priority = 1.f; 149 | 150 | VkDeviceQueueCreateInfo queue_create_info = {}; 151 | queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 152 | queue_create_info.queueFamilyIndex = graphics_queue_index; 153 | queue_create_info.queueCount = 1; 154 | queue_create_info.pQueuePriorities = &queue_priority; 155 | 156 | VkPhysicalDeviceFeatures device_features = {}; 157 | // TODO: RTX feature 158 | 159 | const std::array device_extensions = { 160 | VK_KHR_SWAPCHAIN_EXTENSION_NAME 161 | }; 162 | 163 | VkDeviceCreateInfo create_info = {}; 164 | create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 165 | create_info.queueCreateInfoCount = 1; 166 | create_info.pQueueCreateInfos = &queue_create_info; 167 | create_info.enabledLayerCount = validation_layers.size(); 168 | create_info.ppEnabledLayerNames = validation_layers.data(); 169 | create_info.enabledExtensionCount = device_extensions.size(); 170 | create_info.ppEnabledExtensionNames = device_extensions.data(); 171 | create_info.pEnabledFeatures = &device_features; 172 | CHECK_VULKAN(vkCreateDevice(vk_physical_device, &create_info, nullptr, &vk_device)); 173 | 174 | vkGetDeviceQueue(vk_device, graphics_queue_index, 0, &vk_queue); 175 | } 176 | 177 | // Setup swapchain, assume a real GPU so don't bother querying the capabilities, just get what we want 178 | VkExtent2D swapchain_extent = {}; 179 | swapchain_extent.width = win_width; 180 | swapchain_extent.height = win_height; 181 | const VkFormat swapchain_img_format = VK_FORMAT_B8G8R8A8_UNORM; 182 | 183 | VkSwapchainKHR vk_swapchain = VK_NULL_HANDLE; 184 | std::vector swapchain_images; 185 | std::vector swapchain_image_views; 186 | { 187 | VkSwapchainCreateInfoKHR create_info = {}; 188 | create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; 189 | create_info.surface = vk_surface; 190 | create_info.minImageCount = 2; 191 | create_info.imageFormat = swapchain_img_format; 192 | create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; 193 | create_info.imageExtent = swapchain_extent; 194 | create_info.imageArrayLayers = 1; 195 | create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; 196 | // We only have 1 queue 197 | create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; 198 | create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; 199 | create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; 200 | create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; 201 | create_info.clipped = true; 202 | create_info.oldSwapchain = VK_NULL_HANDLE; 203 | CHECK_VULKAN(vkCreateSwapchainKHR(vk_device, &create_info, nullptr, &vk_swapchain)); 204 | 205 | // Get the swap chain images 206 | uint32_t num_swapchain_imgs = 0; 207 | vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &num_swapchain_imgs, nullptr); 208 | swapchain_images.resize(num_swapchain_imgs); 209 | vkGetSwapchainImagesKHR(vk_device, vk_swapchain, &num_swapchain_imgs, swapchain_images.data()); 210 | 211 | for (const auto &img : swapchain_images) { 212 | VkImageViewCreateInfo view_create_info = {}; 213 | view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 214 | view_create_info.image = img; 215 | view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; 216 | view_create_info.format = swapchain_img_format; 217 | 218 | view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; 219 | view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; 220 | view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; 221 | view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 222 | 223 | view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 224 | view_create_info.subresourceRange.baseMipLevel = 0; 225 | view_create_info.subresourceRange.levelCount = 1; 226 | view_create_info.subresourceRange.baseArrayLayer = 0; 227 | view_create_info.subresourceRange.layerCount = 1; 228 | 229 | VkImageView img_view; 230 | CHECK_VULKAN(vkCreateImageView(vk_device, &view_create_info, nullptr, &img_view)); 231 | swapchain_image_views.push_back(img_view); 232 | } 233 | } 234 | 235 | // Build the pipeline 236 | VkPipelineLayout vk_pipeline_layout; 237 | VkRenderPass vk_render_pass; 238 | VkPipeline vk_pipeline; 239 | { 240 | VkShaderModule vertex_shader_module = VK_NULL_HANDLE; 241 | 242 | VkShaderModuleCreateInfo create_info = {}; 243 | create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 244 | create_info.codeSize = sizeof(vert_spv); 245 | create_info.pCode = vert_spv; 246 | CHECK_VULKAN(vkCreateShaderModule(vk_device, &create_info, nullptr, &vertex_shader_module)); 247 | 248 | VkPipelineShaderStageCreateInfo vertex_stage = {}; 249 | vertex_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 250 | vertex_stage.stage = VK_SHADER_STAGE_VERTEX_BIT; 251 | vertex_stage.module = vertex_shader_module; 252 | vertex_stage.pName = "main"; 253 | 254 | VkShaderModule fragment_shader_module = VK_NULL_HANDLE; 255 | create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 256 | create_info.codeSize = sizeof(frag_spv); 257 | create_info.pCode = frag_spv; 258 | CHECK_VULKAN(vkCreateShaderModule(vk_device, &create_info, nullptr, &fragment_shader_module)); 259 | 260 | VkPipelineShaderStageCreateInfo fragment_stage = {}; 261 | fragment_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 262 | fragment_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT; 263 | fragment_stage.module = fragment_shader_module; 264 | fragment_stage.pName = "main"; 265 | 266 | std::array shader_stages = { vertex_stage, fragment_stage }; 267 | 268 | // Vertex data hard-coded in vertex shader 269 | VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; 270 | vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 271 | vertex_input_info.vertexBindingDescriptionCount = 0; 272 | vertex_input_info.vertexAttributeDescriptionCount = 0; 273 | 274 | // Primitive type 275 | VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; 276 | input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 277 | input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 278 | input_assembly.primitiveRestartEnable = VK_FALSE; 279 | 280 | // Viewport config 281 | VkViewport viewport = {}; 282 | viewport.x = 0.0f; 283 | viewport.y = 0.0f; 284 | viewport.width = win_width; 285 | viewport.height = win_height; 286 | viewport.minDepth = 0.0f; 287 | viewport.maxDepth = 1.0f; 288 | 289 | // Scissor rect config 290 | VkRect2D scissor = {}; 291 | scissor.offset.x = 0; 292 | scissor.offset.y = 0; 293 | scissor.extent = swapchain_extent; 294 | 295 | VkPipelineViewportStateCreateInfo viewport_state_info = {}; 296 | viewport_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 297 | viewport_state_info.viewportCount = 1; 298 | viewport_state_info.pViewports = &viewport; 299 | viewport_state_info.scissorCount = 1; 300 | viewport_state_info.pScissors = &scissor; 301 | 302 | VkPipelineRasterizationStateCreateInfo rasterizer_info = {}; 303 | rasterizer_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 304 | rasterizer_info.depthClampEnable = VK_FALSE; 305 | rasterizer_info.rasterizerDiscardEnable = VK_FALSE; 306 | rasterizer_info.polygonMode = VK_POLYGON_MODE_FILL; 307 | rasterizer_info.lineWidth = 1.f; 308 | rasterizer_info.cullMode = VK_CULL_MODE_BACK_BIT; 309 | rasterizer_info.frontFace = VK_FRONT_FACE_CLOCKWISE; 310 | rasterizer_info.depthBiasEnable = VK_FALSE; 311 | 312 | VkPipelineMultisampleStateCreateInfo multisampling = {}; 313 | multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 314 | multisampling.sampleShadingEnable = VK_FALSE; 315 | multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 316 | 317 | VkPipelineColorBlendAttachmentState blend_mode = {}; 318 | blend_mode.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; 319 | blend_mode.blendEnable = VK_FALSE; 320 | 321 | VkPipelineColorBlendStateCreateInfo blend_info = {}; 322 | blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; 323 | blend_info.logicOpEnable = VK_FALSE; 324 | blend_info.attachmentCount = 1; 325 | blend_info.pAttachments = &blend_mode; 326 | 327 | VkPipelineLayoutCreateInfo pipeline_info = {}; 328 | pipeline_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 329 | CHECK_VULKAN(vkCreatePipelineLayout(vk_device, &pipeline_info, nullptr, &vk_pipeline_layout)); 330 | 331 | VkAttachmentDescription color_attachment = {}; 332 | color_attachment.format = swapchain_img_format; 333 | color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; 334 | color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 335 | color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 336 | color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 337 | color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 338 | color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 339 | color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 340 | 341 | VkAttachmentReference color_attachment_ref = {}; 342 | color_attachment_ref.attachment = 0; 343 | color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 344 | 345 | VkSubpassDescription subpass = {}; 346 | subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 347 | subpass.colorAttachmentCount = 1; 348 | subpass.pColorAttachments = &color_attachment_ref; 349 | 350 | VkRenderPassCreateInfo render_pass_info = {}; 351 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 352 | render_pass_info.attachmentCount = 1; 353 | render_pass_info.pAttachments = &color_attachment; 354 | render_pass_info.subpassCount = 1; 355 | render_pass_info.pSubpasses = &subpass; 356 | CHECK_VULKAN(vkCreateRenderPass(vk_device, &render_pass_info, nullptr, &vk_render_pass)); 357 | 358 | VkGraphicsPipelineCreateInfo graphics_pipeline_info = {}; 359 | graphics_pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 360 | graphics_pipeline_info.stageCount = 2; 361 | graphics_pipeline_info.pStages = shader_stages.data(); 362 | graphics_pipeline_info.pVertexInputState = &vertex_input_info; 363 | graphics_pipeline_info.pInputAssemblyState = &input_assembly; 364 | graphics_pipeline_info.pViewportState = &viewport_state_info; 365 | graphics_pipeline_info.pRasterizationState = &rasterizer_info; 366 | graphics_pipeline_info.pMultisampleState = &multisampling; 367 | graphics_pipeline_info.pColorBlendState = &blend_info; 368 | graphics_pipeline_info.layout = vk_pipeline_layout; 369 | graphics_pipeline_info.renderPass = vk_render_pass; 370 | graphics_pipeline_info.subpass = 0; 371 | CHECK_VULKAN(vkCreateGraphicsPipelines(vk_device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr, &vk_pipeline)); 372 | 373 | vkDestroyShaderModule(vk_device, vertex_shader_module, nullptr); 374 | vkDestroyShaderModule(vk_device, fragment_shader_module, nullptr); 375 | } 376 | 377 | // Setup framebuffers 378 | std::vector framebuffers; 379 | for (const auto &v : swapchain_image_views) { 380 | std::array attachments = { v }; 381 | VkFramebufferCreateInfo create_info = {}; 382 | create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 383 | create_info.renderPass = vk_render_pass; 384 | create_info.attachmentCount = 1; 385 | create_info.pAttachments = attachments.data(); 386 | create_info.width = win_width; 387 | create_info.height = win_height; 388 | create_info.layers = 1; 389 | VkFramebuffer fb = VK_NULL_HANDLE; 390 | CHECK_VULKAN(vkCreateFramebuffer(vk_device, &create_info, nullptr, &fb)); 391 | framebuffers.push_back(fb); 392 | } 393 | 394 | // Setup the command pool 395 | VkCommandPool vk_command_pool; 396 | { 397 | VkCommandPoolCreateInfo create_info = {}; 398 | create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 399 | create_info.queueFamilyIndex = graphics_queue_index; 400 | CHECK_VULKAN(vkCreateCommandPool(vk_device, &create_info, nullptr, &vk_command_pool)); 401 | } 402 | 403 | std::vector command_buffers(framebuffers.size(), VkCommandBuffer{}); 404 | { 405 | VkCommandBufferAllocateInfo info = {}; 406 | info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 407 | info.commandPool = vk_command_pool; 408 | info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 409 | info.commandBufferCount = command_buffers.size(); 410 | CHECK_VULKAN(vkAllocateCommandBuffers(vk_device, &info, command_buffers.data())); 411 | } 412 | 413 | // Now record the rendering commands (TODO: Could also do this pre-recording in the DXR backend 414 | // of rtobj. Will there be much perf. difference?) 415 | for (size_t i = 0; i < command_buffers.size(); ++i) { 416 | auto& cmd_buf = command_buffers[i]; 417 | 418 | VkCommandBufferBeginInfo begin_info = {}; 419 | begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 420 | CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf, &begin_info)); 421 | 422 | VkRenderPassBeginInfo render_pass_info = {}; 423 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 424 | render_pass_info.renderPass = vk_render_pass; 425 | render_pass_info.framebuffer = framebuffers[i]; 426 | render_pass_info.renderArea.offset.x = 0; 427 | render_pass_info.renderArea.offset.y = 0; 428 | render_pass_info.renderArea.extent = swapchain_extent; 429 | 430 | VkClearValue clear_color = { 0.f, 0.f, 0.f, 1.f }; 431 | render_pass_info.clearValueCount = 1; 432 | render_pass_info.pClearValues = &clear_color; 433 | 434 | vkCmdBeginRenderPass(cmd_buf, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); 435 | 436 | vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, vk_pipeline); 437 | 438 | // Draw our "triangle" embedded in the shader 439 | vkCmdDraw(cmd_buf, 3, 1, 0, 0); 440 | 441 | vkCmdEndRenderPass(cmd_buf); 442 | 443 | CHECK_VULKAN(vkEndCommandBuffer(cmd_buf)); 444 | } 445 | 446 | VkSemaphore img_avail_semaphore = VK_NULL_HANDLE; 447 | VkSemaphore render_finished_semaphore = VK_NULL_HANDLE; 448 | { 449 | VkSemaphoreCreateInfo info = {}; 450 | info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; 451 | 452 | CHECK_VULKAN(vkCreateSemaphore(vk_device, &info, nullptr, &img_avail_semaphore)); 453 | CHECK_VULKAN(vkCreateSemaphore(vk_device, &info, nullptr, &render_finished_semaphore)); 454 | } 455 | 456 | // We use a fence to wait for the rendering work to finish 457 | VkFence vk_fence = VK_NULL_HANDLE; 458 | { 459 | VkFenceCreateInfo info = {}; 460 | info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; 461 | CHECK_VULKAN(vkCreateFence(vk_device, &info, nullptr, &vk_fence)); 462 | } 463 | 464 | std::cout << "Running loop\n"; 465 | bool done = false; 466 | while (!done) { 467 | SDL_Event event; 468 | while (SDL_PollEvent(&event)) { 469 | if (event.type == SDL_QUIT) { 470 | done = true; 471 | } 472 | if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) { 473 | done = true; 474 | } 475 | if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE 476 | && event.window.windowID == SDL_GetWindowID(window)) { 477 | done = true; 478 | } 479 | } 480 | 481 | // Get an image from the swap chain 482 | uint32_t img_index = 0; 483 | CHECK_VULKAN(vkAcquireNextImageKHR(vk_device, vk_swapchain, std::numeric_limits::max(), 484 | img_avail_semaphore, VK_NULL_HANDLE, &img_index)); 485 | 486 | // We need to wait for the image before we can run the commands to draw to it, and signal 487 | // the render finished one when we're done 488 | const std::array wait_semaphores = { img_avail_semaphore }; 489 | const std::array signal_semaphores = { render_finished_semaphore }; 490 | const std::array wait_stages = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT }; 491 | 492 | CHECK_VULKAN(vkResetFences(vk_device, 1, &vk_fence)); 493 | 494 | VkSubmitInfo submit_info = {}; 495 | submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 496 | submit_info.waitSemaphoreCount = wait_semaphores.size(); 497 | submit_info.pWaitSemaphores = wait_semaphores.data(); 498 | submit_info.pWaitDstStageMask = wait_stages.data(); 499 | submit_info.commandBufferCount = 1; 500 | submit_info.pCommandBuffers = &command_buffers[img_index]; 501 | submit_info.signalSemaphoreCount = signal_semaphores.size(); 502 | submit_info.pSignalSemaphores = signal_semaphores.data(); 503 | CHECK_VULKAN(vkQueueSubmit(vk_queue, 1, &submit_info, vk_fence)); 504 | 505 | // Finally, present the updated image in the swap chain 506 | std::array present_chain = { vk_swapchain }; 507 | VkPresentInfoKHR present_info = {}; 508 | present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; 509 | present_info.waitSemaphoreCount = signal_semaphores.size(); 510 | present_info.pWaitSemaphores = signal_semaphores.data(); 511 | present_info.swapchainCount = present_chain.size(); 512 | present_info.pSwapchains = present_chain.data(); 513 | present_info.pImageIndices = &img_index; 514 | CHECK_VULKAN(vkQueuePresentKHR(vk_queue, &present_info)); 515 | 516 | // Wait for the frame to finish 517 | CHECK_VULKAN(vkWaitForFences(vk_device, 1, &vk_fence, true, std::numeric_limits::max())); 518 | } 519 | 520 | vkDestroySemaphore(vk_device, img_avail_semaphore, nullptr); 521 | vkDestroySemaphore(vk_device, render_finished_semaphore, nullptr); 522 | vkDestroyFence(vk_device, vk_fence, nullptr); 523 | vkDestroyCommandPool(vk_device, vk_command_pool, nullptr); 524 | vkDestroySwapchainKHR(vk_device, vk_swapchain, nullptr); 525 | for (auto &fb : framebuffers) { 526 | vkDestroyFramebuffer(vk_device, fb, nullptr); 527 | } 528 | vkDestroyPipeline(vk_device, vk_pipeline, nullptr); 529 | vkDestroyRenderPass(vk_device, vk_render_pass, nullptr); 530 | vkDestroyPipelineLayout(vk_device, vk_pipeline_layout, nullptr); 531 | for (auto &v : swapchain_image_views) { 532 | vkDestroyImageView(vk_device, v, nullptr); 533 | } 534 | vkDestroySurfaceKHR(vk_instance, vk_surface, nullptr); 535 | vkDestroyDevice(vk_device, nullptr); 536 | vkDestroyInstance(vk_instance, nullptr); 537 | 538 | SDL_DestroyWindow(window); 539 | SDL_Quit(); 540 | 541 | return 0; 542 | } 543 | 544 | -------------------------------------------------------------------------------- /vert.vert: -------------------------------------------------------------------------------- 1 | // Not vulkan? 2 | // Omit and set via glslc? 3 | #version 450 4 | #extension GL_ARB_separate_shader_objects : enable 5 | 6 | layout(location = 0) out vec3 frag_color; 7 | 8 | vec2 positions[3] = vec2[]( 9 | vec2(0.0, -0.5), 10 | vec2(0.5, 0.5), 11 | vec2(-0.5, 0.5) 12 | ); 13 | 14 | vec3 colors[3] = vec3[]( 15 | vec3(1.0, 0.0, 0.0), 16 | vec3(0.0, 1.0, 0.0), 17 | vec3(0.0, 0.0, 1.0) 18 | ); 19 | 20 | void main() { 21 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 22 | frag_color = colors[gl_VertexIndex]; 23 | } 24 | 25 | --------------------------------------------------------------------------------