├── .gitignore ├── .travis.yml ├── COPYING ├── Makefile.am ├── README.md ├── configure.ac ├── shaders ├── tut10.frag ├── tut10.vert ├── tut11_postproc.frag ├── tut11_postproc.vert ├── tut11_render.frag ├── tut11_render.vert ├── tut12.frag ├── tut12.vert ├── tut3.comp ├── tut8.frag ├── tut8.vert ├── tut9.frag └── tut9.vert ├── tut1 ├── main.c ├── tut1.c ├── tut1.h ├── tut1_error.c └── tut1_error.h ├── tut10 ├── main.c ├── tut10_render.c └── tut10_render.h ├── tut11 ├── main.c ├── tut11.c ├── tut11.h ├── tut11_render.c └── tut11_render.h ├── tut12 ├── main.c ├── tut12.c └── tut12.h ├── tut2 ├── main.c ├── tut2.c └── tut2.h ├── tut3 ├── main.c ├── tut3.c └── tut3.h ├── tut4 ├── main.c ├── tut4.c └── tut4.h ├── tut5 ├── README.md ├── main.c ├── tut5.c ├── tut5.h └── vulkan-objects.svg ├── tut6 ├── main.c ├── tut6.c └── tut6.h ├── tut7 ├── main.c ├── tut7.c ├── tut7.h ├── tut7_render.c └── tut7_render.h ├── tut8 ├── main.c ├── tut8.c ├── tut8.h ├── tut8_render.c └── tut8_render.h └── tut9 ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | # autotools generated files 2 | autom4te.cache 3 | m4/ 4 | Makefile 5 | Makefile.in 6 | configure 7 | config.status 8 | libtool 9 | config.log 10 | stamp-* 11 | config.h.in 12 | config.h 13 | 14 | # autotools installed files 15 | aclocal.m4 16 | bin/ 17 | 18 | # build artifacts 19 | *.o 20 | *.exe 21 | *.a 22 | *.so 23 | *.la 24 | *.lo 25 | test 26 | .deps 27 | .libs 28 | 29 | # tests 30 | *.log 31 | *.trs 32 | 33 | # misc 34 | *.swp 35 | *~ 36 | 37 | # build directories 38 | build*/ 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | sudo: required # add sudo for the sake of vulkan 1.0.8.0 which tries to actually install Vulkan 4 | dist: trusty 5 | 6 | compiler: 7 | - gcc 8 | - clang 9 | 10 | env: 11 | global: 12 | - CFLAGS="-Wall -O2" 13 | matrix: 14 | - VULKAN_VERSION="1.0.8.0" VULKAN_ARCH="x86_64" 15 | - VULKAN_VERSION="1.0.17.0" VULKAN_ARCH="x86_64" 16 | - VULKAN_VERSION="1.0.26.0" VULKAN_ARCH="x86_64" 17 | - VULKAN_VERSION="1.0.39.1" VULKAN_ARCH="x86_64" 18 | 19 | before_install: 20 | - sudo apt-get -qq update 21 | - sudo apt-get install -y autoconf-archive libsdl2-dev graphviz 22 | 23 | before_script: 24 | - wget "https://vulkan.lunarg.com/sdk/download/$VULKAN_VERSION/linux/vulkansdk-linux-$VULKAN_ARCH-$VULKAN_VERSION.run" 25 | - chmod +x "vulkansdk-linux-$VULKAN_ARCH-$VULKAN_VERSION.run" 26 | - ./"vulkansdk-linux-$VULKAN_ARCH-$VULKAN_VERSION.run" 27 | - mkdir m4 || true 28 | - autoreconf -iv 29 | 30 | script: 31 | - VULKAN_SDK="$(pwd)/VulkanSDK/$VULKAN_VERSION/$VULKAN_ARCH" ./configure --without-ncurses # travis's packages are too old 32 | - make 33 | 34 | notifications: 35 | email: false 36 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | 3 | EXTRA_DIST = COPYING README.md tut5/README.md tut5/vulkan-objects.svg tut9/README.md 4 | 5 | bin_PROGRAMS = tut1/tut1 6 | tut1_tut1_SOURCES = tut1/tut1.c tut1/tut1_error.c tut1/main.c tut1/tut1.h tut1/tut1_error.h 7 | 8 | bin_PROGRAMS += tut2/tut2 9 | tut2_tut2_SOURCES = tut2/tut2.c tut2/main.c tut2/tut2.h \ 10 | tut1/tut1.c tut1/tut1_error.c 11 | 12 | bin_PROGRAMS += tut3/tut3 13 | tut3_tut3_SOURCES = tut3/tut3.c tut3/main.c tut3/tut3.h \ 14 | tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 15 | 16 | bin_PROGRAMS += tut4/tut4 17 | tut4_tut4_SOURCES = tut4/tut4.c tut4/main.c tut4/tut4.h \ 18 | tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 19 | 20 | bin_PROGRAMS += tut5/tut5 21 | tut5_tut5_SOURCES = tut5/tut5.c tut5/main.c tut5/tut5.h \ 22 | tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 23 | 24 | bin_PROGRAMS += tut6/tut6 25 | tut6_tut6_SOURCES = tut6/tut6.c tut6/main.c tut6/tut6.h \ 26 | tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 27 | 28 | bin_PROGRAMS += tut7/tut7 29 | tut7_tut7_SOURCES = tut7/tut7.c tut7/tut7_render.c tut7/main.c tut7/tut7.h tut7/tut7_render.h \ 30 | tut6/tut6.c tut4/tut4.c tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 31 | 32 | bin_PROGRAMS += tut8/tut8 33 | tut8_tut8_SOURCES = tut8/tut8.c tut8/tut8_render.c tut8/main.c tut8/tut8.h tut8/tut8_render.h \ 34 | tut7/tut7.c tut7/tut7_render.c tut6/tut6.c tut4/tut4.c tut3/tut3.c tut2/tut2.c \ 35 | tut1/tut1.c tut1/tut1_error.c 36 | 37 | bin_PROGRAMS += tut9/tut9 38 | tut9_tut9_SOURCES = tut9/main.c \ 39 | tut8/tut8.c tut8/tut8_render.c tut7/tut7.c tut7/tut7_render.c tut6/tut6.c \ 40 | tut4/tut4.c tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 41 | 42 | bin_PROGRAMS += tut10/tut10 43 | tut10_tut10_SOURCES = tut10/tut10_render.c tut10/main.c tut10/tut10_render.h \ 44 | tut8/tut8.c tut8/tut8_render.c tut7/tut7.c tut7/tut7_render.c tut6/tut6.c \ 45 | tut4/tut4.c tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 46 | 47 | bin_PROGRAMS += tut11/tut11 48 | tut11_tut11_SOURCES = tut11/tut11.c tut11/tut11_render.c tut11/main.c tut11/tut11.h tut11/tut11_render.h \ 49 | tut10/tut10_render.c tut8/tut8.c tut8/tut8_render.c tut7/tut7.c tut7/tut7_render.c \ 50 | tut6/tut6.c tut4/tut4.c tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 51 | 52 | if HAVE_NCURSES 53 | bin_PROGRAMS += tut12/tut12 54 | tut12_tut12_SOURCES = tut12/tut12.c tut12/main.c tut12/tut12.h \ 55 | tut8/tut8.c tut8/tut8_render.c tut7/tut7.c tut7/tut7_render.c tut6/tut6.c \ 56 | tut4/tut4.c tut3/tut3.c tut2/tut2.c tut1/tut1.c tut1/tut1_error.c 57 | endif 58 | 59 | shaderdir = $(datadir)/shaders 60 | shader_DATA = shaders/tut3.comp.spv \ 61 | shaders/tut8.vert.spv shaders/tut8.frag.spv \ 62 | shaders/tut9.vert.spv shaders/tut9.frag.spv \ 63 | shaders/tut10.vert.spv shaders/tut10.frag.spv \ 64 | shaders/tut11_render.vert.spv shaders/tut11_render.frag.spv \ 65 | shaders/tut11_postproc.vert.spv shaders/tut11_postproc.frag.spv \ 66 | shaders/tut12.vert.spv shaders/tut12.frag.spv 67 | CLEANFILES = $(shader_DATA) 68 | 69 | V_GLSLANG = $(V_GLSLANG_@AM_V@) 70 | V_GLSLANG_ = $(V_GLSLANG_@AM_DEFAULT_V@) 71 | V_GLSLANG_0 = @echo " GLSLANG " $@; 72 | V_GLSLANG_1 = 73 | 74 | %.spv: % 75 | @$(mkdir_p) $(builddir)/shaders 76 | $(V_GLSLANG)$(GLSLANGVALIDATOR) -V $< -o $@ 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shabi's Vulkan Tutorials (!OUTDATED!) 2 | ===================================== 3 | 4 | **NOTE: Do NOT follow this tutorial, Vulkan has improved a lot, and the API 5 | presented in these tutorials is outdated. For example, use dynamic rendering 6 | (introduced in VK_KHR_dynamic_rendering, promoted to Vulkan 1.3) instead of 7 | VkRenderPass objects. Another examples include use of dynamic state, 8 | VK_EXT_graphics_pipeline_library (or VK_EXT_shader_object), 9 | VK_KHR_synchronization2, timeline semaphores, descriptor buffers, descriptor 10 | indexing and more.** 11 | 12 | Travis-CI: [![Build Status](https://travis-ci.org/ShabbyX/vktut.svg?branch=master)](https://travis-ci.org/ShabbyX/vktut) 13 | 14 | This is a repository of tutorials on Vulkan. It starts with the basics, 15 | enumerating your GPUs, allocating memory and getting computation done. It then 16 | moves on to actually displaying things and using shaders. Along the way, some 17 | of the optional Vulkan layers are explored. 18 | 19 | Each tutorial may use functions implemented in a previous tutorial. The 20 | functions are thus named as `tutX_*()` so that it would be immediately obvious 21 | which tutorial you should refer to, if you don't remember what that function 22 | does. 23 | 24 | These tutorials were developed in Linux, and I have no intention of trying them 25 | on Windows. Feel free to send a pull request for the tweaks they would need to 26 | run on that. You would need the following external libraries: 27 | 28 | - Vulkan, of course 29 | - SDL2 30 | - X11-xcb 31 | - ncurses (optional for Tutorial 12) 32 | 33 | The tutorials are source code themselves, with enough comments in them to 34 | explain how things are done and/or why. I strongly recommend reading the 35 | Vulkan specifications alongside the tutorials. It is well-written and easy to 36 | grasp. Also note that Vulkan is not for the faint of heart. I expect you are 37 | not struggling with reading C code and have some understanding of computer 38 | graphics and operating systems. 39 | 40 | The `main.c` of each tutorial glues together the functionalities implemented in 41 | the `tut*.c` files, and may include uninteresting things like printing out 42 | information. The core usage of Vulkan is usually in `tut[0-9]\+.c` files, 43 | with `tut[0-9]\+_render.c` files containing utilities useful for rendering 44 | based on top of the core functions. 45 | 46 | Build and Execution 47 | ------------------- 48 | 49 | This project uses autotools. If you have received the project as a release 50 | tarball, you can go through the usual procedure: 51 | 52 | ``` 53 | mkdir build && cd build 54 | ../configure --enable-silent-rules 55 | make -j 56 | ``` 57 | 58 | If your Vulkan SDK installation is not in the standard locations, you would 59 | need to have the `VULKAN_SDK` variable set, for example set it in your 60 | environment: 61 | 62 | ``` 63 | export VULKAN_SDK=/path/to/vulkan/arch 64 | ``` 65 | 66 | Or run `configure` file with this environment: 67 | 68 | ``` 69 | VULKAN_SDK=/path/to/vulkan/arch ../configure 70 | ``` 71 | 72 | If you are building from source, you would need autotools installed as well as 73 | autoconf-archive and do the following step beforehand: 74 | 75 | ``` 76 | autoreconf -i 77 | ``` 78 | 79 | Each tutorial will have an executable in its own directory that you can run. 80 | 81 | --- 82 | 83 | **Disclaimer:** I am writing these tutorials as I am learning Vulkan myself. I 84 | am in no way an expert in computer graphics and my own knowledge of OpenGL 85 | barely passes 1.2. Please do not consider these tutorials as "best practices" 86 | in Vulkan. They are intended to merely ease you in Vulkan, getting you 87 | familiar with the API. How to _best_ utilize the API is beyond me at this 88 | point. 89 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.69]) 2 | AC_INIT([Vulkan Tutorials], [0.1], [https://github.com/ShabbyX/vktut/issues]) 3 | AC_CONFIG_AUX_DIR([bin]) 4 | AC_CONFIG_MACRO_DIR([m4]) 5 | AC_CANONICAL_SYSTEM 6 | 7 | AM_INIT_AUTOMAKE([1.14 subdir-objects -Wall -Wno-portability -Werror foreign]) 8 | 9 | AC_CONFIG_SRCDIR([tut1/main.c]) 10 | AC_CONFIG_HEADERS([config.h]) 11 | 12 | # Checks for programs 13 | AC_PROG_CC_C99 14 | AC_PROG_INSTALL 15 | AM_PROG_AR 16 | 17 | LT_INIT 18 | 19 | # Make sure C99 is available. We really need C11, but this is the best autoconf currently provides 20 | AS_IF([test x"$ac_cv_prog_cc_c99" = xno], 21 | [AC_MSG_ERROR([C99 support is required])]) 22 | 23 | # Checks for header files 24 | AC_CHECK_HEADERS([stddef.h stdlib.h string.h time.h], [], [AC_MSG_ERROR(Required header missing)]) 25 | 26 | # Checks for typedefs, structures, and compiler characteristics 27 | AC_C_INLINE 28 | AC_TYPE_SIZE_T 29 | 30 | # Checks for library functions 31 | AC_CHECK_FUNCS([malloc realloc memmove memset], [], [AC_MSG_ERROR(Required function missing)]) 32 | AC_CHECK_LIB([rt], [clock_gettime]) 33 | 34 | # Check for pthread 35 | AX_PTHREAD([ 36 | LIBS="$PTHREAD_LIBS $LIBS" 37 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 38 | CC="$PTHREAD_CC" 39 | ], [AC_MSG_ERROR(Pthread missing)]) 40 | 41 | # Now that the basics are checked, make sure vulkan is installed 42 | AS_IF([test x"$VULKAN_SDK" != x], [ 43 | CPPFLAGS="$CPPFLAGS -I$VULKAN_SDK/include" 44 | LDFLAGS="$LDFLAGS -L$VULKAN_SDK/lib" 45 | glslangvalidator="$VULKAN_SDK/bin/glslangValidator" 46 | ], [glslangvalidator="glslangValidator"]) 47 | AC_CHECK_HEADER([vulkan/vulkan.h], [], [AC_MSG_ERROR(Vulkan header not found)]) 48 | AC_CHECK_LIB([vulkan], [vkCreateInstance], [], [AC_MSG_ERROR(Vulkan library not found)]) 49 | 50 | # Make sure external dependencies are also installed 51 | # - SDL2 52 | # - XCB and Xlib 53 | AC_CHECK_HEADER([SDL2/SDL.h], [], [AC_MSG_ERROR(SDL2 headers not found)]) 54 | AC_CHECK_LIB([SDL2], [SDL_Init], [], [AC_MSG_ERROR(SDL2 library not found)]) 55 | AC_CHECK_HEADER([X11/Xlib-xcb.h], [], [AC_MSG_ERROR(Xlib-xcb header missing)]) 56 | AC_CHECK_LIB([X11-xcb], [XGetXCBConnection], [], [AC_MSG_ERROR(X11 library not found)]) 57 | 58 | # Optional libraries 59 | AC_ARG_WITH(ncurses, 60 | [AS_HELP_STRING([--with-ncurses@<:@=ARG@:>@], [Whether ncurses tutorials should be built @<:@default=yes@:>@])], , [with_ncurses=yes]) 61 | AS_IF([test x"$with_ncurses" = xyes], [ 62 | AC_CHECK_HEADER([curses.h], [], [with_ncurses=no]) 63 | AS_IF([test x"$with_ncurses" != xyes], [AC_CHECK_HEADER([ncurses/curses.h], [with_ncurses=yes], [with_ncurses=no])]) 64 | AC_CHECK_LIB([ncurses], [initscr], [], [with_ncurses=no]) 65 | ]) 66 | 67 | AC_SUBST(GLSLANGVALIDATOR, [$glslangvalidator]) 68 | 69 | AM_CONDITIONAL(HAVE_NCURSES, [test x"$with_ncurses" = xyes]) 70 | 71 | AC_CONFIG_FILES([Makefile]) 72 | AC_OUTPUT 73 | -------------------------------------------------------------------------------- /shaders/tut10.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_color; 7 | layout (location = 1) in vec2 in_tex; 8 | 9 | layout (set = 0, binding = 0) uniform sampler2D texture_image1; 10 | layout (set = 0, binding = 1) uniform sampler2D texture_image2; 11 | 12 | /* 13 | * Here is how you declare push constants. It should always be a uniform block, with the layout qualified as 14 | * push_constant, and where each element has an offset declaration. Note that GLSL already defines offset, array and 15 | * matrix strides when declaring a uniform block, so the `layout (offset=XX)` is unnecessary here. Still, this is 16 | * useful to know, especially with push constants, since this "offset" is the only thing you tell Vulkan when talking 17 | * about push constants. 18 | * 19 | * Note: I figured this syntax out by reading the grammar accepted by glslang (https://github.com/KhronosGroup/glslang). 20 | * If you see imprecisions, or know of a proper documentation, do let me know. 21 | */ 22 | layout (push_constant) uniform push_constants 23 | { 24 | layout (offset = 0) float mix_value; 25 | } constants; 26 | 27 | layout (location = 0) out vec4 out_color; 28 | 29 | void main() 30 | { 31 | vec4 tex_color1 = texture(texture_image1, in_tex); 32 | vec4 tex_color2 = texture(texture_image2, in_tex); 33 | vec4 tex_color = mix(tex_color1, tex_color2, constants.mix_value); 34 | out_color = vec4(in_color * tex_color.xyz, 1.0); 35 | } 36 | -------------------------------------------------------------------------------- /shaders/tut10.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 1) in vec3 in_color; 8 | layout (location = 2) in vec2 in_tex; 9 | 10 | layout (set = 0, binding = 2) uniform transformation_matrices 11 | { 12 | mat4x4 mvp; /* model, view and projection matrices pre-calculated */ 13 | } transform; 14 | 15 | layout (location = 0) out vec3 out_color; 16 | layout (location = 1) out vec2 out_tex; 17 | 18 | void main() 19 | { 20 | gl_Position = transform.mvp * vec4(in_pos.xyz, 1); 21 | out_color = in_color; 22 | out_tex = in_tex; 23 | } 24 | -------------------------------------------------------------------------------- /shaders/tut11_postproc.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec2 in_tex; 7 | 8 | layout (set = 0, binding = 0) uniform sampler2D rendered_image; 9 | 10 | layout (push_constant) uniform push_constants 11 | { 12 | float pixel_size; 13 | float hue_levels; 14 | float saturation_levels; 15 | float intensity_levels; 16 | } constants; 17 | 18 | layout (location = 0) out vec4 out_color; 19 | 20 | /* 21 | * rgb2hsv and hsv2rgb are shamelessly copied off the internet: 22 | * 23 | * http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl 24 | */ 25 | vec3 rgb2hsv(vec3 c) 26 | { 27 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 28 | vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy);; 29 | vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); 30 | 31 | float d = q.x - min(q.w, q.y); 32 | float e = 1.0e-10; 33 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 34 | } 35 | 36 | vec3 hsv2rgb(vec3 c) 37 | { 38 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 39 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 40 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 41 | } 42 | 43 | vec2 levelize(vec2 v, vec2 levels) 44 | { 45 | return clamp(round(v * levels) / levels, 0.0, 1.0); 46 | } 47 | 48 | vec3 levelize(vec3 v, vec3 levels) 49 | { 50 | return clamp(round(v * levels) / levels, 0.0, 1.0); 51 | } 52 | 53 | void main() 54 | { 55 | vec2 window_size = textureSize(rendered_image, 0); 56 | vec2 pixelate_levels = window_size / constants.pixel_size; 57 | 58 | vec2 bottom_left = levelize(in_tex, pixelate_levels); 59 | vec2 window_pixel_length = vec2(1.0) / window_size; 60 | vec2 top_right = bottom_left + constants.pixel_size * window_pixel_length; 61 | 62 | /* 63 | * Sample the image at all pixels inside the fake big pixel and average the colors. 64 | * 65 | * Note that this is a terrible hit on the performance. What might one want to do instead 66 | * is to ask Vulkan to shrink the image (vkCmdBlitImage), then sample it with VK_FILTER_LINEAR. 67 | * The problem here is that this averaging is repeated by each pixel inside the fake big pixel 68 | * only to produce the same result. vkCmdBlitImage would more efficiently do it. 69 | */ 70 | float count = 0; 71 | vec4 sum = vec4(0.0); 72 | for (float s = bottom_left.s; s < top_right.s; s += window_pixel_length.s) 73 | for (float t = bottom_left.t; t < top_right.t; t += window_pixel_length.t) 74 | { 75 | sum += texture(rendered_image, vec2(s, t)); 76 | count += 1; 77 | } 78 | sum /= count; 79 | sum = clamp(sum, 0.0, 1.0); 80 | 81 | /* Levelize each component of the colors in hsv space. */ 82 | vec3 levels = vec3(constants.hue_levels, constants.saturation_levels, constants.intensity_levels); 83 | vec3 hsv = rgb2hsv(sum.xyz); 84 | vec3 rgb = hsv2rgb(levelize(hsv, levels)); 85 | out_color = vec4(rgb.xyz, 1.0); 86 | } 87 | -------------------------------------------------------------------------------- /shaders/tut11_postproc.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 2) in vec2 in_tex; 8 | 9 | layout (location = 0) out vec2 out_tex; 10 | 11 | void main() 12 | { 13 | gl_Position = vec4(in_pos.xyz, 1); 14 | out_tex = in_tex; 15 | } 16 | -------------------------------------------------------------------------------- /shaders/tut11_render.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_color; 7 | 8 | layout (location = 0) out vec4 out_color; 9 | 10 | void main() 11 | { 12 | out_color = vec4(in_color.xyz, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /shaders/tut11_render.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 1) in vec3 in_color; 8 | 9 | layout (set = 0, binding = 0) uniform transformation_matrices 10 | { 11 | mat4x4 mvp; /* model, view and projection matrices pre-calculated */ 12 | } transform; 13 | 14 | layout (location = 0) out vec3 out_color; 15 | 16 | void main() 17 | { 18 | gl_Position = transform.mvp * vec4(in_pos.xyz, 1); 19 | out_color = in_color; 20 | } 21 | -------------------------------------------------------------------------------- /shaders/tut12.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_color; 7 | 8 | layout (location = 0) out vec4 out_color; 9 | 10 | void main() 11 | { 12 | out_color = vec4(in_color.xyz, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /shaders/tut12.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 1) in vec3 in_color; 8 | 9 | layout (set = 0, binding = 0) uniform transformation_matrices 10 | { 11 | mat4x4 mvp; /* model, view and projection matrices pre-calculated */ 12 | } transform; 13 | 14 | layout (push_constant) uniform push_constants 15 | { 16 | layout (offset = 0) float angle; 17 | } constants; 18 | 19 | layout (location = 0) out vec3 out_color; 20 | 21 | void main() 22 | { 23 | mat4 rotation = mat4(cos(constants.angle), -sin(constants.angle), 0.0, 0.0, 24 | sin(constants.angle), cos(constants.angle), 0.0, 0.0, 25 | 0.0, 0.0, 1.0, 0.0, 26 | 0.0, 0.0, 0.0, 1.0); 27 | gl_Position = transform.mvp * rotation * vec4(in_pos.xyz, 1); 28 | out_color = in_color; 29 | } 30 | -------------------------------------------------------------------------------- /shaders/tut3.comp: -------------------------------------------------------------------------------- 1 | /* 2 | * To use this shader, you need to generate a SPIR-V binary file out of it. 3 | * This can be done with glslangValidator (automatically done by make): 4 | * 5 | * glslangValidator -V tut3.comp -o tut3.comp.spv 6 | * 7 | * You then need to give the SPIR-V file path to the tut3 or tut4 executables. 8 | */ 9 | 10 | #version 450 11 | 12 | #extension GL_ARB_separate_shader_objects: enable 13 | #extension GL_ARB_shading_language_420pack: enable 14 | 15 | layout (local_size_x = 64) in; 16 | layout (set = 0, binding = 0, r32f) uniform imageBuffer buf; 17 | 18 | void main() 19 | { 20 | imageStore(buf, int(gl_GlobalInvocationID.x), imageLoad(buf, int(gl_GlobalInvocationID.x)) + 1); 21 | } 22 | -------------------------------------------------------------------------------- /shaders/tut8.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_color; 7 | 8 | layout (location = 0) out vec4 out_color; 9 | 10 | void main() 11 | { 12 | out_color = vec4(in_color.xyz, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /shaders/tut8.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 1) in vec3 in_color; 8 | 9 | layout (set = 0, binding = 0) uniform transformation_matrices 10 | { 11 | mat4x4 mvp; /* model, view and projection matrices pre-calculated */ 12 | } transform; 13 | 14 | layout (location = 0) out vec3 out_color; 15 | 16 | void main() 17 | { 18 | gl_Position = transform.mvp * vec4(in_pos.xyz, 1); 19 | out_color = in_color; 20 | } 21 | -------------------------------------------------------------------------------- /shaders/tut9.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_color; 7 | layout (location = 1) in vec2 in_tex; 8 | 9 | layout (set = 0, binding = 0) uniform sampler2D texture_image; 10 | 11 | layout (location = 0) out vec4 out_color; 12 | 13 | void main() 14 | { 15 | vec4 tex_color = texture(texture_image, in_tex); 16 | out_color = vec4(in_color * tex_color.xyz, 1.0); 17 | } 18 | -------------------------------------------------------------------------------- /shaders/tut9.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects: enable 4 | #extension GL_ARB_shading_language_420pack: enable 5 | 6 | layout (location = 0) in vec3 in_pos; 7 | layout (location = 1) in vec3 in_color; 8 | layout (location = 2) in vec2 in_tex; 9 | 10 | layout (set = 0, binding = 1) uniform transformation_matrices 11 | { 12 | mat4x4 mvp; /* model, view and projection matrices pre-calculated */ 13 | } transform; 14 | 15 | layout (location = 0) out vec3 out_color; 16 | layout (location = 1) out vec2 out_tex; 17 | 18 | void main() 19 | { 20 | gl_Position = transform.mvp * vec4(in_pos.xyz, 1); 21 | out_color = in_color; 22 | out_tex = in_tex; 23 | } 24 | -------------------------------------------------------------------------------- /tut1/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "tut1.h" 24 | 25 | #define MAX_DEVICES 2 26 | 27 | static void print_surprise(const char *indent, const char *who, const char *what, const char *how) 28 | { 29 | static bool already_asked = false; 30 | static char choice = 0;; 31 | if (!already_asked) 32 | { 33 | printf("Are you a woman or a man? "); 34 | if (scanf(" %c%*s", &choice) != 1) 35 | choice = 0; 36 | already_asked = true; 37 | } 38 | 39 | printf("%s", indent); 40 | if (choice == 'w' || choice == 'W') 41 | printf("Damn girl, "); 42 | else if (choice == 'm' || choice == 'M') 43 | printf("Whoa dude, "); 44 | else 45 | printf("Wow neither-woman-nor-man, "); 46 | 47 | printf("%s more %s than I could %s.\n", who, what, how); 48 | } 49 | 50 | int main(int argc, char **argv) 51 | { 52 | tut1_error res; 53 | int retval = EXIT_FAILURE; 54 | VkInstance vk; 55 | struct tut1_physical_device devs[MAX_DEVICES]; 56 | uint32_t dev_count = MAX_DEVICES; 57 | 58 | /* Fire up Vulkan */ 59 | res = tut1_init(&vk); 60 | if (!tut1_error_is_success(&res)) 61 | { 62 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 63 | goto exit_bad_init; 64 | } 65 | 66 | printf("Vulkan is in the house.\n"); 67 | 68 | /* Take a look at what devices there are */ 69 | res = tut1_enumerate_devices(vk, devs, &dev_count); 70 | if (tut1_error_is_warning(&res)) 71 | { 72 | print_surprise("", "you've got", "devices", "dream of"); 73 | printf("I have information on only %"PRIu32" of them:\n", dev_count); 74 | } 75 | else if (!tut1_error_is_success(&res)) 76 | { 77 | tut1_error_printf(&res, "Could not enumerate devices\n"); 78 | goto exit_bad_enumerate; 79 | } 80 | else 81 | printf("I detected the following %"PRIu32" device%s:\n", dev_count, dev_count == 1?"":"s"); 82 | 83 | /* 84 | * Print out some of the information taken when enumerating physical devices. This is by no means an 85 | * exhaustive printout, but to give you the idea. 86 | */ 87 | for (uint32_t i = 0; i < dev_count; ++i) 88 | { 89 | struct tut1_physical_device *dev = &devs[i]; 90 | VkPhysicalDeviceProperties *pr = &dev->properties; 91 | 92 | printf(" - %s: %s (id: 0x%04X) from vendor 0x%04X [driver version: 0x%04X, API version: 0x%04X]\n", 93 | tut1_VkPhysicalDeviceType_string(pr->deviceType), pr->deviceName, 94 | pr->deviceID, pr->vendorID, pr->driverVersion, pr->apiVersion); 95 | if (dev->queue_families_incomplete) 96 | { 97 | print_surprise(" ", "your device", "queue families", "imagine"); 98 | printf(" I have information on only %"PRIu32" of them:\n", dev->queue_family_count); 99 | } 100 | else 101 | printf(" The device supports the following %"PRIu32" queue famil%s:\n", dev->queue_family_count, dev->queue_family_count == 1?"y":"ies"); 102 | 103 | for (uint32_t j = 0; j < dev->queue_family_count; ++j) 104 | { 105 | VkQueueFamilyProperties *qf = &dev->queue_families[j]; 106 | 107 | printf(" * %"PRIu32" queue%s with the following capabilit%s:\n", qf->queueCount, qf->queueCount == 1?"":"s", 108 | qf->queueFlags && (qf->queueFlags & (qf->queueFlags - 1)) == 0?"y":"ies"); 109 | if (qf->queueFlags == 0) 110 | printf(" None\n"); 111 | if ((qf->queueFlags & VK_QUEUE_GRAPHICS_BIT)) 112 | printf(" Graphics\n"); 113 | if ((qf->queueFlags & VK_QUEUE_COMPUTE_BIT)) 114 | printf(" Compute\n"); 115 | if ((qf->queueFlags & VK_QUEUE_TRANSFER_BIT)) 116 | printf(" Transfer\n"); 117 | if ((qf->queueFlags & VK_QUEUE_SPARSE_BINDING_BIT)) 118 | printf(" Sparse binding\n"); 119 | } 120 | 121 | printf(" The device supports memories of the following types:\n"); 122 | for (uint32_t j = 0; j < dev->memories.memoryTypeCount; ++j) 123 | { 124 | printf(" *"); 125 | if (dev->memories.memoryTypes[j].propertyFlags == 0) 126 | printf(" "); 127 | if ((dev->memories.memoryTypes[j].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) 128 | printf(" device-local"); 129 | if ((dev->memories.memoryTypes[j].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) 130 | printf(" host-visible"); 131 | if ((dev->memories.memoryTypes[j].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) 132 | printf(" host-coherent"); 133 | if ((dev->memories.memoryTypes[j].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)) 134 | printf(" host-cached"); 135 | if ((dev->memories.memoryTypes[j].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)) 136 | printf(" lazy"); 137 | printf(": Available in Heap of size %"PRIu64"MB\n", dev->memories.memoryHeaps[dev->memories.memoryTypes[j].heapIndex].size / (1024 * 1024)); 138 | } 139 | } 140 | 141 | /* Congratulations, you can now duplicate the `vulkaninfo` program. */ 142 | 143 | retval = 0; 144 | 145 | /* Cleanup after yourself */ 146 | 147 | exit_bad_enumerate: 148 | tut1_exit(vk); 149 | 150 | exit_bad_init: 151 | return retval; 152 | } 153 | -------------------------------------------------------------------------------- /tut1/tut1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include "tut1.h" 21 | 22 | tut1_error tut1_init(VkInstance *vk) 23 | { 24 | tut1_error retval = TUT1_ERROR_NONE; 25 | VkResult res; 26 | 27 | /* 28 | * Vulkan is not just for graphics. As the slogan goes, graphics and computation belong together. As a 29 | * result, initialization in Vulkan is rather verbose to allow the application to adapt to a wide set of 30 | * possible hardware configurations. 31 | * 32 | * Initializing Vulkan is thus somewhat verbose, and unsurprisingly, independent of window management systems. 33 | * In fact, there is nothing related to graphics in this tutorial. 34 | * 35 | * To initialize Vulkan, a set of information needs to be given to it. Most importantly, this includes Vulkan 36 | * layers and extensions that are desired (none in this tutorial). 37 | * 38 | * Some of Vulkan structs, such as those ending in Info need to have their `sType` set, which is the first 39 | * member of the struct. Generally, VkSomeStruct has type VK_STRUCTURE_TYPE_SOME_STRUCT, so this is easy to 40 | * remember. The rest of the struct members are described as they are used. 41 | */ 42 | 43 | /* 44 | * The vkApplicationInfo struct contains some information about what application is going to run. This may seem 45 | * completely unnecessary and it in fact is! However, giving this information helps the drivers know which 46 | * application, or better yet which engine, they are dealing with. They might have foreseen special 47 | * optimizations knowing the engine, for example. 48 | * 49 | * With that in mind, the application and engine names are arbitrarily set in this tutorial as well as their 50 | * versions. I have used 0xAABBCC to denote version AA.BB.CC. For example 0x010000 for version 1.0.0. 51 | */ 52 | VkApplicationInfo app_info = { 53 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 54 | .pApplicationName = "Vulkan Tutorial", 55 | .applicationVersion = 0x010000, 56 | .pEngineName = "Vulkan Tutorial", 57 | .engineVersion = 0x010000, 58 | /* 59 | * the apiVersion field is used to make sure your application is going to work with the driver. You 60 | * could either use VK_MAKE_VERSION to specify a particular version, e.g. VK_MAKE_VERSION(1, 0, 0), or 61 | * use a predefined value, for example VK_API_VERSION_1_0. 62 | */ 63 | .apiVersion = VK_API_VERSION_1_0, 64 | }; 65 | 66 | /* 67 | * The vkInstanceCreateInfo struct takes the previous application information, as well as the names of layers 68 | * and extensions your application needs to use. This tutorial uses none, so they are left out (set to 0). 69 | */ 70 | VkInstanceCreateInfo info = { 71 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 72 | .pApplicationInfo = &app_info, 73 | }; 74 | 75 | /* 76 | * vkCreateInstance then takes the above info and initializes the vkInstance struct. This struct replaces 77 | * all the global state traditionally found in OpenGL. This means that the application can actually have 78 | * multiple instances of Vulkan at the same time, each bound to a separate implementation. Imagine your PC 79 | * having one nvidia card and one amd, and your application is rendering/computing something different on 80 | * each of them! 81 | * 82 | * The second argument of vkCreateInstance gives callbacks to replace the default memory allocation functions. 83 | * This is useful if an application wants to track its memory usage (there is a Vulkan layer doing this 84 | * already by the way), improve the speed of the allocation functions by using custom allocation algorithms 85 | * that are better suited to the allocation patterns of the application, or as the Vulkan specs mentions, 86 | * embedded systems debugging or logging. Using custom allocation functions will be explored in a future 87 | * tutorial. 88 | */ 89 | res = vkCreateInstance(&info, NULL, vk); 90 | tut1_error_set_vkresult(&retval, res); 91 | 92 | return retval; 93 | } 94 | 95 | void tut1_exit(VkInstance vk) 96 | { 97 | /* 98 | * Once the application is finished with Vulkan, it can perform cleanups. The second argument is the same 99 | * as the one with vkCreateInstance(*). Since we didn't provide one to vkCreateInstance, we don't need to 100 | * provide one to vkDestroyInstance either. 101 | * 102 | * (*) technically, it doesn't need to be the same, as long as the callbacks are compatible. That is, as 103 | * long as memory allocated with one can be freed with the other, the callbacks are ok. 104 | */ 105 | vkDestroyInstance(vk, NULL); 106 | } 107 | 108 | tut1_error tut1_enumerate_devices(VkInstance vk, struct tut1_physical_device *devs, uint32_t *count) 109 | { 110 | VkPhysicalDevice phy_devs[*count]; 111 | tut1_error retval = TUT1_ERROR_NONE; 112 | VkResult res; 113 | 114 | /* 115 | * A physical device in Vulkan is any actual device with Vulkan capabilities. For example, a physical GPU is 116 | * such a device. Vulkan later on associates logical devices to these physical devices as a way to access 117 | * them. For now, we just want to peek at what devices there are. 118 | * 119 | * The vkEnumeratePhysicalDevices takes an array of VkPhysicalDevices to fill in any physical device it finds, 120 | * with the maximum size of the array in its `count` argument. This `count` argument also serves the purpose 121 | * of returning the actual number of devices filled in the array. 122 | * 123 | * If the array (`phy_devs` below) is not provided (i.e., is `NULL`), then the `count` argument would return 124 | * the total number of physical devices available. I could have used this to query the number of devices and 125 | * allocate memory large enough for all of them. For the sake of simplicity however, let's just assume there 126 | * can't be _that_ many devices and use a fixed upper bound. If there are more devices than I foresaw, then 127 | * VK_INCOMPLETE would be returned, stating that the array doesn't contain all that there is. 128 | */ 129 | res = vkEnumeratePhysicalDevices(vk, count, phy_devs); 130 | tut1_error_set_vkresult(&retval, res); 131 | if (res < 0) 132 | goto exit_failed; 133 | 134 | for (uint32_t i = 0; i < *count; ++i) 135 | { 136 | devs[i].physical_device = phy_devs[i]; 137 | 138 | /* 139 | * Once we have handles to each physical device, we can query information regarding the device. This 140 | * is done with vkGetPhysicalDeviceProperties. The device properties include vendor and device ids, 141 | * driver version, device name, device limits and such information. 142 | * 143 | * While the device properties mostly deal with identifying the physical device, information is 144 | * separately available regarding what the device (or rather the device driver) can do. These features 145 | * include bound checking on buffer access, whether other-than-1-width lines are supported, whether 146 | * more than one viewport is possible, whether various texture compression algorithms are understood, 147 | * whether certain data types are supported in shaders, and miscellaneous information such as these. 148 | * 149 | * The information on the device memories can also be queried. The device memory is divided in 150 | * "heaps", and each heap may contain memory of several "types". A memory type indicates whether the 151 | * memory is visible to host (CPU) and other such properties. The memory types are returned in an 152 | * array, sorted in such a way that if you are looking for a memory with certain properties, the first 153 | * memory you would find (searching from the first element of the array), would be the most efficient 154 | * for that purpose. This is useful in future tutorials when we actually allocate device memory. 155 | */ 156 | vkGetPhysicalDeviceProperties(devs[i].physical_device, &devs[i].properties); 157 | vkGetPhysicalDeviceFeatures(devs[i].physical_device, &devs[i].features); 158 | vkGetPhysicalDeviceMemoryProperties(devs[i].physical_device, &devs[i].memories); 159 | 160 | /* 161 | * Each physical device has certain abilities, such as being able to support graphics operations or 162 | * general computation. Vulkan uses _queues_ to transmit operations to the devices for various 163 | * purposes (e.g. graphics or compute). A device groups its queues in _families_ and indicates how 164 | * many queues in each family it supports. The application can thus know which queues to use to 165 | * perform the desired operation. 166 | * 167 | * The vkGetPhysicalDeviceQueueFamilyProperties function gets this information. Note again that I 168 | * could have used the technique described above to first get the total number of queue families, 169 | * dynamically allocate enough space for it and then query the queues themselves, making sure I avoid 170 | * getting a VK_INCOMPLETE return value. Once again, for the sake of simplicity, a maximum possible 171 | * number of queue families is assumed. 172 | */ 173 | uint32_t qfc = 0; 174 | devs[i].queue_family_count = TUT1_MAX_QUEUE_FAMILY; 175 | vkGetPhysicalDeviceQueueFamilyProperties(devs[i].physical_device, &qfc, NULL); 176 | vkGetPhysicalDeviceQueueFamilyProperties(devs[i].physical_device, &devs[i].queue_family_count, devs[i].queue_families); 177 | 178 | devs[i].queue_families_incomplete = devs[i].queue_family_count < qfc; 179 | } 180 | 181 | /* 182 | * At this point, there is a great deal of information regarding the physical devices available and their 183 | * capabilities stored in `devs`. See main.c for some of this information getting printed out. 184 | */ 185 | 186 | exit_failed: 187 | return retval; 188 | } 189 | 190 | /* The following functions get a readable string out of the Vulkan standard enums */ 191 | 192 | const char *tut1_VkPhysicalDeviceType_string(VkPhysicalDeviceType type) 193 | { 194 | switch (type) 195 | { 196 | case VK_PHYSICAL_DEVICE_TYPE_OTHER: 197 | return "Neither GPU nor CPU"; 198 | case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: 199 | return "Integrated GPU"; 200 | case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: 201 | return "Discrete GPU"; 202 | case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: 203 | return "Virtual GPU"; 204 | case VK_PHYSICAL_DEVICE_TYPE_CPU: 205 | return "CPU"; 206 | default: 207 | return "Unrecognized device type"; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /tut1/tut1.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT1_H 21 | #define TUT1_H 22 | 23 | #include 24 | #include 25 | #include "tut1_error.h" 26 | 27 | tut1_error tut1_init(VkInstance *vk); 28 | void tut1_exit(VkInstance vk); 29 | 30 | #define TUT1_MAX_QUEUE_FAMILY 10 31 | 32 | struct tut1_physical_device 33 | { 34 | VkPhysicalDevice physical_device; 35 | VkPhysicalDeviceProperties properties; 36 | VkPhysicalDeviceFeatures features; 37 | VkPhysicalDeviceMemoryProperties memories; 38 | 39 | VkQueueFamilyProperties queue_families[TUT1_MAX_QUEUE_FAMILY]; 40 | uint32_t queue_family_count; 41 | bool queue_families_incomplete; 42 | }; 43 | 44 | tut1_error tut1_enumerate_devices(VkInstance vk, struct tut1_physical_device *devs, uint32_t *count); 45 | 46 | const char *tut1_VkPhysicalDeviceType_string(VkPhysicalDeviceType type); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /tut1/tut1_error.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "tut1_error.h" 24 | 25 | /* 26 | * Note: this file handles error tracking and reporting, and has little to do with the tutorials themselves. The main 27 | * purpose here is to track the errors precisely, while keeping the actual tutorials as clean as possible. 28 | */ 29 | 30 | void tut1_error_data_set_vkresult(struct tut1_error_data *error, VkResult vkresult, const char *file, unsigned int line) 31 | { 32 | /* If this is not an error, ignore it */ 33 | if (vkresult == 0) 34 | return; 35 | 36 | /* If error is already set, keep the oldest error, but override warnings */ 37 | if (error->type != TUT1_ERROR_SUCCESS && !(error->type == TUT1_ERROR_VKRESULT_WARNING && vkresult < 0)) 38 | return; 39 | 40 | *error = (struct tut1_error_data){ 41 | .type = vkresult < 0?TUT1_ERROR_VKRESULT:TUT1_ERROR_VKRESULT_WARNING, 42 | .vkresult = vkresult, 43 | .file = file, 44 | .line = line, 45 | }; 46 | } 47 | 48 | void tut1_error_data_set_errno(struct tut1_error_data *error, int err_no, const char *file, unsigned int line) 49 | { 50 | /* If this is not an error, ignore it */ 51 | if (err_no == 0) 52 | return; 53 | 54 | /* If error is already set, keep the oldest error, but override warnings */ 55 | if (error->type != TUT1_ERROR_SUCCESS && error->type != TUT1_ERROR_VKRESULT_WARNING) 56 | return; 57 | 58 | *error = (struct tut1_error_data){ 59 | .type = TUT1_ERROR_ERRNO, 60 | .err_no = err_no, 61 | .file = file, 62 | .line = line, 63 | }; 64 | } 65 | 66 | bool tut1_error_data_merge(struct tut1_error_data *error, struct tut1_error_data *other) 67 | { 68 | /* If this is not an error, ignore it */ 69 | if (other->type == TUT1_ERROR_SUCCESS) 70 | return false; 71 | 72 | /* If error is already set, keep the oldest error, but override warnings */ 73 | if (error->type != TUT1_ERROR_SUCCESS && !(error->type == TUT1_ERROR_VKRESULT_WARNING && (other->type == TUT1_ERROR_VKRESULT || other->type == TUT1_ERROR_ERRNO))) 74 | return false; 75 | 76 | *error = *other; 77 | return true; 78 | } 79 | 80 | bool tut1_error_is_success(struct tut1_error *error) 81 | { 82 | return error->error.type == TUT1_ERROR_SUCCESS; 83 | } 84 | 85 | bool tut1_error_is_warning(struct tut1_error *error) 86 | { 87 | return error->error.type == TUT1_ERROR_VKRESULT_WARNING; 88 | } 89 | 90 | bool tut1_error_is_error(struct tut1_error *error) 91 | { 92 | return !tut1_error_is_success(error) && !tut1_error_is_warning(error); 93 | } 94 | 95 | static const char *VkResult_string(VkResult res) 96 | { 97 | switch (res) 98 | { 99 | case VK_SUCCESS: 100 | return "Success"; 101 | case VK_NOT_READY: 102 | return "Not ready"; 103 | case VK_TIMEOUT: 104 | return "Timeout"; 105 | case VK_EVENT_SET: 106 | return "Event set"; 107 | case VK_EVENT_RESET: 108 | return "Event reset"; 109 | case VK_INCOMPLETE: 110 | return "Incomplete"; 111 | case VK_ERROR_OUT_OF_HOST_MEMORY: 112 | return "Out of host memory"; 113 | case VK_ERROR_OUT_OF_DEVICE_MEMORY: 114 | return "Out of device memory"; 115 | case VK_ERROR_INITIALIZATION_FAILED: 116 | return "Initialization failed"; 117 | case VK_ERROR_DEVICE_LOST: 118 | return "Device lost"; 119 | case VK_ERROR_MEMORY_MAP_FAILED: 120 | return "Memory map failed"; 121 | case VK_ERROR_LAYER_NOT_PRESENT: 122 | return "Layer not present"; 123 | case VK_ERROR_EXTENSION_NOT_PRESENT: 124 | return "Extension not present"; 125 | case VK_ERROR_FEATURE_NOT_PRESENT: 126 | return "Feature not present"; 127 | case VK_ERROR_INCOMPATIBLE_DRIVER: 128 | return "Incompatible driver"; 129 | case VK_ERROR_TOO_MANY_OBJECTS: 130 | return "Too many objects"; 131 | case VK_ERROR_FORMAT_NOT_SUPPORTED: 132 | return "Format not supported"; 133 | #if 0 134 | /* TODO: enable this when it becomes wide-spread (new error code in 1.0.22) */ 135 | case VK_ERROR_FRAGMENTED_POOL: 136 | return "Allocation failure due to fragmented pool"; 137 | #endif 138 | case VK_ERROR_SURFACE_LOST_KHR: 139 | return "Surface lost"; 140 | case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: 141 | return "Native window is in use"; 142 | case VK_SUBOPTIMAL_KHR: 143 | return "Suboptimal"; 144 | case VK_ERROR_OUT_OF_DATE_KHR: 145 | return "Surface is out of date"; 146 | case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: 147 | return "Incompatible display"; 148 | case VK_ERROR_VALIDATION_FAILED_EXT: 149 | return "Validation failed"; 150 | default: 151 | return "Unrecognized error"; 152 | } 153 | } 154 | 155 | static void print_error(FILE *fout, struct tut1_error_data *error_data, const char *prefix) 156 | { 157 | fprintf(fout, "%s:%u: %s", error_data->file, error_data->line, prefix); 158 | switch (error_data->type) 159 | { 160 | case TUT1_ERROR_VKRESULT_WARNING: 161 | case TUT1_ERROR_VKRESULT: 162 | fprintf(fout, "%s (VkResult %d)\n", VkResult_string(error_data->vkresult), error_data->vkresult); 163 | break; 164 | case TUT1_ERROR_ERRNO: 165 | fprintf(fout, "%s (errno %d)\n", strerror(error_data->err_no), error_data->err_no); 166 | break; 167 | default: 168 | fprintf(fout, "\n"); 169 | break; 170 | } 171 | } 172 | 173 | void tut1_error_fprintf(FILE *fout, struct tut1_error *error, const char *fmt, ...) 174 | { 175 | /* if no error, don't print anything */ 176 | if (error->error.type == TUT1_ERROR_SUCCESS) 177 | return; 178 | 179 | va_list args; 180 | va_start(args, fmt); 181 | vfprintf(fout, fmt, args); 182 | va_end(args); 183 | 184 | print_error(fout, &error->error, ""); 185 | if (error->sub_error.type != TUT1_ERROR_SUCCESS) 186 | print_error(fout, &error->sub_error, " Resulting from this error: "); 187 | } 188 | -------------------------------------------------------------------------------- /tut1/tut1_error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT1_ERROR_H 21 | #define TUT1_ERROR_H 22 | 23 | /* 24 | * Note: this file handles error tracking and reporting, and has little to do with the tutorials themselves. The main 25 | * purpose here is to track the errors precisely, while keeping the actual tutorials as clean as possible. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | enum tut1_error_type 34 | { 35 | TUT1_ERROR_SUCCESS = 0, 36 | TUT1_ERROR_VKRESULT, 37 | TUT1_ERROR_VKRESULT_WARNING, /* VK_INCOMPLETE for example */ 38 | TUT1_ERROR_ERRNO, 39 | }; 40 | 41 | struct tut1_error_data 42 | { 43 | enum tut1_error_type type; 44 | union { 45 | VkResult vkresult; 46 | int err_no; 47 | }; 48 | const char *file; 49 | unsigned int line; 50 | } tut1_error_data; 51 | 52 | typedef struct tut1_error 53 | { 54 | struct tut1_error_data error; 55 | struct tut1_error_data sub_error; /* 56 | * Used in cases where error is e.g. "VK_INCOMPLETE", and it is due to 57 | * another error. 58 | */ 59 | } tut1_error; 60 | 61 | #define TUT1_ERROR_NONE (struct tut1_error){ .error = { .type = TUT1_ERROR_SUCCESS, }, .sub_error = { .type = TUT1_ERROR_SUCCESS, }, } 62 | 63 | #define tut1_error_set_vkresult(es, e) tut1_error_data_set_vkresult(&(es)->error, (e), __FILE__, __LINE__) 64 | #define tut1_error_set_errno(es, e) tut1_error_data_set_errno (&(es)->error, (e), __FILE__, __LINE__) 65 | #define tut1_error_sub_set_vkresult(es, e) tut1_error_data_set_vkresult(&(es)->sub_error, (e), __FILE__, __LINE__) 66 | #define tut1_error_sub_set_errno(es, e) tut1_error_data_set_errno (&(es)->sub_error, (e), __FILE__, __LINE__) 67 | #define tut1_error_merge(es, os) \ 68 | do { \ 69 | if (tut1_error_data_merge(&(es)->error, &(os)->error)) \ 70 | (es)->sub_error = (os)->sub_error; \ 71 | } while (0) 72 | #define tut1_error_sub_merge(es, os) tut1_error_data_merge(&(es)->sub_error, &(os)->error) 73 | 74 | void tut1_error_data_set_vkresult(struct tut1_error_data *error, VkResult vkresult, const char *file, unsigned int line); 75 | void tut1_error_data_set_errno(struct tut1_error_data *error, int err_no, const char *file, unsigned int line); 76 | bool tut1_error_data_merge(struct tut1_error_data *error, struct tut1_error_data *other); 77 | 78 | bool tut1_error_is_success(struct tut1_error *error); 79 | bool tut1_error_is_warning(struct tut1_error *error); 80 | bool tut1_error_is_error(struct tut1_error *error); 81 | #define tut1_error_printf(es, ...) tut1_error_fprintf(stdout, (es), __VA_ARGS__) 82 | void tut1_error_fprintf(FILE *fout, struct tut1_error *error, const char *fmt, ...) __attribute__((format(printf, 3, 4))); 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /tut10/tut10_render.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include "tut10_render.h" 21 | 22 | static tut1_error create_staging_buffer(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_render_essentials *essentials, 23 | struct tut7_buffer *staging, uint8_t *contents, size_t size, const char *name) 24 | { 25 | tut1_error retval = TUT1_ERROR_NONE; 26 | 27 | /* Create a buffer to hold the data. */ 28 | *staging = (struct tut7_buffer){ 29 | .size = size, 30 | .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 31 | .host_visible = true, 32 | }; 33 | 34 | retval = tut7_create_buffers(phy_dev, dev, staging, 1); 35 | if (!tut1_error_is_success(&retval)) 36 | { 37 | tut1_error_printf(&retval, "Failed to create staging %s buffer\n", name); 38 | return retval; 39 | } 40 | 41 | /* Copy the data over to the buffer. */ 42 | char staging_name[50]; 43 | snprintf(staging_name, 50, "staging %s", name); 44 | retval = tut8_render_fill_buffer(dev, staging, contents, size, staging_name); 45 | 46 | return retval; 47 | } 48 | 49 | tut1_error tut10_render_init_texture(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_render_essentials *essentials, 50 | struct tut7_image *image, VkImageLayout layout, uint8_t *contents, const char *name) 51 | { 52 | tut1_error retval = TUT1_ERROR_NONE; 53 | 54 | /* Create a buffer to hold the texture data. */ 55 | struct tut7_buffer staging; 56 | retval = create_staging_buffer(phy_dev, dev, essentials, &staging, contents, image->extent.width * image->extent.height * 4, name); 57 | if (!tut1_error_is_success(&retval)) 58 | goto exit_cleanup; 59 | 60 | /* Transition the image to a layout we can copy to. */ 61 | retval = tut8_render_transition_images(dev, essentials, image, 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT, name); 62 | if (!tut1_error_is_success(&retval)) 63 | goto exit_cleanup; 64 | 65 | /* Copy the buffer to the image. */ 66 | VkBufferImageCopy image_copy = { 67 | .imageSubresource = { 68 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 69 | .layerCount = 1, 70 | }, 71 | .imageExtent = { 72 | .width = image->extent.width, 73 | .height = image->extent.height, 74 | }, 75 | }; 76 | 77 | retval = tut8_render_copy_buffer_to_image(dev, essentials, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &staging, &image_copy, name); 78 | if (!tut1_error_is_success(&retval)) 79 | goto exit_cleanup; 80 | 81 | /* Transition the image to the desired layout */ 82 | retval = tut8_render_transition_images(dev, essentials, image, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout, VK_IMAGE_ASPECT_COLOR_BIT, name); 83 | 84 | exit_cleanup: 85 | /* 86 | * TODO: Enable after the NVidia driver fixes its bug with handling NULL pointers. 87 | * tut7_free_buffers(dev, &staging, 1); 88 | */ 89 | 90 | return retval; 91 | } 92 | 93 | tut1_error tut10_render_init_buffer(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_render_essentials *essentials, 94 | struct tut7_buffer *buffer, void *contents, const char *name) 95 | { 96 | tut1_error retval = TUT1_ERROR_NONE; 97 | 98 | /* Create a buffer to hold the data. */ 99 | struct tut7_buffer staging; 100 | retval = create_staging_buffer(phy_dev, dev, essentials, &staging, contents, buffer->size, name); 101 | if (!tut1_error_is_success(&retval)) 102 | goto exit_cleanup; 103 | 104 | /* Copy staging buffer over to the real buffer. */ 105 | retval = tut8_render_copy_buffer(dev, essentials, buffer, &staging, buffer->size, name); 106 | 107 | exit_cleanup: 108 | /* 109 | * TODO: Enable after the NVidia driver fixes its bug with handling NULL pointers. 110 | * tut7_free_buffers(dev, &staging, 1); 111 | */ 112 | 113 | return retval; 114 | } 115 | -------------------------------------------------------------------------------- /tut10/tut10_render.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT10_RENDER_H 21 | #define TUT10_RENDER_H 22 | 23 | #include "../tut8/tut8_render.h" 24 | 25 | /* 26 | * Create a texture image filled with BGRA data. This uses a command buffer, submits it, and waits for it to finish, 27 | * so it's not supposed to be used while recording a command buffer. It creates and destroys a staging buffer in the 28 | * process. In the end, it transitions the image to the desired layout. 29 | */ 30 | tut1_error tut10_render_init_texture(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_render_essentials *essentials, 31 | struct tut7_image *image, VkImageLayout layout, uint8_t *contents, const char *name); 32 | 33 | /* 34 | * Copy over arbitrary data to the buffer. This uses a command buffer, submits it, and waits for it to finish, so it's 35 | * not supposed to be used while recording a command buffer. It creates and destroys a staging buffer in the process. 36 | */ 37 | tut1_error tut10_render_init_buffer(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_render_essentials *essentials, 38 | struct tut7_buffer *buffer, void *contents, const char *name); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /tut11/tut11.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include "tut11.h" 22 | 23 | static tut1_error create_render_pass(struct tut2_device *dev, VkFormat color_format, VkFormat depth_format, VkRenderPass *render_pass, 24 | enum tut11_render_pass_load_op keeps_contents, enum tut11_make_depth_buffer has_depth) 25 | { 26 | tut1_error retval = TUT1_ERROR_NONE; 27 | VkResult res; 28 | 29 | /* Create the render pass containing both attachments, similar to tut7_create_graphics_buffers. */ 30 | VkAttachmentDescription render_pass_attachments[2] = { 31 | [0] = { 32 | .format = color_format, 33 | .samples = VK_SAMPLE_COUNT_1_BIT, 34 | .loadOp = keeps_contents?VK_ATTACHMENT_LOAD_OP_LOAD:VK_ATTACHMENT_LOAD_OP_CLEAR, 35 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, 36 | .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 37 | .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 38 | }, 39 | [1] = { 40 | .format = depth_format, 41 | .samples = VK_SAMPLE_COUNT_1_BIT, 42 | .loadOp = keeps_contents?VK_ATTACHMENT_LOAD_OP_LOAD:VK_ATTACHMENT_LOAD_OP_CLEAR, 43 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, 44 | .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 45 | .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 46 | }, 47 | }; 48 | VkAttachmentReference render_pass_attachment_references[2] = { 49 | [0] = { 50 | .attachment = 0, 51 | .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 52 | }, 53 | [1] = { 54 | .attachment = 1, 55 | .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 56 | }, 57 | }; 58 | VkSubpassDescription render_pass_subpasses[1] = { 59 | [0] = { 60 | .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, 61 | .colorAttachmentCount = 1, 62 | .pColorAttachments = &render_pass_attachment_references[0], 63 | .pDepthStencilAttachment = has_depth?&render_pass_attachment_references[1]:NULL, 64 | }, 65 | }; 66 | VkRenderPassCreateInfo render_pass_info = { 67 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, 68 | .attachmentCount = has_depth?2:1, 69 | .pAttachments = render_pass_attachments, 70 | .subpassCount = 1, 71 | .pSubpasses = render_pass_subpasses, 72 | }; 73 | 74 | res = vkCreateRenderPass(dev->device, &render_pass_info, NULL, render_pass); 75 | tut1_error_set_vkresult(&retval, res); 76 | 77 | return retval; 78 | } 79 | 80 | tut1_error tut11_create_offscreen_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkFormat format, 81 | struct tut11_offscreen_buffers *offscreen_buffers, uint32_t offscreen_buffer_count, VkRenderPass *render_pass, 82 | enum tut11_render_pass_load_op keeps_contents, enum tut11_make_depth_buffer has_depth) 83 | { 84 | /* 85 | * This is similar to tut7_create_graphics_buffers, but is more tailored towards off-screen rendering. To that 86 | * end, it creates a color image, a depth image, a render pass (unique among all offscreen_buffers) and a 87 | * framebuffer. The depth buffer is optional, in case this is an off-screen post-processing for example. 88 | * Additionally, unlike tut7_create_graphics_buffers it doesn't assume that the contents of the color image 89 | * should be cleared on load! 90 | */ 91 | uint32_t successful = 0; 92 | tut1_error retval = TUT1_ERROR_NONE; 93 | VkResult res; 94 | tut1_error err; 95 | 96 | for (uint32_t i = 0; i < offscreen_buffer_count; ++i) 97 | { 98 | offscreen_buffers[i].color= (struct tut7_image){0}; 99 | offscreen_buffers[i].depth = (struct tut7_image){0}; 100 | offscreen_buffers[i].framebuffer = NULL; 101 | } 102 | 103 | /* Choose a supported format for depth/stencil attachment */ 104 | VkFormat depth_format = tut7_get_supported_depth_stencil_format(phy_dev); 105 | 106 | /* Render pass */ 107 | retval = create_render_pass(dev, format, depth_format, render_pass, keeps_contents, has_depth); 108 | if (!tut1_error_is_success(&retval)) 109 | goto exit_failed; 110 | 111 | for (uint32_t i = 0; i < offscreen_buffer_count; ++i) 112 | { 113 | /* 114 | * Create an image for color attachment, an image for depth attachment, and a framebuffer holding them 115 | * together. Again, somewhat similar to tut7_create_graphics_buffers. 116 | */ 117 | 118 | /* 119 | * Here, we create an off-screen image to render into. In essence, what we are doing here is creating 120 | * an image just like the images the swapchain makes for us. Well, not exactly like it! The swapchain 121 | * images only necessarily support the COLOR_ATTACHMENT usage (remember supportedUsageFlags of 122 | * VkSurfaceCapabilitiesKHR? No? See `tut6_get_swapchain`), but here we want to later be able to 123 | * SAMPLE the image for post-processing. 124 | */ 125 | offscreen_buffers[i].color = (struct tut7_image){ 126 | .format = format, 127 | .extent = offscreen_buffers[i].surface_size, 128 | .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT /* Use as target of rendering */ 129 | | VK_IMAGE_USAGE_SAMPLED_BIT, /* Use as input in post-processing */ 130 | .stage = VK_SHADER_STAGE_FRAGMENT_BIT, /* Used in fragment shader stage in both rendering and post-processing */ 131 | .make_view = true, 132 | }; 133 | err = tut7_create_images(phy_dev, dev, &offscreen_buffers[i].color, 1); 134 | tut1_error_sub_merge(&retval, &err); 135 | if (!tut1_error_is_success(&err)) 136 | continue; 137 | 138 | if (has_depth) 139 | { 140 | /* The depth/stencil buffer is exactly like in tut7_create_graphics_buffers. */ 141 | offscreen_buffers[i].depth = (struct tut7_image){ 142 | .format = depth_format, 143 | .extent = offscreen_buffers[i].surface_size, 144 | .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 145 | .make_view = true, 146 | }; 147 | 148 | err = tut7_create_images(phy_dev, dev, &offscreen_buffers[i].depth, 1); 149 | tut1_error_sub_merge(&retval, &err); 150 | if (!tut1_error_is_success(&err)) 151 | continue; 152 | } 153 | 154 | /* The framebuffer is also exactly like in tut7_create_graphics_buffers. */ 155 | VkImageView framebuffer_attachments[2] = { 156 | offscreen_buffers[i].color.view, 157 | offscreen_buffers[i].depth.view, 158 | }; 159 | VkFramebufferCreateInfo framebuffer_info = { 160 | .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, 161 | .renderPass = *render_pass, 162 | .attachmentCount = has_depth?2:1, 163 | .pAttachments = framebuffer_attachments, 164 | .width = offscreen_buffers[i].surface_size.width, 165 | .height = offscreen_buffers[i].surface_size.height, 166 | .layers = 1, 167 | }; 168 | 169 | res = vkCreateFramebuffer(dev->device, &framebuffer_info, NULL, &offscreen_buffers[i].framebuffer); 170 | tut1_error_sub_set_vkresult(&retval, res); 171 | if (res) 172 | continue; 173 | 174 | ++successful; 175 | } 176 | 177 | tut1_error_set_vkresult(&retval, successful == offscreen_buffer_count?VK_SUCCESS:VK_INCOMPLETE); 178 | exit_failed: 179 | return retval; 180 | } 181 | 182 | tut1_error tut11_create_graphics_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkFormat format, 183 | struct tut7_graphics_buffers *graphics_buffers, uint32_t graphics_buffer_count, VkRenderPass *render_pass, 184 | enum tut11_render_pass_load_op keeps_contents, enum tut11_make_depth_buffer has_depth) 185 | { 186 | /* 187 | * This function is exactly like tut7_create_graphics_buffers, but with the new configuration arguments 188 | * keeps_contents and has_depth. 189 | * 190 | * The difference between this function and tut11_create_offscreen_buffers is essentially that the color image 191 | * is taken from swapchain here, but created from scratch there. 192 | */ 193 | uint32_t successful = 0; 194 | tut1_error retval = TUT1_ERROR_NONE; 195 | VkResult res; 196 | tut1_error err; 197 | 198 | for (uint32_t i = 0; i < graphics_buffer_count; ++i) 199 | { 200 | graphics_buffers[i].color_view = NULL; 201 | graphics_buffers[i].depth = (struct tut7_image){0}; 202 | graphics_buffers[i].framebuffer = NULL; 203 | } 204 | 205 | /* Supported depth/stencil format */ 206 | VkFormat depth_format = tut7_get_supported_depth_stencil_format(phy_dev);; 207 | 208 | /* Render pass */ 209 | retval = create_render_pass(dev, format, depth_format, render_pass, keeps_contents, has_depth); 210 | if (!tut1_error_is_success(&retval)) 211 | goto exit_failed; 212 | 213 | for (uint32_t i = 0; i < graphics_buffer_count; ++i) 214 | { 215 | /* View on swapchain images + depth buffer */ 216 | VkImageViewCreateInfo view_info = { 217 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 218 | .image = graphics_buffers[i].swapchain_image, 219 | .viewType = VK_IMAGE_VIEW_TYPE_2D, 220 | .format = format, 221 | .subresourceRange = { 222 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 223 | .baseMipLevel = 0, 224 | .levelCount = VK_REMAINING_MIP_LEVELS, 225 | .baseArrayLayer = 0, 226 | .layerCount = VK_REMAINING_ARRAY_LAYERS, 227 | }, 228 | }; 229 | 230 | res = vkCreateImageView(dev->device, &view_info, NULL, &graphics_buffers[i].color_view); 231 | tut1_error_sub_set_vkresult(&retval, res); 232 | if (res) 233 | continue; 234 | 235 | if (has_depth) 236 | { 237 | graphics_buffers[i].depth = (struct tut7_image){ 238 | .format = depth_format, 239 | .extent = graphics_buffers[i].surface_size, 240 | .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 241 | .make_view = true, 242 | .multisample = false, 243 | .will_be_initialized = false, 244 | }; 245 | 246 | err = tut7_create_images(phy_dev, dev, &graphics_buffers[i].depth, 1); 247 | tut1_error_sub_merge(&retval, &err); 248 | if (!tut1_error_is_success(&err)) 249 | continue; 250 | } 251 | 252 | /* The framebuffer */ 253 | VkImageView framebuffer_attachments[2] = { 254 | graphics_buffers[i].color_view, 255 | graphics_buffers[i].depth.view, 256 | }; 257 | VkFramebufferCreateInfo framebuffer_info = { 258 | .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, 259 | .renderPass = *render_pass, 260 | .attachmentCount = has_depth?2:1, 261 | .pAttachments = framebuffer_attachments, 262 | .width = graphics_buffers[i].surface_size.width, 263 | .height = graphics_buffers[i].surface_size.height, 264 | .layers = 1, 265 | }; 266 | 267 | res = vkCreateFramebuffer(dev->device, &framebuffer_info, NULL, &graphics_buffers[i].framebuffer); 268 | tut1_error_sub_set_vkresult(&retval, res); 269 | if (res) 270 | continue; 271 | 272 | ++successful; 273 | } 274 | 275 | tut1_error_set_vkresult(&retval, successful == graphics_buffer_count?VK_SUCCESS:VK_INCOMPLETE); 276 | exit_failed: 277 | return retval; 278 | } 279 | 280 | void tut11_free_offscreen_buffers(struct tut2_device *dev, struct tut11_offscreen_buffers *offscreen_buffers, uint32_t graphics_buffer_count, 281 | VkRenderPass render_pass) 282 | { 283 | vkDeviceWaitIdle(dev->device); 284 | 285 | /* Same old, same old */ 286 | for (uint32_t i = 0; i < graphics_buffer_count; ++i) 287 | { 288 | tut7_free_images(dev, &offscreen_buffers[i].color, 1); 289 | tut7_free_images(dev, &offscreen_buffers[i].depth, 1); 290 | 291 | vkDestroyFramebuffer(dev->device, offscreen_buffers[i].framebuffer, NULL); 292 | } 293 | 294 | vkDestroyRenderPass(dev->device, render_pass, NULL); 295 | } 296 | 297 | void tut11_free_graphics_buffers(struct tut2_device *dev, struct tut7_graphics_buffers *graphics_buffers,uint32_t graphics_buffer_count, 298 | VkRenderPass render_pass) 299 | { 300 | tut7_free_graphics_buffers(dev, graphics_buffers, graphics_buffer_count, render_pass); 301 | } 302 | -------------------------------------------------------------------------------- /tut11/tut11.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT11_H 21 | #define TUT11_H 22 | 23 | #include "../tut8/tut8.h" 24 | 25 | enum tut11_render_pass_load_op 26 | { 27 | TUT11_CLEAR = 0, 28 | TUT11_KEEP = 1, 29 | }; 30 | 31 | enum tut11_make_depth_buffer 32 | { 33 | TUT11_WITHOUT_DEPTH = 0, 34 | TUT11_WITH_DEPTH = 1, 35 | }; 36 | 37 | struct tut11_offscreen_buffers 38 | { 39 | /* inputs */ 40 | 41 | VkExtent2D surface_size; 42 | 43 | /* outputs */ 44 | 45 | struct tut7_image color; 46 | struct tut7_image depth; 47 | 48 | VkFramebuffer framebuffer; 49 | }; 50 | 51 | tut1_error tut11_create_offscreen_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkFormat format, 52 | struct tut11_offscreen_buffers *offscreen_buffers, uint32_t offscreen_buffer_count, VkRenderPass *render_pass, 53 | enum tut11_render_pass_load_op keeps_contents, enum tut11_make_depth_buffer has_depth); 54 | tut1_error tut11_create_graphics_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkFormat format, 55 | struct tut7_graphics_buffers *graphics_buffers, uint32_t graphics_buffer_count, VkRenderPass *render_pass, 56 | enum tut11_render_pass_load_op keeps_contents, enum tut11_make_depth_buffer has_depth); 57 | 58 | void tut11_free_offscreen_buffers(struct tut2_device *dev, struct tut11_offscreen_buffers *offscreen_buffers, uint32_t offscreen_buffer_count, 59 | VkRenderPass render_pass); 60 | void tut11_free_graphics_buffers(struct tut2_device *dev, struct tut7_graphics_buffers *graphics_buffers,uint32_t graphics_buffer_count, 61 | VkRenderPass render_pass); 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /tut11/tut11_render.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include "tut11_render.h" 21 | 22 | int tut11_render_start(struct tut7_render_essentials *essentials, struct tut2_device *dev, 23 | struct tut6_swapchain *swapchain, VkImageLayout to_layout, uint32_t *image_index) 24 | { 25 | /* Nothing different here */ 26 | return tut7_render_start(essentials, dev, swapchain, to_layout, image_index); 27 | } 28 | 29 | int tut11_render_finish(struct tut7_render_essentials *essentials, struct tut2_device *dev, 30 | struct tut6_swapchain *swapchain, VkImageLayout from_layout, uint32_t image_index, 31 | VkSemaphore wait_sem, VkSemaphore signal_sem) 32 | { 33 | /* This is all the same as in tut7_render_finish, except the added semaphores */ 34 | tut1_error retval = TUT1_ERROR_NONE; 35 | VkResult res; 36 | 37 | /* Transition image to PRESENT_SRC */ 38 | VkImageMemoryBarrier image_barrier = { 39 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 40 | .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, 41 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, 42 | .oldLayout = from_layout, 43 | .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 44 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 45 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 46 | .image = essentials->images[image_index], 47 | .subresourceRange = { 48 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 49 | .baseMipLevel = 0, 50 | .levelCount = 1, 51 | .baseArrayLayer = 0, 52 | .layerCount = 1, 53 | }, 54 | }; 55 | 56 | /* All WRITEs anywhere must be done before all READs after the pipeline. */ 57 | vkCmdPipelineBarrier(essentials->cmd_buffer, 58 | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 59 | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 60 | 0, /* no flags */ 61 | 0, NULL, /* no memory barriers */ 62 | 0, NULL, /* no buffer barriers */ 63 | 1, &image_barrier); /* our image transition */ 64 | 65 | vkEndCommandBuffer(essentials->cmd_buffer); 66 | 67 | res = vkResetFences(dev->device, 1, &essentials->exec_fence); 68 | tut1_error_set_vkresult(&retval, res); 69 | if (res) 70 | { 71 | tut1_error_printf(&retval, "Failed to reset fence\n"); 72 | return res; 73 | } 74 | 75 | /* 76 | * Submit to queue. 77 | * 78 | * Wait on wait_sem (if provided) in addition to sem_post_acquire. Signal signal_sem (if provided) in addition 79 | * to sem_pre_submit. 80 | */ 81 | VkSemaphore wait_sems[2] = {essentials->sem_post_acquire, wait_sem}; 82 | VkSemaphore signal_sems[2] = {essentials->sem_pre_submit, signal_sem}; 83 | VkPipelineStageFlags wait_sem_stages[2] = {VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT}; 84 | VkSubmitInfo submit_info = { 85 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 86 | .waitSemaphoreCount = wait_sem?2:1, 87 | .pWaitSemaphores = wait_sems, 88 | .pWaitDstStageMask = wait_sem_stages, 89 | .commandBufferCount = 1, 90 | .pCommandBuffers = &essentials->cmd_buffer, 91 | .signalSemaphoreCount = signal_sem?2:1, 92 | .pSignalSemaphores = signal_sems, 93 | }; 94 | vkQueueSubmit(essentials->present_queue, 1, &submit_info, essentials->exec_fence); 95 | 96 | /* Present image */ 97 | VkPresentInfoKHR present_info = { 98 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 99 | .waitSemaphoreCount = 1, 100 | .pWaitSemaphores = &essentials->sem_pre_submit, 101 | .swapchainCount = 1, 102 | .pSwapchains = &swapchain->swapchain, 103 | .pImageIndices = &image_index, 104 | }; 105 | res = vkQueuePresentKHR(essentials->present_queue, &present_info); 106 | tut1_error_set_vkresult(&retval, res); 107 | if (res < 0) 108 | { 109 | tut1_error_printf(&retval, "Failed to queue image for presentation\n"); 110 | return -1; 111 | } 112 | 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /tut11/tut11_render.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT11_RENDER_H 21 | #define TUT11_RENDER_H 22 | 23 | #include "tut11.h" 24 | #include "../tut10/tut10_render.h" 25 | 26 | /* 27 | * Similar to tut7_render_start/finish, but allow additional wait and signal semaphores so the submission will be 28 | * synchronized with off-screen renders as well. 29 | */ 30 | int tut11_render_start(struct tut7_render_essentials *essentials, struct tut2_device *dev, 31 | struct tut6_swapchain *swapchain, VkImageLayout to_layout, uint32_t *image_index); 32 | int tut11_render_finish(struct tut7_render_essentials *essentials, struct tut2_device *dev, 33 | struct tut6_swapchain *swapchain, VkImageLayout from_layout, uint32_t image_index, 34 | VkSemaphore wait_sem, VkSemaphore signal_sem); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /tut12/tut12.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT12_H 21 | #define TUT12_H 22 | 23 | #include 24 | #include "../tut6/tut6.h" 25 | 26 | tut1_error tut12_get_swapchain(VkInstance vk, struct tut1_physical_device *phy_dev, struct tut2_device *dev, 27 | struct tut6_swapchain *swapchain, WINDOW *window, uint32_t thread_count, bool allow_no_vsync); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /tut2/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "tut2.h" 23 | 24 | #define MAX_DEVICES 2 25 | 26 | int main(int argc, char **argv) 27 | { 28 | tut1_error res; 29 | int retval = EXIT_FAILURE; 30 | VkInstance vk; 31 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 32 | struct tut2_device devs[MAX_DEVICES]; 33 | uint32_t dev_count = MAX_DEVICES; 34 | 35 | /* Fire up Vulkan */ 36 | res = tut1_init(&vk); 37 | if (!tut1_error_is_success(&res)) 38 | { 39 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 40 | goto exit_bad_init; 41 | } 42 | 43 | /* Take a look at what devices there are */ 44 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 45 | if (tut1_error_is_error(&res)) 46 | { 47 | tut1_error_printf(&res, "Could not enumerate devices\n"); 48 | goto exit_bad_enumerate; 49 | } 50 | 51 | /* 52 | * Set up devices. In the early tutorials, we will use the simpler compute queues rather than graphics. This 53 | * may not be as cool, but directly moving on to graphics is a large step. 54 | */ 55 | for (uint32_t i = 0; i < dev_count; ++i) 56 | { 57 | res = tut2_setup(&phy_devs[i], &devs[i], VK_QUEUE_COMPUTE_BIT); 58 | if (!tut1_error_is_success(&res)) 59 | { 60 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 61 | goto exit_bad_setup; 62 | } 63 | } 64 | 65 | /* 66 | * Let's take a break at this point. There is already a lot of information in this tutorial and we need a few 67 | * more things to set up before we can do anything. You probably had heard that Vulkan is a verbose API. 68 | * 69 | * [Drops mic] 70 | */ 71 | 72 | printf("Got queues and command buffers, it was nice.\n"); 73 | 74 | retval = 0; 75 | 76 | /* Cleanup after yourself */ 77 | 78 | exit_bad_setup: 79 | for (uint32_t i = 0; i < dev_count; ++i) 80 | tut2_cleanup(&devs[i]); 81 | 82 | exit_bad_enumerate: 83 | tut1_exit(vk); 84 | 85 | exit_bad_init: 86 | return retval; 87 | } 88 | -------------------------------------------------------------------------------- /tut2/tut2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "tut2.h" 23 | 24 | tut1_error tut2_get_dev(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 25 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count) 26 | { 27 | tut1_error retval = TUT1_ERROR_NONE; 28 | VkResult res; 29 | 30 | /* 31 | * A Vulkan logical device is a connection to a physical device, which is used to interact with that device. 32 | * In Tutorial 1, we gathered information regarding the physical device. Now, we want to actually do something 33 | * with it. 34 | * 35 | * By now, you should be familiar with how this works. There is a VkDeviceCreateInfo struct that gives 36 | * information on what is expected of the device, and a vkDeviceCreate function that performs the action. 37 | * There is the usual optional argument to use user-provided allocation callbacks, which is ignored in this 38 | * tutorial. 39 | */ 40 | *dev = (struct tut2_device){0}; 41 | 42 | /* 43 | * As we have seen in Tutorial 1, the physical device can possibly support multiple queue families, each with 44 | * different capabilities. When create a logical device, we also ask for a set of queues to be allocated for 45 | * us. Here, I'm using the information we recovered in Tutorial 1 and ask for all available queues that match 46 | * any of the capabilities requested (`qflags`). 47 | * 48 | * The VkDeviceQueueCreateInfo takes the index of queue family (as recovered in the list of 49 | * VkQueueFamilyProperties) so that the driver knows which queue family you are referring to, as well as the 50 | * number of actual queues that need to be dedicated to the application. Each queue in the queue family is 51 | * given a priority between 0 (low) and 1 (high) and therefore an array of priorities is also provided to 52 | * VkDeviceQueueCreateInfo. What effects priorities actually have on the execution of queues are left to the 53 | * drivers. We'll ignore that and just give them all an equal priority of 0. 54 | */ 55 | uint32_t max_queue_count = *queue_info_count; 56 | *queue_info_count = 0; 57 | 58 | uint32_t max_family_queues = 0; 59 | for (uint32_t i = 0; i < phy_dev->queue_family_count; ++i) 60 | if (max_family_queues < phy_dev->queue_families[i].queueCount) 61 | max_family_queues = phy_dev->queue_families[i].queueCount; 62 | float queue_priorities[max_family_queues]; 63 | memset(queue_priorities, 0, sizeof queue_priorities); 64 | 65 | for (uint32_t i = 0; i < phy_dev->queue_family_count && i < max_queue_count; ++i) 66 | { 67 | /* Check if the queue has any of the desired capabilities. If so, add it to the list of desired queues */ 68 | if ((phy_dev->queue_families[i].queueFlags & qflags) == 0) 69 | continue; 70 | 71 | queue_info[(*queue_info_count)++] = (VkDeviceQueueCreateInfo){ 72 | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 73 | .queueFamilyIndex = i, 74 | .queueCount = phy_dev->queue_families[i].queueCount, 75 | .pQueuePriorities = queue_priorities, 76 | }; 77 | } 78 | 79 | /* If there are no compatible queues, there is little one can do here */ 80 | if (*queue_info_count == 0) 81 | { 82 | tut1_error_set_vkresult(&retval, VK_ERROR_FEATURE_NOT_PRESENT); 83 | goto exit_failed; 84 | } 85 | 86 | /* 87 | * The VkDeviceCreateInfo asks for resources in the driver to be dedicated to the application. We already saw 88 | * this with the defining the desired queues above. We should also do this with the features we request from 89 | * the device. Here, I will ask for all the features that the device has (information which was queried in 90 | * Tutorial 1). This may in general not be desirable though; for example robostBufferAccess may be forced to 91 | * VK_FALSE to disable the overhead of array bounds checking. 92 | * 93 | * Additionally, one could enable layers and extensions similar to vkCreateInstance. The layers and extensions 94 | * enabled with vkCreateInstance are those that apply the Vulkan API in general, such as a validation layer, 95 | * but the layers and extensions enabled with vkDeviceCreate are more specific to the device itself. In either 96 | * case, they will be explored in a future tutorial. 97 | */ 98 | VkDeviceCreateInfo dev_info = { 99 | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 100 | .queueCreateInfoCount = *queue_info_count, 101 | .pQueueCreateInfos = queue_info, 102 | .pEnabledFeatures = &phy_dev->features, 103 | }; 104 | 105 | /* 106 | * All API functions that create something follow the same general rule. The handle of the object to be 107 | * created is provided last, an optional argument providing memory allocation callbacks is present, and a 108 | * CreateInfo struct is passed. The parent object is passed first (except for vkCreateInstance, where 109 | * there is no parent). 110 | * 111 | * vkCreateDevice is no exception. We are not yet using custom memory allocation functions, so they are 112 | * unused. The rest is already explained. 113 | */ 114 | res = vkCreateDevice(phy_dev->physical_device, &dev_info, NULL, &dev->device); 115 | tut1_error_set_vkresult(&retval, res); 116 | 117 | exit_failed: 118 | return retval; 119 | } 120 | 121 | tut1_error tut2_get_commands(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkDeviceQueueCreateInfo queue_info[], uint32_t queue_info_count) 122 | { 123 | tut1_error retval = TUT1_ERROR_NONE; 124 | VkResult res; 125 | 126 | /* 127 | * Now that we have a device handle, we can start talking with it using command buffers. A command buffer is 128 | * a recording of actions to be taken by the device. Vulkan works by allowing a thread to record its command 129 | * buffers and once done, submit it to the driver for execution. The commands themselves are numerous and 130 | * including commands to draw (for graphics), compute, execute a secondary buffer, copy images, etc. 131 | * 132 | * To efficiently create command buffers, a command buffer pool is used for each queue family. A pool is 133 | * generally a set of preallocated objects (possibly in contiguous memory), which can be used to "allocate" 134 | * and "free" objects of those type more efficiently. If you don't already know what a pool is, you can think 135 | * of it as a specialized `malloc`/`free` implementation that is faster because it already knows the object 136 | * sizes in advance. The pool memory itself is created using the usual memory allocation functions. 137 | */ 138 | dev->command_pools = malloc(queue_info_count * sizeof *dev->command_pools); 139 | if (dev->command_pools == NULL) 140 | { 141 | tut1_error_set_errno(&retval, errno); 142 | goto exit_failed; 143 | } 144 | 145 | for (uint32_t i = 0; i < queue_info_count; ++i) 146 | { 147 | struct tut2_commands *cmd = &dev->command_pools[i]; 148 | *cmd = (struct tut2_commands){0}; 149 | 150 | /* 151 | * Remember the actual queue flags for each queue, so that later we can know which queue had which of 152 | * the capabilities we asked of it. 153 | */ 154 | cmd->qflags = phy_dev->queue_families[queue_info[i].queueFamilyIndex].queueFlags; 155 | 156 | /* 157 | * The vkCreateCommandPool takes a VkCommandPoolCreateInfo that tells it what queue family the pool 158 | * would be creating command buffers from. Apart from that, you can provide flags that indicate your 159 | * usage of the command pool, to help it better optimize the process. For example, if you create and 160 | * destroy command buffers frequently, you could give VK_COMMAND_POOL_CREATE_TRANSIENT_BIT to the 161 | * flags. If you would like each command buffer to be resettable separately (we will get to what this 162 | * means later), you can give VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT to flags. For now, let's 163 | * go with resettable command buffers. 164 | */ 165 | VkCommandPoolCreateInfo pool_info = { 166 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 167 | .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, 168 | .queueFamilyIndex = queue_info[i].queueFamilyIndex, 169 | }; 170 | 171 | /* Does this start to look familiar? */ 172 | res = vkCreateCommandPool(dev->device, &pool_info, NULL, &cmd->pool); 173 | tut1_error_set_vkresult(&retval, res); 174 | if (res < 0) 175 | goto exit_failed; 176 | ++dev->command_pool_count; 177 | 178 | /* 179 | * Once a pool is created, we are free to allocate as many command buffers as we like. Beforehand, 180 | * however, you need to know more about queues. We already saw how there are different queue families 181 | * with different properties. The driver may allow multiple queues of the same family. Different 182 | * threads can submit their work on different queues without conflict. Later, they would need to 183 | * synchronize the queues, but we will get to that in a future tutorial. 184 | * 185 | * For now, let's create one command buffer per queue. 186 | */ 187 | 188 | cmd->queues = malloc(queue_info[i].queueCount * sizeof *cmd->queues); 189 | cmd->buffers = malloc(queue_info[i].queueCount * sizeof *cmd->buffers); 190 | if (cmd->queues == NULL || cmd->buffers == NULL) 191 | { 192 | tut1_error_set_errno(&retval, errno); 193 | goto exit_failed; 194 | } 195 | 196 | /* 197 | * To get a queue, you can simply call vkGetDeviceQueue, asking for the family index and queue index 198 | * of the queue you are interested from the device. The number of queues available for a family is 199 | * retrieved in Tutorial 1 in VkQueueFamilyProperties, which we used in the beginning of this function 200 | * to set VkDeviceQueueCreateInfo's queueCount. 201 | */ 202 | for (uint32_t j = 0; j < queue_info[i].queueCount; ++j) 203 | vkGetDeviceQueue(dev->device, queue_info[i].queueFamilyIndex, j, &cmd->queues[j]); 204 | cmd->queue_count = queue_info[i].queueCount; 205 | 206 | /* 207 | * To allocate command buffers, we can do take them in bulk from the pool. Here, a vkAllocate* 208 | * function is used instead of the usual vkCreate* functions we have seen so far. However, the usage 209 | * of the function is quite similar. There is a VkCommandBufferAllocateInfo struct that defines how 210 | * the allocation needs to take place, such as which pool to take from and how many command buffers to 211 | * allocate. 212 | * 213 | * The command buffers can be primary or secondary. A primary command buffer is one that can be 214 | * submitted to a queue for execution, while a secondary command buffer is one that can be invoked by 215 | * a primary command buffer, like a subroutine. For now, we will only consider primary command 216 | * buffers. 217 | */ 218 | VkCommandBufferAllocateInfo buffer_info = { 219 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 220 | .commandPool = cmd->pool, 221 | .commandBufferCount = queue_info[i].queueCount, 222 | }; 223 | 224 | /* 225 | * vkAllocateCommandBuffers would do the actual allocation of the command buffers. Its usage is 226 | * similar to the vkCreate* functions we have so far seen in Vulkan. Notable difference is the lack 227 | * of memory allocation functions. This is because the command buffer pool that the allocation is 228 | * being made from already took these callbacks! 229 | */ 230 | res = vkAllocateCommandBuffers(dev->device, &buffer_info, cmd->buffers); 231 | tut1_error_set_vkresult(&retval, res); 232 | if (res) 233 | goto exit_failed; 234 | 235 | cmd->buffer_count = queue_info[i].queueCount; 236 | } 237 | 238 | exit_failed: 239 | return retval; 240 | } 241 | 242 | void tut2_cleanup(struct tut2_device *dev) 243 | { 244 | /* 245 | * Before destroying a device, we must make sure that there is no ongoing work. vkDeviceWaitIdle, as the name 246 | * suggests, blocks until the device is idle. 247 | */ 248 | vkDeviceWaitIdle(dev->device); 249 | 250 | /* 251 | * Before cleaning up a device, any allocation made with the device needs to be freed to prevent memory leaks. 252 | * In this tutorial, we have created command pools, and therefore we need to destroy them. Any command buffer 253 | * allocated from the pool is implicitly freed, so we don't need to take explicit actions for that. 254 | * 255 | * The optional memory allocation callbacks are unused because they were unused when creating the pool. 256 | */ 257 | for (uint32_t i = 0; i < dev->command_pool_count; ++i) 258 | { 259 | free(dev->command_pools[i].queues); 260 | free(dev->command_pools[i].buffers); 261 | vkDestroyCommandPool(dev->device, dev->command_pools[i].pool, NULL); 262 | } 263 | free(dev->command_pools); 264 | 265 | /* 266 | * The device can now be destroyed. As common with other vkDestroy* functions, vkDestroyDevice takes the 267 | * device to destroy and the memory allocation callbacks, which are unused. The allocated queues are 268 | * automatically freed as the device is destroyed. 269 | */ 270 | vkDestroyDevice(dev->device, NULL); 271 | 272 | *dev = (struct tut2_device){0}; 273 | } 274 | -------------------------------------------------------------------------------- /tut2/tut2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT2_H 21 | #define TUT2_H 22 | 23 | #include "../tut1/tut1.h" 24 | 25 | struct tut2_commands 26 | { 27 | VkQueueFlags qflags; 28 | 29 | VkCommandPool pool; 30 | VkQueue *queues; 31 | uint32_t queue_count; 32 | VkCommandBuffer *buffers; 33 | uint32_t buffer_count; 34 | }; 35 | 36 | struct tut2_device 37 | { 38 | VkDevice device; 39 | struct tut2_commands *command_pools; 40 | uint32_t command_pool_count; 41 | }; 42 | 43 | tut1_error tut2_get_dev(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 44 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count); 45 | tut1_error tut2_get_commands(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkDeviceQueueCreateInfo queue_info[], uint32_t queue_info_count); 46 | 47 | static inline tut1_error tut2_setup(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags) 48 | { 49 | VkDeviceQueueCreateInfo queue_info[phy_dev->queue_family_count]; 50 | uint32_t queue_info_count = phy_dev->queue_family_count; 51 | 52 | tut1_error res = tut2_get_dev(phy_dev, dev, qflags, queue_info, &queue_info_count); 53 | if (tut1_error_is_success(&res)) 54 | res = tut2_get_commands(phy_dev, dev, queue_info, queue_info_count); 55 | return res; 56 | } 57 | void tut2_cleanup(struct tut2_device *dev); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /tut3/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "tut3.h" 23 | 24 | #define MAX_DEVICES 2 25 | 26 | int main(int argc, char **argv) 27 | { 28 | tut1_error res; 29 | int retval = EXIT_FAILURE; 30 | VkInstance vk; 31 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 32 | struct tut2_device devs[MAX_DEVICES]; 33 | uint32_t dev_count = MAX_DEVICES; 34 | VkShaderModule shaders[MAX_DEVICES] = {NULL}; 35 | struct tut3_pipelines pipelines[MAX_DEVICES]; 36 | 37 | if (argc < 2) 38 | { 39 | printf("Usage: %s shader_file\n\n", argv[0]); 40 | return EXIT_FAILURE; 41 | } 42 | 43 | /* Fire up Vulkan */ 44 | res = tut1_init(&vk); 45 | if (!tut1_error_is_success(&res)) 46 | { 47 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 48 | goto exit_bad_init; 49 | } 50 | 51 | /* Enumerate devices */ 52 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 53 | if (tut1_error_is_error(&res)) 54 | { 55 | tut1_error_printf(&res, "Could not enumerate devices\n"); 56 | goto exit_bad_enumerate; 57 | } 58 | 59 | /* Set up devices */ 60 | for (uint32_t i = 0; i < dev_count; ++i) 61 | { 62 | res = tut2_setup(&phy_devs[i], &devs[i], VK_QUEUE_COMPUTE_BIT); 63 | if (!tut1_error_is_success(&res)) 64 | { 65 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 66 | goto exit_bad_setup; 67 | } 68 | } 69 | 70 | /* Load our compute shader */ 71 | for (uint32_t i = 0; i < dev_count; ++i) 72 | { 73 | res = tut3_load_shader(&devs[i], argv[1], &shaders[i]); 74 | if (!tut1_error_is_success(&res)) 75 | { 76 | tut1_error_printf(&res, "Could not load shader on device %u\n", i); 77 | goto exit_bad_shader; 78 | } 79 | } 80 | 81 | printf("Loaded the shader, awesome!\n"); 82 | 83 | /* 84 | * Create the pipelines. There are as many pipelines created as command buffers (just for example). If 85 | * there are not actually enough resources for them, as many as possible are created. 86 | */ 87 | for (uint32_t i = 0; i < dev_count; ++i) 88 | tut3_make_compute_pipeline(&devs[i], &pipelines[i], shaders[i]); 89 | 90 | /* 91 | * Like tutorial 2, we have covered a lot of ground in this tutorial. Let's keep actual usage of our compute 92 | * shader to the next tutorial, where we would see the effect of multiple threads on the processing speed. 93 | */ 94 | for (uint32_t i = 0; i < dev_count; ++i) 95 | { 96 | uint32_t count = 0; 97 | for (uint32_t j = 0; j < pipelines[i].pipeline_count; ++j) 98 | if (pipelines[i].pipelines[j].pipeline) 99 | ++count; 100 | 101 | printf("Created %u pipeline%s on device %u\n", count, count == 1?"":"s", i); 102 | } 103 | 104 | retval = 0; 105 | 106 | /* Cleanup after yourself */ 107 | 108 | for (uint32_t i = 0; i < dev_count; ++i) 109 | tut3_destroy_pipeline(&devs[i], &pipelines[i]); 110 | 111 | exit_bad_shader: 112 | for (uint32_t i = 0; i < dev_count; ++i) 113 | tut3_free_shader(&devs[i], shaders[i]); 114 | 115 | exit_bad_setup: 116 | for (uint32_t i = 0; i < dev_count; ++i) 117 | tut2_cleanup(&devs[i]); 118 | 119 | exit_bad_enumerate: 120 | tut1_exit(vk); 121 | 122 | exit_bad_init: 123 | return retval; 124 | } 125 | -------------------------------------------------------------------------------- /tut3/tut3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "tut3.h" 24 | 25 | tut1_error tut3_load_shader(struct tut2_device *dev, const char *spirv_file, VkShaderModule *shader) 26 | { 27 | /* 28 | * Allocating a shader is easy. Similar to other vkCreate* functions, a CreateInfo structure is taken that 29 | * describes the shader itself. A set of memory allocator callbacks could be given which we don't use for now. 30 | * The VkShaderModuleCreateInfo struct, besides the usual attributes (such as sType or flags), simply takes the 31 | * SPIR-V code of the shader. 32 | */ 33 | tut1_error retval = TUT1_ERROR_NONE; 34 | VkResult res; 35 | void *code = NULL; 36 | size_t size = 0, cur = 0; 37 | FILE *fin = fopen(spirv_file, "rb"); 38 | 39 | *shader = NULL; 40 | 41 | if (fin == NULL) 42 | { 43 | tut1_error_set_errno(&retval, errno); 44 | goto exit_no_file; 45 | } 46 | 47 | /* Get the size of the file */ 48 | fseek(fin, 0, SEEK_END); 49 | size = ftell(fin); 50 | fseek(fin, 0, SEEK_SET); 51 | 52 | /* Allocate memory for the code */ 53 | code = malloc(size); 54 | if (code == NULL) 55 | { 56 | tut1_error_set_errno(&retval, errno); 57 | goto exit_no_mem; 58 | } 59 | 60 | /* Read all of the SPIR-V file */ 61 | while (cur < size) 62 | { 63 | size_t read = fread(code + cur, 1, size - cur, fin); 64 | if (read == 0) 65 | { 66 | tut1_error_set_errno(&retval, errno); 67 | goto exit_io_error; 68 | } 69 | cur += read; 70 | } 71 | 72 | /* Finally, create the shader module by specifying its code */ 73 | VkShaderModuleCreateInfo info = { 74 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 75 | .codeSize = size, 76 | .pCode = code, 77 | }; 78 | 79 | res = vkCreateShaderModule(dev->device, &info, NULL, shader); 80 | tut1_error_set_vkresult(&retval, res); 81 | 82 | exit_io_error: 83 | free(code); 84 | exit_no_mem: 85 | fclose(fin); 86 | exit_no_file: 87 | return retval; 88 | } 89 | 90 | void tut3_free_shader(struct tut2_device *dev, VkShaderModule shader) 91 | { 92 | /* 93 | * Destroying a shader is similar to other vkDestroy* functions. The shader object itself is taken as well 94 | * as the device for which it was created for. Since we didn't use custom allocators, we are not providing 95 | * them here either. 96 | */ 97 | vkDestroyShaderModule(dev->device, shader, NULL); 98 | } 99 | 100 | tut1_error tut3_make_compute_pipeline(struct tut2_device *dev, struct tut3_pipelines *pipelines, VkShaderModule shader) 101 | { 102 | /* 103 | * For the command buffers to execute commands, they need to be bound to a pipeline. From OpenGL, you must be 104 | * familiar with what a pipeline is: the elements to draw (points, lines, triangles etc) are sent to the vertex 105 | * shader, possibly tessellation shader, possibly geometric shader, rasterization and finally fragment shader. 106 | * Vulkan pipelines are the same, except they are explicitly defined objects and you can have more than one of 107 | * them. Compute pipelines are simpler than graphics pipelines; data is sent to a compute shader and that's 108 | * it. That's why in this tutorial we focus on the simpler Compute pipelines. Once we are comfortable with 109 | * the way Vulkan handles the objects and how they interact with the pipelines, we will move to graphics. 110 | * 111 | * I recommend taking a look at the Pipelines section of the Vulkan specification, there is a nice graph of 112 | * the graphics and compute pipelines. 113 | * 114 | * To be able to send data to the pipelines, and be able to locate those data from the shaders, a fair bit of 115 | * object layout management needs to be done by the application. First, let's talk about descriptor sets: 116 | * 117 | * A descriptor set is a set of objects (such as buffer or image). Each object in the set is called a binding. 118 | * The descriptor set groups these objects so they can be bound together for efficiency reasons. Each object 119 | * could also be an array. In GLSL, this would look like this: 120 | * 121 | * layout (set=m, binding=n) uniform sampler2D variableName; 122 | * layout (set=r, binding=s) uniform sampler2D variableNameArray[L]; 123 | * 124 | * The above code makes the shader use `variableName` to refer to binding index `n` from descriptor set index 125 | * `m`, and use `variableNameArray` to refer to binding index `s` from descriptor set index `s`, where the 126 | * array elements are in the same order as defined by the application. 127 | * 128 | * The following is an example of a descriptor set *layout*: 129 | * 130 | * set_4: { Buffer, Buffer, Image, Buffer, Image[10], Image } 131 | * 132 | * which (declared to vulkan as the 5th descriptor set layout) says that set index 4 has 6 bindings, where the 133 | * first two are buffers, the third is an image, the fourth is a buffer, the fifth is an array of images of 134 | * size 10, and the last is also an image. 135 | * 136 | * Note that the descriptor set layout declares just the layout! At a later stage, actual data needs to be 137 | * bound to these objects. 138 | * 139 | * --- 140 | * 141 | * In each stage of a pipeline, various descriptor sets can be made accessible. For example, if there are 142 | * three descriptor sets, descriptor sets 0 and 2 could be made available to the vertex shader and descriptor 143 | * sets 1 and 2 to the fragment shader. To define this relationship, a pipeline layout is created. The 144 | * pipeline layout additionally declares the push constants available to each state. Push constants are 145 | * special small values that can be sent to the shaders more efficiently than other methods (such as buffers 146 | * and images). For now, let's ignore push constants. 147 | */ 148 | 149 | tut1_error retval = TUT1_ERROR_NONE; 150 | VkResult res; 151 | uint32_t cmd_buffer_count = 0; 152 | 153 | *pipelines = (struct tut3_pipelines){0}; 154 | 155 | /* Count the total number of command buffers, to create that many pipelines */ 156 | for (uint32_t i = 0; i < dev->command_pool_count; ++i) 157 | cmd_buffer_count += dev->command_pools[i].buffer_count; 158 | 159 | /* Allocate memory for the pipelines */ 160 | pipelines->pipelines = malloc(cmd_buffer_count * sizeof *pipelines->pipelines); 161 | if (pipelines->pipelines == NULL) 162 | { 163 | tut1_error_set_errno(&retval, errno); 164 | goto exit_failed; 165 | } 166 | memset(pipelines->pipelines, 0, cmd_buffer_count * sizeof *pipelines->pipelines); 167 | pipelines->pipeline_count = cmd_buffer_count; 168 | 169 | for (uint32_t i = 0; i < cmd_buffer_count; ++i) 170 | { 171 | VkDescriptorSetLayoutBinding set_layout_binding; 172 | VkDescriptorSetLayoutCreateInfo set_layout_info; 173 | VkPipelineLayoutCreateInfo pipeline_layout_info; 174 | VkComputePipelineCreateInfo pipeline_info; 175 | 176 | struct tut3_pipeline *pl = &pipelines->pipelines[i]; 177 | 178 | /* 179 | * For our compute shader, we are going to choose a simple layout; a single binding that is a buffer. 180 | * Creating the descriptor set layout is similar to all other vkCreate* functions. 181 | * 182 | * The CreateInfo of the descriptor set layout simply takes the bindings in the set as an array. We 183 | * have only one of it. The binding description itself consists of several fields. The binding index 184 | * is the index the shader would use to refer to this object. In the GLSL example above, `binding=n` 185 | * matches the object with `binding` index set to `n`. The descriptor type is the type of the object, 186 | * which could be a buffer, an image, texel buffer, etc. The descriptor count is the size of the 187 | * array, if the object is an array. A size of 1 means that it's not an array. Finally, the binding 188 | * description tells which pipeline stages the object can be used in. In our case, we want to use the 189 | * object in the compute stage (our only stage!). 190 | */ 191 | set_layout_binding = (VkDescriptorSetLayoutBinding){ 192 | .binding = 0, 193 | .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 194 | .descriptorCount = 1, 195 | .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, 196 | }; 197 | 198 | set_layout_info = (VkDescriptorSetLayoutCreateInfo){ 199 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 200 | .bindingCount = 1, 201 | .pBindings = &set_layout_binding, 202 | }; 203 | 204 | res = vkCreateDescriptorSetLayout(dev->device, &set_layout_info, NULL, &pl->set_layout); 205 | tut1_error_set_vkresult(&retval, res); 206 | if (res) 207 | goto exit_failed; 208 | 209 | /* 210 | * To create a pipeline layout, we need to know the descriptor set layouts and push constant ranges 211 | * used within the pipelines. The single descriptor set layout we are going to use is already created 212 | * and we are going to ignore push constants, so this is quite simple. 213 | */ 214 | pipeline_layout_info = (VkPipelineLayoutCreateInfo){ 215 | .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 216 | .setLayoutCount = 1, 217 | .pSetLayouts = &pl->set_layout, 218 | }; 219 | 220 | res = vkCreatePipelineLayout(dev->device, &pipeline_layout_info, NULL, &pl->pipeline_layout); 221 | tut1_error_set_vkresult(&retval, res); 222 | if (res) 223 | goto exit_failed; 224 | 225 | /* 226 | * Pipelines can be created more than one at a time. In this tutorial, we will create one pipeline for 227 | * command buffer (created in tutorial 2). Creating the pipelines themselves is similar to all other 228 | * vkCreate* functions, but with the pipelines and the CreateInfo struct possibly an array. As always, 229 | * we don't use the allocator callbacks for now. There is one more argument to vkCreate*Pipeline 230 | * though, and that's a pipeline cache. 231 | * 232 | * Pipeline caches can be used to store a pipeline to file, and later on retrieve the pipeline instead 233 | * of building one anew. This is purely for performance, and we will ignore it for now. 234 | * 235 | * In this tutorial, we will create the pipeline objects one by one for simplicity. 236 | * 237 | * The CreateInfo of the pipeline takes a set of flags. One of the flags disallows optimization of the 238 | * pipeline, for faster creation. We don't care about that. Another flag says that other pipelines 239 | * can be derived from this one, again as optimization, where the pipelines are very similar. The 240 | * other flag indicates that this pipeline is derived from another pipeline. We won't be using 241 | * pipeline derivation for now either. 242 | * 243 | * The CreateInfo also takes the shader itself. Since we are creating a compute shader, there is only 244 | * one shader stage; a compute shader. Let's assume the entry point of this shader is called "main". 245 | * 246 | * The CreateInfo gets the pipeline layout as well, which we have already created above. 247 | */ 248 | 249 | pipeline_info = (VkComputePipelineCreateInfo){ 250 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, 251 | .stage = { 252 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 253 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, 254 | .module = shader, 255 | .pName = "main", 256 | }, 257 | .layout = pl->pipeline_layout, 258 | }; 259 | 260 | res = vkCreateComputePipelines(dev->device, NULL, 1, &pipeline_info, NULL, &pl->pipeline); 261 | tut1_error_set_vkresult(&retval, res); 262 | if (res) 263 | goto exit_failed; 264 | } 265 | 266 | exit_failed: 267 | return retval; 268 | } 269 | 270 | void tut3_destroy_pipeline(struct tut2_device *dev, struct tut3_pipelines *pipelines) 271 | { 272 | vkDeviceWaitIdle(dev->device); 273 | 274 | for (uint32_t i = 0; i < pipelines->pipeline_count; ++i) 275 | { 276 | struct tut3_pipeline *pl = &pipelines->pipelines[i]; 277 | 278 | /* 279 | * Destroying the pipeline, pipeline layout and descriptor set layout is similar to other vkDestroy* 280 | * functions we have seen so far. The allocator callbacks are not used since they were not provided 281 | * when the objects were created. 282 | */ 283 | vkDestroyPipeline(dev->device, pl->pipeline, NULL); 284 | vkDestroyPipelineLayout(dev->device, pl->pipeline_layout, NULL); 285 | vkDestroyDescriptorSetLayout(dev->device, pl->set_layout, NULL); 286 | } 287 | 288 | free(pipelines->pipelines); 289 | 290 | *pipelines = (struct tut3_pipelines){0}; 291 | } 292 | -------------------------------------------------------------------------------- /tut3/tut3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT3_H 21 | #define TUT3_H 22 | 23 | #include "../tut2/tut2.h" 24 | 25 | struct tut3_pipeline 26 | { 27 | VkDescriptorSetLayout set_layout; 28 | VkPipelineLayout pipeline_layout; 29 | VkPipeline pipeline; 30 | }; 31 | 32 | struct tut3_pipelines 33 | { 34 | struct tut3_pipeline *pipelines; 35 | uint32_t pipeline_count; 36 | }; 37 | 38 | tut1_error tut3_load_shader(struct tut2_device *dev, const char *spirv_file, VkShaderModule *shader); 39 | void tut3_free_shader(struct tut2_device *dev, VkShaderModule shader); 40 | 41 | tut1_error tut3_make_compute_pipeline(struct tut2_device *dev, struct tut3_pipelines *pipeline, VkShaderModule shader); 42 | void tut3_destroy_pipeline(struct tut2_device *dev, struct tut3_pipelines *pipelines); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /tut4/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "tut4.h" 24 | 25 | #define MAX_DEVICES 2 26 | 27 | int main(int argc, char **argv) 28 | { 29 | tut1_error res; 30 | int retval = EXIT_FAILURE; 31 | VkInstance vk; 32 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 33 | struct tut2_device devs[MAX_DEVICES]; 34 | uint32_t dev_count = MAX_DEVICES; 35 | VkShaderModule shaders[MAX_DEVICES] = {NULL}; 36 | struct tut3_pipelines pipelines[MAX_DEVICES]; 37 | struct tut4_data test_data[MAX_DEVICES]; 38 | int success = 0; 39 | 40 | /* How many threads to do the work on */ 41 | size_t thread_count = 8; 42 | /* Whether the threads should take some CPU time as well */ 43 | bool busy_threads = false; 44 | /* Default to 1MB of buffer data to work on */ 45 | size_t buffer_size = 1024 * 1024 / sizeof(float); 46 | 47 | bool bad_args = false; 48 | if (argc < 2) 49 | bad_args = true; 50 | if (argc > 2 && sscanf(argv[2], "%zu", &thread_count) != 1) 51 | bad_args = true; 52 | if (argc > 3) 53 | { 54 | int temp; 55 | if (sscanf(argv[3], "%d", &temp) != 1) 56 | bad_args = true; 57 | else 58 | busy_threads = temp; 59 | } 60 | if (argc > 4) 61 | { 62 | if (sscanf(argv[4], "%zu", &buffer_size) != 1) 63 | bad_args = true; 64 | else 65 | buffer_size /= sizeof(float); 66 | } 67 | 68 | if (bad_args) 69 | { 70 | printf("Usage: %s shader_file [thread_count(8) [busy_threads(0) [buffer_size(1MB)]]]\n\n", argv[0]); 71 | return EXIT_FAILURE; 72 | } 73 | 74 | /* Fire up Vulkan */ 75 | res = tut1_init(&vk); 76 | if (!tut1_error_is_success(&res)) 77 | { 78 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 79 | goto exit_bad_init; 80 | } 81 | 82 | /* Enumerate devices */ 83 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 84 | if (tut1_error_is_error(&res)) 85 | { 86 | tut1_error_printf(&res, "Could not enumerate devices\n"); 87 | goto exit_bad_enumerate; 88 | } 89 | 90 | /* Set up devices */ 91 | for (uint32_t i = 0; i < dev_count; ++i) 92 | { 93 | res = tut2_setup(&phy_devs[i], &devs[i], VK_QUEUE_COMPUTE_BIT); 94 | if (!tut1_error_is_success(&res)) 95 | { 96 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 97 | goto exit_bad_setup; 98 | } 99 | } 100 | 101 | /* Load our compute shader */ 102 | for (uint32_t i = 0; i < dev_count; ++i) 103 | { 104 | res = tut3_load_shader(&devs[i], argv[1], &shaders[i]); 105 | if (!tut1_error_is_success(&res)) 106 | { 107 | tut1_error_printf(&res, "Could not load shader on device %u\n", i); 108 | goto exit_bad_shader; 109 | } 110 | } 111 | 112 | /* 113 | * Create the pipelines. There are as many pipelines created as command buffers (just for example). If 114 | * there are not actually enough resources for them, as many as possible are created. In this test, we are 115 | * not going to handle the case where some pipelines are not created. 116 | */ 117 | for (uint32_t i = 0; i < dev_count; ++i) 118 | { 119 | res = tut3_make_compute_pipeline(&devs[i], &pipelines[i], shaders[i]); 120 | if (!tut1_error_is_success(&res)) 121 | { 122 | tut1_error_printf(&res, "Could not allocate enough pipelines on device %u\n", i); 123 | goto exit_bad_pipeline; 124 | } 125 | } 126 | 127 | /* 128 | * Prepare our test. Both the buffers and threads are divided near-equally among the physical devices, which 129 | * are likely to be just 1 in your case, but who knows. 130 | */ 131 | for (uint32_t i = 0; i < dev_count; ++i) 132 | { 133 | size_t this_buffer_size = buffer_size / dev_count; 134 | size_t this_thread_count = thread_count / dev_count; 135 | 136 | /* Make sure the last device gets all the left-over */ 137 | if (i == dev_count - 1) 138 | { 139 | this_buffer_size = buffer_size - buffer_size / dev_count * (dev_count - 1); 140 | this_thread_count = thread_count - thread_count / dev_count * (dev_count - 1); 141 | } 142 | 143 | res = tut4_prepare_test(&phy_devs[i], &devs[i], &pipelines[i], &test_data[i], this_buffer_size, this_thread_count); 144 | if (!tut1_error_is_success(&res)) 145 | { 146 | tut1_error_printf(&res, "Could not allocate resources on device %u\n", i); 147 | goto exit_bad_test_prepare; 148 | } 149 | } 150 | 151 | /* 152 | * Ok, this was a LOT of initializing! But we are finally ready to run something. tut4_start_test() creates 153 | * a test thread for us, which further spawns the corresponding device's thread_count threads that do the 154 | * calculations. We then wait for the tests to finish with tut4_wait_test_end(). 155 | */ 156 | for (uint32_t i = 0; i < dev_count; ++i) 157 | { 158 | if (tut4_start_test(&test_data[i], busy_threads)) 159 | { 160 | printf("Could not start the test threads for device %u\n", i); 161 | perror("Error"); 162 | } 163 | } 164 | 165 | printf("Running the tests...\n"); 166 | 167 | for (uint32_t i = 0; i < dev_count; ++i) 168 | tut4_wait_test_end(&test_data[i]); 169 | 170 | success = 1; 171 | for (uint32_t i = 0; i < dev_count; ++i) 172 | if (!test_data[i].success) 173 | { 174 | if (!tut1_error_is_success(&test_data[i].error)) 175 | tut1_error_printf(&test_data[i].error, "Error starting test on device %u\n", i); 176 | else 177 | printf("The test didn't produce expected results (device %u)\n", i); 178 | success = 0; 179 | } 180 | 181 | if (success) 182 | printf("Everything went well :) We just wasted your GPU doing something stupid\n"); 183 | 184 | /* 185 | * You can time the execution of the program with time(1): 186 | * 187 | * $ time ./tut4/tut4 shaders/tut3.comp.spv ... 188 | * 189 | * Then try to play with different number of threads and see if the total execution time of the application 190 | * changes and how! 191 | * 192 | * ... 193 | * 194 | * Did you try that? Already? Well, that was disappointing. More threads probably resulted in higher 195 | * execution time, right? That actually makes sense. You see, we have N data to compute, and whether you tell 196 | * the GPU to do N computations from one thread, or N/T computations each from T threads, you aren't actually 197 | * doing any less computation. You probably just have more overhead from the threads. 198 | * 199 | * So what's the deal with multi-threaded and Vulkan? Well, the problem is that this test was heavily 200 | * GPU-bound, and as you have noticed, multi-CPU-threaded doesn't help. For this reason, this test has a 201 | * little feature to "fake" some execution on the CPU threads as well. If you run the program like this: 202 | * 203 | * $ time ./tut4/tut4 shaders/tut3.comp.spv ... 204 | * 205 | * where can be either 0 (no CPU usage) or 1 (some fake CPU usage), and then experiment with different 206 | * number of threads, you can see the benefit of multi-threading. In this case, while the GPU is working, the 207 | * CPU thread spends time fake-doing something. If there is only one thread, the CPU cannot keep the GPU 208 | * constantly busy, so the computation slows down. On the other hand, with multiple threads, the same amount 209 | * of CPU work is spread out and done in parallel, so the threads together can feed the GPU with instructions 210 | * faster. 211 | * 212 | * In this test, the total amount of time to waste is 3.2 seconds (32ms for each "render" operation, and there 213 | * are a hundred of them). Depending on your GPU, you may notice that above a certain number of threads, there 214 | * is no more any speedup. That is when the amount of time spent in each CPU thread becomes less than the time 215 | * spent in the GPU for that thread's task, so whether the CPU spent time doing something before waiting for 216 | * the GPU doesn't make a difference in the execution time. 217 | */ 218 | 219 | retval = 0; 220 | 221 | /* Cleanup after yourself */ 222 | 223 | exit_bad_test_prepare: 224 | for (uint32_t i = 0; i < dev_count; ++i) 225 | tut4_free_test(&devs[i], &test_data[i]); 226 | 227 | exit_bad_pipeline: 228 | for (uint32_t i = 0; i < dev_count; ++i) 229 | tut3_destroy_pipeline(&devs[i], &pipelines[i]); 230 | 231 | exit_bad_shader: 232 | for (uint32_t i = 0; i < dev_count; ++i) 233 | tut3_free_shader(&devs[i], shaders[i]); 234 | 235 | exit_bad_setup: 236 | for (uint32_t i = 0; i < dev_count; ++i) 237 | tut2_cleanup(&devs[i]); 238 | 239 | exit_bad_enumerate: 240 | tut1_exit(vk); 241 | 242 | exit_bad_init: 243 | return retval; 244 | } 245 | -------------------------------------------------------------------------------- /tut4/tut4.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT4_H 21 | #define TUT4_H 22 | 23 | #include 24 | #include "../tut3/tut3.h" 25 | 26 | struct tut4_per_cmd_buffer_data 27 | { 28 | VkBufferView buffer_view; 29 | VkDescriptorSet set; 30 | VkFence fence; 31 | 32 | pthread_mutex_t *cmd_pool_mutex; 33 | 34 | size_t start_index, end_index; 35 | 36 | /* worker thread data */ 37 | VkDevice device; 38 | VkQueue queue; 39 | VkCommandBuffer cmd_buffer; 40 | VkPipeline pipeline; 41 | VkPipelineLayout pipeline_layout; 42 | uint64_t busy_time_ns; 43 | 44 | int success; 45 | tut1_error error; 46 | }; 47 | 48 | struct tut4_data 49 | { 50 | VkBuffer buffer; 51 | VkDeviceMemory buffer_mem; 52 | VkDescriptorPool set_pool; 53 | size_t buffer_size; 54 | 55 | pthread_mutex_t cmd_pool_mutex; 56 | 57 | struct tut4_per_cmd_buffer_data *per_cmd_buffer; 58 | uint32_t per_cmd_buffer_count; 59 | 60 | /* test thread data */ 61 | struct tut2_device *dev; 62 | struct tut3_pipelines *pipelines; 63 | bool busy_threads; 64 | pthread_t test_thread; 65 | 66 | int success; 67 | tut1_error error; 68 | }; 69 | 70 | tut1_error tut4_prepare_test(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut3_pipelines *pipelines, 71 | struct tut4_data *test_data, size_t buffer_size, size_t thread_count); 72 | void tut4_free_test(struct tut2_device *dev, struct tut4_data *test_data); 73 | 74 | uint32_t tut4_find_suitable_memory(struct tut1_physical_device *phy_dev, struct tut2_device *dev, 75 | VkMemoryRequirements *mem_req, VkMemoryPropertyFlags properties); 76 | 77 | int tut4_start_test(struct tut4_data *test_data, bool busy_threads); 78 | void tut4_wait_test_end(struct tut4_data *test_data); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /tut5/README.md: -------------------------------------------------------------------------------- 1 | Vulkan So Far 2 | ============= 3 | 4 | In these 5 tutorials, we have seen how Vulkan basically works. There was no 5 | graphics involved, and no distractions such as window management. Still, there 6 | was a lot to cover! Tutorial 5 is rather basic and shows how to enable layers 7 | and extensions. As usual, see the source code for that. 8 | 9 | So far, we have seen a handful of entities in Vulkan and their relationship. 10 | This is summarized in the Figure below. 11 | 12 | ![Vulkan objects and their relationships](vulkan-objects.svg). 13 | 14 | In summary, there are physical devices that Vulkan can enumerate, for example 15 | your two graphics cards. Each physical device has certain properties, features 16 | and capabilities that Vulkan can tell you about. The device performs actions 17 | from queues, and each queue family supports a set of operations, such as 18 | graphics or computation. 19 | 20 | Vulkan needs to be instanced, which allows it to not have any global states. 21 | From this instance, you can create a logical device for each physical device if 22 | you so choose to. All operations go through these logical devices. There are 23 | four main entities that need to be defined before any action could be taken. 24 | 25 | - Command Buffers 26 | - Descriptor Sets 27 | - Pipelines 28 | - Synchronization Primitives 29 | 30 | Command buffers, allocated from command pools, are used to record graphics or 31 | compute operations on descriptor sets it is bound to using the pipeline it is 32 | bound to. The recorded sequence of commands is eventually sent to a device 33 | queue for execution. 34 | 35 | Descriptor sets, allocated from descriptor pools, are instantiated according to 36 | a defined layout and contain the actual data that is sent to the GPU, in the 37 | form of images, buffers etc. 38 | 39 | Pipelines are instantiated according to a defined pipeline layout as well, 40 | which itself needs to know about the layout of descriptor sets that would be 41 | used within the pipeline. While descriptor sets carry data to the GPU, 42 | pipelines carry programs aka shaders. 43 | 44 | Synchronization primitives make sure memory accesses and execution between CPU 45 | and GPU or within GPU are, well, synchronized. Semaphores, barriers, events 46 | and fences each help solve a different synchronization issue. These primitives 47 | are used when recording to a command buffer and submitting it to a queue for 48 | execution. 49 | -------------------------------------------------------------------------------- /tut5/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "tut5.h" 23 | 24 | #define MAX_DEVICES 2 25 | 26 | int main(int argc, char **argv) 27 | { 28 | tut1_error res; 29 | int retval = EXIT_FAILURE; 30 | VkInstance vk; 31 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 32 | struct tut2_device devs[MAX_DEVICES]; 33 | uint32_t dev_count = MAX_DEVICES; 34 | 35 | /* Fire up Vulkan */ 36 | res = tut5_init(&vk); 37 | if (!tut1_error_is_success(&res)) 38 | { 39 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 40 | goto exit_bad_init; 41 | } 42 | 43 | /* Enumerate devices */ 44 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 45 | if (tut1_error_is_error(&res)) 46 | { 47 | tut1_error_printf(&res, "Could not enumerate devices\n"); 48 | goto exit_bad_enumerate; 49 | } 50 | 51 | /* 52 | * This step is similar to Tutorial 2, except that instead of tut2_setup we use tut5_setup. The difference is 53 | * that tut5_setup also enables layers and extensions. From this Tutorial on, we will always use tut5_setup 54 | * instead of tut2_setup. 55 | */ 56 | for (uint32_t i = 0; i < dev_count; ++i) 57 | { 58 | res = tut5_setup(&phy_devs[i], &devs[i], VK_QUEUE_COMPUTE_BIT); 59 | if (tut1_error_is_error(&res)) 60 | { 61 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 62 | goto exit_bad_setup; 63 | } 64 | } 65 | 66 | /* Let's print layers and extensions that we detected and are enabled */ 67 | tut5_print_layers_and_extensions(); 68 | for (uint32_t i = 0; i < dev_count; ++i) 69 | { 70 | printf("\n"); 71 | tut5_print_device_layers_and_extensions(&phy_devs[i]); 72 | } 73 | 74 | /* 75 | * While mass-enabling all layers and extensions has been fun, it's not really what you should be doing. 76 | * Neither during your own development, and especially nor when releasing your software/game. Instead, you 77 | * normally look for a specific set of layers or extensions that serve your need. 78 | * 79 | * For example, VK_LAYER_LUNARG_parameter_validation is useful to make sure you got all the parameters to 80 | * Vulkan functions correctly. There are many useful layers already and you can just list them in an array 81 | * and provide that to Vulkan, assuming you can tolerate some of them not existing on another computer (if you 82 | * release the software). 83 | * 84 | * Vulkan has a nice way of setting itself up. Normally, when you use Vulkan functions, they go through a 85 | * Vulkan "loader" that call the actual Vulkan functions. You could actually retrieve those functions yourself 86 | * and circumvent the loader, but the loader is nice! One nice thing it does for you is to manage layers and 87 | * extensions. What's even nicer is that it can be affected by environment variables. 88 | * 89 | * Before Tutorial 5, we hadn't had any layers enabled. What if we want to enable the parameter_validation 90 | * layer to see if there were any mistakes in previous Tutorials? One way is to change their code to use 91 | * `tut5_setup()` instead of `tut2_setup()` and get all layers and extensions enabled. Another way is to use 92 | * environment variables to have the loader enable any layers we want, without rebuilding the code. The nice 93 | * thing about this is that you actually never need to think about layers in the code itself. You can do 94 | * without them, and during testing just enable those layers you need from the outside. This is not true (as 95 | * far as I can tell) about extensions; likely because the code needs to know what extensions are enabled to 96 | * use them, so it must enable them itself (if I'm wrong, please correct me). 97 | * 98 | * To enable layers using environment variables, you can do the following: 99 | * 100 | * $ export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_parameter_validation 101 | * $ export VK_DEVICE_LAYERS=VK_LAYER_LUNARG_parameter_validation 102 | * 103 | * and then run your program. Needless to say, the VK_INSTANCE_LAYERS enables layers that apply to the Vulkan 104 | * instance and VK_DEVICE_LAYERS enables those that are per-device. The variables can actually take a list of 105 | * layers that are separated by : (or ; on windows, because windows). 106 | * 107 | * You can go ahead and set the above environment variables and run any of the previous tutorials. I found a 108 | * bug in Tutorial 2 with it actually myself, which resulted in this: 109 | * 110 | * https://github.com/LunarG/VulkanTools/issues/16 111 | * 112 | * Feel free to experiment with other layers as well. There are very useful layers available, which I will not 113 | * get into (at least not any time soon), but I'm sure you can benefit from. 114 | * 115 | * Notes: 116 | * 117 | * - VK_LAYER_LUNARG_parameter_validation was called VK_LAYER_LUNARG_param_checker in the past. You can use 118 | * `vulkaninfo` to see which name you have in your version (or run this program!). 119 | * 120 | * - Device layers are deprecated in Vulkan 1.0.17, so after this version you don't need to set 121 | * VK_DEVICE_LAYERS at all. 122 | * 123 | * - If you don't see any of the layers installed through the Vulkan SDK, you would need to set VK_LAYER_PATH 124 | * to where the layer manifest files are. For example: 125 | * 126 | * $ export VK_LAYER_PATH=/etc/vulkan/explicit_layer.d/ 127 | * 128 | * - There is a layer named VK_LAYER_LUNARG_standard_validation which bundles many validation layers together. 129 | * It's very nice. 130 | */ 131 | 132 | retval = 0; 133 | 134 | /* Cleanup after yourself */ 135 | 136 | exit_bad_setup: 137 | for (uint32_t i = 0; i < dev_count; ++i) 138 | tut2_cleanup(&devs[i]); 139 | 140 | exit_bad_enumerate: 141 | tut1_exit(vk); 142 | 143 | exit_bad_init: 144 | return retval; 145 | } 146 | -------------------------------------------------------------------------------- /tut5/tut5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "tut5.h" 23 | 24 | #define TUT5_MAX_LAYER_COUNT 20 25 | #define TUT5_MAX_EXTENSION_COUNT 20 26 | 27 | static tut1_error get_layers_and_extensions(VkLayerProperties layers[TUT5_MAX_LAYER_COUNT], uint32_t *layer_count, 28 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT], uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]) 29 | { 30 | tut1_error retval = TUT1_ERROR_NONE; 31 | VkResult res; 32 | 33 | /* 34 | * To get a list of layers, vkEnumerateInstanceLayerProperties can be simply called. If there are more layers 35 | * than our array can hold, VK_INCOMPLETE is returned, in which case we don't really mind and we'll enable as 36 | * many layers as possible. 37 | */ 38 | res = vkEnumerateInstanceLayerProperties(layer_count, layers); 39 | tut1_error_set_vkresult(&retval, res); 40 | if (res < 0) 41 | goto exit_failed; 42 | 43 | /* 44 | * Extensions are either independent, or are based on a layer. Therefore, we will enumerate the extensions 45 | * once given no layer name, and once per layer name. 46 | */ 47 | extensions_count[0] = TUT5_MAX_EXTENSION_COUNT; 48 | res = vkEnumerateInstanceExtensionProperties(NULL, &extensions_count[0], extensions[0]); 49 | tut1_error_set_vkresult(&retval, res); 50 | if (res < 0) 51 | goto exit_failed; 52 | for (uint32_t i = 0; i < *layer_count; ++i) 53 | { 54 | extensions_count[i + 1] = TUT5_MAX_EXTENSION_COUNT; 55 | res = vkEnumerateInstanceExtensionProperties(layers[i].layerName, &extensions_count[i + 1], extensions[i + 1]); 56 | tut1_error_set_vkresult(&retval, res); 57 | if (res < 0) 58 | goto exit_failed; 59 | } 60 | 61 | exit_failed: 62 | return retval; 63 | } 64 | 65 | static tut1_error get_device_layers_and_extensions(VkPhysicalDevice phy_dev, VkLayerProperties layers[TUT5_MAX_LAYER_COUNT], uint32_t *layer_count, 66 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT], uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]) 67 | { 68 | tut1_error retval = TUT1_ERROR_NONE; 69 | VkResult res; 70 | 71 | /* 72 | * Enumerating layers and extensions that are specific to a device is very similar to enumerating those that 73 | * are generic to a Vulkan instance. The only difference is to use Device instead of Instance name, and give 74 | * the physical device as the first argument. 75 | * 76 | * Note: Vulkan 1.0.17 deprecates device layers. For now, the API is kept for backwards-compatibility, and we 77 | * query them anyway, in case you have an older version installed. 78 | */ 79 | 80 | res = vkEnumerateDeviceLayerProperties(phy_dev, layer_count, layers); 81 | tut1_error_set_vkresult(&retval, res); 82 | if (res < 0) 83 | goto exit_failed; 84 | 85 | extensions_count[0] = TUT5_MAX_EXTENSION_COUNT; 86 | res = vkEnumerateDeviceExtensionProperties(phy_dev, NULL, &extensions_count[0], extensions[0]); 87 | tut1_error_set_vkresult(&retval, res); 88 | if (res < 0) 89 | goto exit_failed; 90 | for (uint32_t i = 0; i < *layer_count; ++i) 91 | { 92 | extensions_count[i + 1] = TUT5_MAX_EXTENSION_COUNT; 93 | res = vkEnumerateDeviceExtensionProperties(phy_dev, layers[i].layerName, &extensions_count[i + 1], extensions[i + 1]); 94 | tut1_error_set_vkresult(&retval, res); 95 | if (res < 0) 96 | goto exit_failed; 97 | } 98 | 99 | exit_failed: 100 | return retval; 101 | } 102 | 103 | static void pack_layer_and_extension_names(VkLayerProperties layers[TUT5_MAX_LAYER_COUNT], uint32_t layer_count, 104 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT], uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1], 105 | const char *layer_names[], const char *extension_names[], uint32_t *total_extensions_count) 106 | { 107 | for (uint32_t j = 0; j < extensions_count[0]; ++j) 108 | extension_names[(*total_extensions_count)++] = extensions[0][j].extensionName; 109 | for (uint32_t i = 0; i < layer_count; ++i) 110 | { 111 | layer_names[i] = layers[i].layerName; 112 | for (uint32_t j = 0; j < extensions_count[i + 1]; ++j) 113 | extension_names[(*total_extensions_count)++] = extensions[i + 1][j].extensionName; 114 | } 115 | } 116 | 117 | tut1_error tut5_init(VkInstance *vk) 118 | { 119 | VkLayerProperties layers[TUT5_MAX_LAYER_COUNT]; 120 | uint32_t layer_count = TUT5_MAX_LAYER_COUNT; 121 | 122 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT]; 123 | uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]; 124 | 125 | const char *layer_names[TUT5_MAX_LAYER_COUNT] = {NULL}; 126 | const char *extension_names[(TUT5_MAX_LAYER_COUNT + 1) * TUT5_MAX_EXTENSION_COUNT] = {NULL}; 127 | uint32_t total_extensions_count = 0; 128 | 129 | VkApplicationInfo app_info = { 130 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 131 | .pApplicationName = "Vulkan Tutorial", 132 | .applicationVersion = 0x010000, 133 | .pEngineName = "Vulkan Tutorial", 134 | .engineVersion = 0x010000, 135 | .apiVersion = VK_API_VERSION_1_0, 136 | }; 137 | VkInstanceCreateInfo info; 138 | 139 | tut1_error retval = TUT1_ERROR_NONE; 140 | VkResult res; 141 | 142 | /* 143 | * We have already seen how to create a Vulkan instance in Tutorial 1. In this tutorial, we will enumerate all 144 | * layers and extensions (depending on the layer or not) and enable all of them, why not! 145 | */ 146 | retval = get_layers_and_extensions(layers, &layer_count, extensions, extensions_count); 147 | if (tut1_error_is_error(&retval)) 148 | goto exit_failed; 149 | 150 | /* 151 | * Now that we have the layer and extension information, we need to pack the names together to be given to 152 | * VkInstanceCreateInfo. 153 | * 154 | * In a real-world application, you would likely just use an array of predefined layer and extensions names, 155 | * knowing which layers and extensions you are in fact interested in. 156 | */ 157 | pack_layer_and_extension_names(layers, layer_count, extensions, extensions_count, 158 | layer_names, extension_names, &total_extensions_count); 159 | 160 | /* 161 | * Enabling the layers and extensions is now just a matter of giving these names to the VkInstanceCreateInfo 162 | * struct. In Tutorial 1, we had just given 0 as the layer and extension counts. 163 | * 164 | * Note: enabling all layers causes a crash at the time of this writing! I have put a *0 where the layer count 165 | * is taken for now, but regardless, you now know how to iterate through the available layers. 166 | */ 167 | info = (VkInstanceCreateInfo){ 168 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 169 | .pApplicationInfo = &app_info, 170 | .enabledLayerCount = layer_count * 0, 171 | .ppEnabledLayerNames = layer_names, 172 | .enabledExtensionCount = total_extensions_count, 173 | .ppEnabledExtensionNames = extension_names, 174 | }; 175 | 176 | res = vkCreateInstance(&info, NULL, vk); 177 | tut1_error_set_vkresult(&retval, res); 178 | 179 | exit_failed: 180 | return retval; 181 | } 182 | 183 | tut1_error tut5_get_dev(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 184 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count) 185 | { 186 | VkLayerProperties layers[TUT5_MAX_LAYER_COUNT]; 187 | uint32_t layer_count = TUT5_MAX_LAYER_COUNT; 188 | 189 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT]; 190 | uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]; 191 | 192 | const char *layer_names[TUT5_MAX_LAYER_COUNT] = {NULL}; 193 | const char *extension_names[(TUT5_MAX_LAYER_COUNT + 1) * TUT5_MAX_EXTENSION_COUNT] = {NULL}; 194 | uint32_t total_extensions_count = 0; 195 | 196 | tut1_error retval = TUT1_ERROR_NONE; 197 | VkResult res; 198 | 199 | *dev = (struct tut2_device){0}; 200 | 201 | /* We have already seen how to create a logical device and request queues in Tutorial 2 */ 202 | uint32_t max_queue_count = *queue_info_count; 203 | *queue_info_count = 0; 204 | 205 | uint32_t max_family_queues = 0; 206 | for (uint32_t i = 0; i < phy_dev->queue_family_count; ++i) 207 | if (max_family_queues < phy_dev->queue_families[i].queueCount) 208 | max_family_queues = phy_dev->queue_families[i].queueCount; 209 | float queue_priorities[max_family_queues]; 210 | memset(queue_priorities, 0, sizeof queue_priorities); 211 | 212 | for (uint32_t i = 0; i < phy_dev->queue_family_count && i < max_queue_count; ++i) 213 | { 214 | /* Check if the queue has the desired capabilities. If so, add it to the list of desired queues */ 215 | if ((phy_dev->queue_families[i].queueFlags & qflags) == 0) 216 | continue; 217 | 218 | queue_info[(*queue_info_count)++] = (VkDeviceQueueCreateInfo){ 219 | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 220 | .queueFamilyIndex = i, 221 | .queueCount = phy_dev->queue_families[i].queueCount, 222 | .pQueuePriorities = queue_priorities, 223 | }; 224 | } 225 | 226 | /* If there are no compatible queues, there is little one can do here */ 227 | if (*queue_info_count == 0) 228 | { 229 | tut1_error_set_vkresult(&retval, VK_ERROR_FEATURE_NOT_PRESENT); 230 | goto exit_failed; 231 | } 232 | 233 | /* 234 | * In tut5_init, we enabled all layers and extensions that there are. Here, we will again enable every layer 235 | * and extension available. In reality, you would likely have a predefined array of layer and extension names 236 | * that you are interested in, and therefore you wouldn't need to enumerate anything. 237 | */ 238 | retval = get_device_layers_and_extensions(phy_dev->physical_device, layers, &layer_count, extensions, extensions_count); 239 | if (tut1_error_is_error(&retval)) 240 | goto exit_failed; 241 | 242 | /* Once again, we need to pack the names in a simple array to be given to VkDeviceQueueCreateInfo */ 243 | pack_layer_and_extension_names(layers, layer_count, extensions, extensions_count, 244 | layer_names, extension_names, &total_extensions_count); 245 | 246 | VkDeviceCreateInfo dev_info = { 247 | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 248 | .queueCreateInfoCount = *queue_info_count, 249 | .pQueueCreateInfos = queue_info, 250 | .enabledLayerCount = layer_count * 0, 251 | .ppEnabledLayerNames = layer_names, 252 | .enabledExtensionCount = total_extensions_count, 253 | .ppEnabledExtensionNames = extension_names, 254 | .pEnabledFeatures = &phy_dev->features, 255 | }; 256 | 257 | res = vkCreateDevice(phy_dev->physical_device, &dev_info, NULL, &dev->device); 258 | tut1_error_set_vkresult(&retval, res); 259 | 260 | exit_failed: 261 | return retval; 262 | } 263 | 264 | static void print_layers_and_extensions(const char *indent, VkLayerProperties layers[TUT5_MAX_LAYER_COUNT], uint32_t layer_count, 265 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT], uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]) 266 | { 267 | /* 268 | * First, let's print the extensions that are independent of layers. Then, for each layer we will enumerate 269 | * its own extensions as well. 270 | */ 271 | for (uint32_t j = 0; j < extensions_count[0]; ++j) 272 | { 273 | VkExtensionProperties *ext = &extensions[0][j]; 274 | printf("%s* Extension: %s (versions: spec: 0x%08X)\n", indent, ext->extensionName, ext->specVersion); 275 | } 276 | for (uint32_t i = 0; i < layer_count; ++i) 277 | { 278 | VkLayerProperties *layer = &layers[i]; 279 | printf("%s* Layer: %s (versions: spec: 0x%08X, implementation: 0x%08X)\n", indent, layer->layerName, 280 | layer->specVersion, layer->implementationVersion); 281 | printf("%s %s\n", indent, layer->description); 282 | for (uint32_t j = 0; j < extensions_count[i + 1]; ++j) 283 | { 284 | VkExtensionProperties *ext = &extensions[i + 1][j]; 285 | printf("%s * Extension: %s (versions: spec: 0x%08X)\n", indent, ext->extensionName, ext->specVersion); 286 | } 287 | } 288 | } 289 | 290 | void tut5_print_layers_and_extensions(void) 291 | { 292 | VkLayerProperties layers[TUT5_MAX_LAYER_COUNT]; 293 | uint32_t layer_count = TUT5_MAX_LAYER_COUNT; 294 | 295 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT]; 296 | uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]; 297 | 298 | tut1_error res = get_layers_and_extensions(layers, &layer_count, extensions, extensions_count); 299 | if (tut1_error_is_error(&res)) 300 | tut1_error_printf(&res, "Failed to enumerate instance layers and extensions\n"); 301 | else if (tut1_error_is_warning(&res)) 302 | printf("Much instance layers and extensions! Such Wow!\n"); 303 | 304 | printf("Instance:\n"); 305 | print_layers_and_extensions("", layers, layer_count, extensions, extensions_count); 306 | } 307 | 308 | void tut5_print_device_layers_and_extensions(struct tut1_physical_device *phy_dev) 309 | { 310 | VkLayerProperties layers[TUT5_MAX_LAYER_COUNT]; 311 | uint32_t layer_count = TUT5_MAX_LAYER_COUNT; 312 | 313 | VkExtensionProperties extensions[TUT5_MAX_LAYER_COUNT + 1][TUT5_MAX_EXTENSION_COUNT]; 314 | uint32_t extensions_count[TUT5_MAX_LAYER_COUNT + 1]; 315 | 316 | tut1_error res = get_device_layers_and_extensions(phy_dev->physical_device, layers, &layer_count, extensions, extensions_count); 317 | if (tut1_error_is_error(&res)) 318 | tut1_error_printf(&res, "%s: Failed to enumerate device layers and extensions\n", phy_dev->properties.deviceName); 319 | else if (tut1_error_is_warning(&res)) 320 | printf("%s: Much device layers and extensions! Such Wow!\n", phy_dev->properties.deviceName); 321 | 322 | printf("- Device %s:\n", phy_dev->properties.deviceName); 323 | print_layers_and_extensions(" ", layers, layer_count, extensions, extensions_count); 324 | } 325 | -------------------------------------------------------------------------------- /tut5/tut5.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT5_H 21 | #define TUT5_H 22 | 23 | #include "../tut1/tut1.h" 24 | #include "../tut2/tut2.h" 25 | 26 | /* This tutorial replaces tut1_init and tut2_get_dev with versions that enable layers and extensions */ 27 | tut1_error tut5_init(VkInstance *vk); 28 | tut1_error tut5_get_dev(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 29 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count); 30 | 31 | void tut5_print_layers_and_extensions(void); 32 | void tut5_print_device_layers_and_extensions(struct tut1_physical_device *phy_dev); 33 | 34 | static inline tut1_error tut5_setup(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags) 35 | { 36 | VkDeviceQueueCreateInfo queue_info[phy_dev->queue_family_count]; 37 | uint32_t queue_info_count = phy_dev->queue_family_count; 38 | 39 | tut1_error res = tut5_get_dev(phy_dev, dev, qflags, queue_info, &queue_info_count); 40 | if (tut1_error_is_success(&res)) 41 | res = tut2_get_commands(phy_dev, dev, queue_info, queue_info_count); 42 | return res; 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /tut6/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "tut6.h" 24 | 25 | #define MAX_DEVICES 2 26 | 27 | static int process_events() 28 | { 29 | SDL_Event event; 30 | while (SDL_PollEvent(&event)) 31 | { 32 | switch (event.type) 33 | { 34 | case SDL_QUIT: 35 | return -1; 36 | case SDL_WINDOWEVENT: 37 | /* 38 | * If the window is resized, the next time a buffer is requested from the presentation engine, 39 | * it either fails or returns VK_SUBOPTIMAL_KHR. In the former case, the swapchain must be 40 | * recreated, and in the later case the presentation engine could somehow show the image 41 | * anyway, but not with great performance. Optimally, we would want to recreate the swapchain 42 | * on window resize, but for now we just rage quit. 43 | */ 44 | if (event.window.type == SDL_WINDOWEVENT_RESIZED) 45 | { 46 | printf("Warning: window resizing is currently not supported\n"); 47 | return -1; 48 | } 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | static void render_loop(uint32_t dev_count, struct tut1_physical_device *phy_devs, struct tut2_device *devs, struct tut6_swapchain *swapchains) 59 | { 60 | tut1_error retval = TUT1_ERROR_NONE; 61 | VkResult res; 62 | 63 | /* 64 | * At first, we need to get a reference to all the images of the swapchains, so that later when we acquire or 65 | * queue them, we would be working with indices only. 66 | */ 67 | VkImage *images[dev_count]; 68 | 69 | for (uint32_t i = 0; i < dev_count; ++i) 70 | { 71 | images[i] = tut6_get_swapchain_images(&devs[i], &swapchains[i], NULL); 72 | if (images[i] == NULL) 73 | { 74 | printf("-- failed for device %u\n", i); 75 | return; 76 | } 77 | } 78 | 79 | /* 80 | * The images to be presented are submitted to queues, which we already created in Tutorial 2. Not all queues 81 | * may be able to present images, so we have to query this information for each queue family. In Tutorial 2, 82 | * we created a command pool for each queue family, so we can use the indices from 0 to command_pool_count-1 as 83 | * queue family indices for querying this information. 84 | */ 85 | VkQueue present_queue[dev_count]; 86 | for (uint32_t i = 0; i < dev_count; ++i) 87 | { 88 | VkBool32 supports = false; 89 | present_queue[i] = NULL; 90 | for (uint32_t j = 0; j < devs[i].command_pool_count; ++j) 91 | { 92 | /* 93 | * Asking whether a queue family is able to present images is straightforward. You need to 94 | * give the `vkGetPhysicalDeviceSurfaceSupportKHR` function the physical device, the surface 95 | * you want to present to and the queue family index. It returns the result in the last 96 | * argument. 97 | * 98 | * Note that this information is queried from the physical device, which means you can already 99 | * know which queue families can support presentation before creating a logical device. If you 100 | * recall from Tutorial 2, creating a logical device requires requesting queues from the 101 | * device's queue families. If you perform this operation beforehand and you are only 102 | * interested in presenting images, you would be able to avoid requesting for queue families 103 | * that don't support it. In Tutorial 2, we just asked for all queue families there are, which 104 | * is not the nicest thing. 105 | */ 106 | res = vkGetPhysicalDeviceSurfaceSupportKHR(phy_devs[i].physical_device, j, swapchains[i].surface, &supports); 107 | tut1_error_set_vkresult(&retval, res); 108 | if (res) 109 | { 110 | tut1_error_printf(&retval, "Failed to determine whether queue family index %u on device %u supports presentation\n", j, i); 111 | goto exit_fail; 112 | } 113 | 114 | /* Just present to the first queue of the first family we find, for now. */ 115 | if (supports) 116 | { 117 | present_queue[i] = devs[i].command_pools[j].queues[0]; 118 | break; 119 | } 120 | } 121 | 122 | if (present_queue[i] == NULL) 123 | { 124 | printf("Failed to find any family queue on device %u that supports presentation\n", i); 125 | goto exit_fail; 126 | } 127 | } 128 | 129 | /* Process events from SDL and render. If process_events returns non-zero, it signals application exit. */ 130 | while (process_events() == 0) 131 | { 132 | /* 133 | * For now, we are going to use 1 thread (this one) to render, mostly because we are not actually 134 | * rendering anything, but rather practicing moving images back and forth between the application 135 | * and the presentation engine. 136 | * 137 | * At first, all buffers (images) are owned by the swapchain (presentation engine). We ask for an 138 | * image, render into it, and return it. In a multi-threaded application, you could ask for images as 139 | * soon as you can get them, render to them and queue them in parallel. The function that "acquires" 140 | * an image can signal a fence and/or a semaphore after acquiring an image. This is very helpful with 141 | * respect to parallelism, because the command buffer being executed can wait for the signal when it 142 | * really needs the images and is able to do some of its work already before the image is available. 143 | * 144 | * On the other hand, the function that "queues" back an image on the swapchain, can wait on a 145 | * number of semaphores before doing so. This is also very helpful for parallelism, as it allows the 146 | * command buffer being executed to signal immediately when it is done with the image before going on 147 | * with finishing up the execution, or calculating more side effects. 148 | * 149 | * Since we are just using 1 thread, the task is a lot simpler. We simply take an image, do something 150 | * with it, and give it back. To actually render something, we would need to have a graphics pipeline 151 | * set up. This would need more work, so we'll leave that to another tutorial. 152 | */ 153 | 154 | for (uint32_t i = 0; i < dev_count; ++i) 155 | { 156 | /* 157 | * The `vkAcquireNextImageKHR` function takes the device and swapchain as other swapchain 158 | * functions do. It then takes a timeout value which tells how many nanoseconds to wait before 159 | * an image is available. This value can be used to make sure this function doesn't 160 | * indefinitely block you, and so that you can take action in case something is wrong. A value 161 | * of 0 will return immediately with results, if any. A value of UINT64_MAX will make the wait 162 | * infinite (even if it didn't, that value is so large, it's infinite anyway). Here, we will 163 | * use a value of 1 second, and if we get a timeout, it means that we cannot render for a whole 164 | * second. 165 | * 166 | * The next two arguments are references to a semaphore and a fence. The function would signal 167 | * the semaphore and the fence as soon as the image is acquired. Since we are not yet 168 | * rendering anything, this is not useful. 169 | * 170 | * The index of the image that is now available is returned through the last argument. 171 | */ 172 | uint32_t image_index; 173 | 174 | res = vkAcquireNextImageKHR(devs[i].device, swapchains[i].swapchain, 1000000000, NULL, NULL, &image_index); 175 | tut1_error_set_vkresult(&retval, res); 176 | if (res == VK_TIMEOUT) 177 | { 178 | printf("A whole second and no image. I give up.\n"); 179 | goto exit_fail; 180 | } 181 | else if (res == VK_SUBOPTIMAL_KHR) 182 | printf("Did you change the window size? I didn't recreate the swapchains,\n" 183 | "so the presentation is now suboptimal.\n"); 184 | else if (res < 0) 185 | { 186 | tut1_error_printf(&retval, "Couldn't acquire image\n"); 187 | goto exit_fail; 188 | } 189 | 190 | /* 191 | * Here is where the rendering would take place, if we had not been so lazy to set a graphics 192 | * pipeline and descriptor set up. 193 | * 194 | * Just a note though: initially, all the images in the swap chain have an undefined layout 195 | * (VK_IMAGE_LAYOUT_UNDEFINED). The command that was supposed to be executed here would 196 | * transition the image into a valid layout. We will get to what this means in the next 197 | * tutorial. More importantly, it must be transitioned to "present src" layout before 198 | * presentation. Now we are not actually doing this last operation, so if the queue operation 199 | * fails, it may be rightly doing so. 200 | */ 201 | 202 | /* 203 | * The `vkQueuePresentKHR` function takes the queue to present the image through and can queue 204 | * multiple images from multiple swapchains at the same time. A set of semaphores to wait on 205 | * before doing so are taken which may not necessarily have a one-to-one correspondence with 206 | * the swapchains. 207 | * 208 | * For each swapchain, the swapchain itself as well as the index of the image to present are 209 | * given. In this example, we could present all dev_count images at the same time outside this 210 | * for loop, but I just went with presenting them one by one. 211 | * 212 | * Since there could be multiple submissions at once, an array of `VkResult`s can also be given 213 | * to specify exactly which queue operations succeeded and which failed. We are making only 214 | * one submission, so the return value of the function is enough. 215 | */ 216 | VkPresentInfoKHR present_info = { 217 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 218 | .swapchainCount = 1, 219 | .pSwapchains = &swapchains[i].swapchain, 220 | .pImageIndices = &image_index, 221 | }; 222 | res = vkQueuePresentKHR(present_queue[i], &present_info); 223 | tut1_error_set_vkresult(&retval, res); 224 | if (res < 0) 225 | { 226 | tut1_error_printf(&retval, "Failed to queue image for presentation on device %u\n", i); 227 | goto exit_fail; 228 | } 229 | } 230 | 231 | /* 232 | * Make sure we don't end up busy looping. Feel free to remove this and see what happens. In the next 233 | * Tutorial we will see what can be done instead of sleeping. As a matter of fact, we should have used 234 | * proper synchronization when acquiring and presenting the image, but that's postponed to Tutorial 7. 235 | * If you see many errors reported by the validation layers, don't panic! 236 | * 237 | * On Linux, with Nvidia GTX 970, and Vulkan 1.0.8, removing this caused the whole UI to become very 238 | * sluggish. 239 | */ 240 | usleep(10000); 241 | } 242 | 243 | exit_fail: 244 | for (uint32_t i = 0; i < dev_count; ++i) 245 | free(images[i]); 246 | } 247 | 248 | int main(int argc, char **argv) 249 | { 250 | tut1_error res; 251 | int retval = EXIT_FAILURE; 252 | VkInstance vk; 253 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 254 | struct tut2_device devs[MAX_DEVICES]; 255 | struct tut6_swapchain swapchains[MAX_DEVICES] = {0}; 256 | SDL_Window *windows[MAX_DEVICES] = {NULL}; 257 | uint32_t dev_count = MAX_DEVICES; 258 | 259 | bool no_vsync = false; 260 | 261 | for (int i = 1; i < argc; ++i) 262 | { 263 | if (strcmp(argv[1], "--help") == 0) 264 | { 265 | printf("Usage: %s [--no-vsync]\n\n", argv[0]); 266 | return 0; 267 | } 268 | if (strcmp(argv[1], "--no-vsync") == 0) 269 | no_vsync = true; 270 | } 271 | 272 | /* Fire up Vulkan */ 273 | res = tut6_init(&vk); 274 | if (!tut1_error_is_success(&res)) 275 | { 276 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 277 | goto exit_bad_init; 278 | } 279 | 280 | /* Enumerate devices */ 281 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 282 | if (tut1_error_is_error(&res)) 283 | { 284 | tut1_error_printf(&res, "Could not enumerate devices\n"); 285 | goto exit_bad_enumerate; 286 | } 287 | 288 | /* 289 | * Once again, tut2_setup is replaced with tut6_setup, which enables WSI (window system integration) extensions 290 | * so that we can actually show something on the screen. Remember that Vulkan nicely separates its work from 291 | * presenting images on the screen, so you can work with graphics without actually having a window. 292 | */ 293 | for (uint32_t i = 0; i < dev_count; ++i) 294 | { 295 | res = tut6_setup(&phy_devs[i], &devs[i], VK_QUEUE_GRAPHICS_BIT); 296 | if (tut1_error_is_error(&res)) 297 | { 298 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 299 | goto exit_bad_setup; 300 | } 301 | } 302 | 303 | /* 304 | * We need to setup SDL now so that we can create a surface and a swapchain over it. We will do this for each 305 | * device, just for example, but in reality you probably just want to have one windows! The way this tutorial 306 | * is done, is like running two separate applications one based on each of your graphics cards. In real 307 | * applications one might want to use the other graphics cards for additional rendering, transfer it back to 308 | * the main graphics card and do the final rendering using that. 309 | */ 310 | if (SDL_Init(SDL_INIT_VIDEO)) 311 | { 312 | printf("Could not initialize SDL: %s\n", SDL_GetError()); 313 | goto exit_bad_sdl; 314 | } 315 | 316 | for (uint32_t i = 0; i < dev_count; ++i) 317 | { 318 | char title[50]; 319 | snprintf(title, sizeof title, "Vk on device %u\n", i); 320 | windows[i] = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1024, 768, 0); 321 | if (windows[i] == NULL) 322 | { 323 | printf("Could not create %u%s window: %s\n", i + 1, 324 | i == 0?"st":i == 1?"nd":i == 2?"rd":"th", /* Assuming you have fewer than 20 graphics cards ;) */ 325 | SDL_GetError()); 326 | goto exit_bad_window; 327 | } 328 | } 329 | 330 | /* Get the surface and swapchain now that we have an actual window */ 331 | for (uint32_t i = 0; i < dev_count; ++i) 332 | { 333 | /* 334 | * The swapchain creates a set of buffers which in tut6_get_swapchain is calculated based on the number 335 | * of rendering threads. For now, let's not bother with threads and just use 1 thread (the current 336 | * one). 337 | */ 338 | res = tut6_get_swapchain(vk, &phy_devs[i], &devs[i], &swapchains[i], windows[i], 1, no_vsync); 339 | if (tut1_error_is_error(&res)) 340 | { 341 | tut1_error_printf(&res, "Could not create surface and swapchain for device %u\n", i); 342 | goto exit_bad_swapchain; 343 | } 344 | } 345 | 346 | /* Let's print the surface capabilities too, just to see what we are up against */ 347 | for (uint32_t i = 0; i < dev_count; ++i) 348 | { 349 | printf("On device %u:\n", i); 350 | tut6_print_surface_capabilities(&swapchains[i]); 351 | printf("\n"); 352 | } 353 | 354 | /* 355 | * We need to do some more work to render something on the screen, but we've already covered a lot in this 356 | * tutorial. Let's just go over the basics of exchanging buffers between the application and the presentation 357 | * engine, by simply clearing the screen (with a different color each time). 358 | */ 359 | render_loop(dev_count, phy_devs, devs, swapchains); 360 | 361 | retval = 0; 362 | 363 | /* Cleanup after yourself */ 364 | 365 | exit_bad_swapchain: 366 | for (uint32_t i = 0; i < dev_count; ++i) 367 | tut6_free_swapchain(vk, &devs[i], &swapchains[i]); 368 | 369 | exit_bad_window: 370 | for (uint32_t i = 0; i < dev_count; ++i) 371 | if (windows[i]) 372 | SDL_DestroyWindow(windows[i]); 373 | exit_bad_sdl: 374 | SDL_Quit(); 375 | 376 | exit_bad_setup: 377 | for (uint32_t i = 0; i < dev_count; ++i) 378 | tut2_cleanup(&devs[i]); 379 | 380 | exit_bad_enumerate: 381 | tut1_exit(vk); 382 | 383 | exit_bad_init: 384 | return retval; 385 | } 386 | -------------------------------------------------------------------------------- /tut6/tut6.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT6_H 21 | #define TUT6_H 22 | 23 | /* 24 | * To create a window and interact with it, we are going to use good old SDL. The core of Vulkan doesn't care about 25 | * displaying the images it creates. This is good, because it means you can render all you want, in parallel, and in 26 | * different images without having to create windows for it. (A nudge to the gimp's developers) 27 | * 28 | * How to put those images on the screen then? This is where extensions come in. Vulkan already comes with a couple 29 | * of extensions for WSI (Window System Integration). Let's see exactly what must happen for the images to get 30 | * rendered to the screen: 31 | * 32 | * 1. The window is created. This is platform-specific, but let's say SDL does that for us. 33 | * 2. A rendering surface is acquired. This is through a Vulkan WSI extension. There is one for libX, XCB, Mir, 34 | * Wayland, Android etc. On Linux, we are likely running X11, so we'd use XCB as the better API to talk with X11. 35 | * 3. A "swapchain" with multiple `VkImage`s is created. The VkImages are what we render into, and they are presented 36 | * one by one on the screen. This covers the usual multi-buffering there is with OpenGL for example. But it also 37 | * gives us the ability to render multiple frames at a time and present them in order. 38 | * 4. Each VkImage is either owned by the swapchain or the application. The application requests ownership of the 39 | * VkImage, renders into it, and passes it back to the swapchain which in turn presents it on the surface, through a 40 | * queue family that supports presentation. 41 | * 42 | * There are a lot of details regarding passing ownership between the swapchain and the application and we'll get to 43 | * them in time. 44 | * 45 | * To use the XCB extension, we need to have VK_USE_PLATFORM_XCB_KHR defined before including vulkan.h. 46 | */ 47 | #define VK_USE_PLATFORM_XCB_KHR 1 48 | 49 | #include 50 | #include "../tut1/tut1.h" 51 | #include "../tut2/tut2.h" 52 | 53 | #define TUT6_MAX_PRESENT_MODES 4 54 | 55 | struct tut6_swapchain 56 | { 57 | VkSurfaceKHR surface; 58 | VkSwapchainKHR swapchain; 59 | 60 | VkSurfaceFormatKHR surface_format; 61 | VkSurfaceCapabilitiesKHR surface_caps; 62 | VkPresentModeKHR present_modes[TUT6_MAX_PRESENT_MODES]; 63 | uint32_t present_modes_count; 64 | }; 65 | 66 | tut1_error tut6_init_ext(VkInstance *vk, const char *ext_names[], uint32_t ext_count); 67 | tut1_error tut6_get_dev_ext(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 68 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count, 69 | const char *ext_names[], uint32_t ext_count); 70 | 71 | /* This tutorial replaces tut1_init and tut2_get_dev with versions that enable WSI extensions */ 72 | static inline tut1_error tut6_init(VkInstance *vk) 73 | { 74 | /* 75 | * In Tutorial 5, we enabled all layers and extensions there were, which is not really the way things should be 76 | * done! In this tutorial, we are going to redo the init function one more time, enabling only the extensions 77 | * we are interested in. We are not enabling layers, since they can be enabled from the command line anyway. 78 | * 79 | * The helper _ext functions do the actual job, so if in a future tutorial we decided to enable a different set 80 | * of extensions, we don't have to rewrite the functions. 81 | */ 82 | const char *extension_names[] = { 83 | VK_KHR_SURFACE_EXTENSION_NAME, 84 | VK_KHR_XCB_SURFACE_EXTENSION_NAME, 85 | }; 86 | return tut6_init_ext(vk, extension_names, sizeof extension_names / sizeof *extension_names); 87 | } 88 | static inline tut1_error tut6_get_dev(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags, 89 | VkDeviceQueueCreateInfo queue_info[], uint32_t *queue_info_count) 90 | { 91 | /* 92 | * The surface and XCB extensions were "instance" extensions and are enabled in tut6_init(). For the device, 93 | * we need to enable the swapchain extension. If you run the code for Tutorial 5, you should be able to see 94 | * VK_KHR_surface and VK_KHR_xcb_surface belonging to instance extensions and VK_KHR_swapchain to your device. 95 | * 96 | * As a matter of fact, if you don't see the above extensions, your driver likely needs an update. 97 | */ 98 | const char *extension_names[] = { 99 | VK_KHR_SWAPCHAIN_EXTENSION_NAME, 100 | }; 101 | return tut6_get_dev_ext(phy_dev, dev, qflags, queue_info, queue_info_count, extension_names, 102 | sizeof extension_names / sizeof *extension_names); 103 | } 104 | 105 | static inline tut1_error tut6_setup(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkQueueFlags qflags) 106 | { 107 | VkDeviceQueueCreateInfo queue_info[phy_dev->queue_family_count]; 108 | uint32_t queue_info_count = phy_dev->queue_family_count; 109 | 110 | tut1_error res = tut6_get_dev(phy_dev, dev, qflags, queue_info, &queue_info_count); 111 | if (tut1_error_is_success(&res)) 112 | res = tut2_get_commands(phy_dev, dev, queue_info, queue_info_count); 113 | return res; 114 | } 115 | 116 | tut1_error tut6_get_swapchain(VkInstance vk, struct tut1_physical_device *phy_dev, struct tut2_device *dev, 117 | struct tut6_swapchain *swapchain, SDL_Window *window, uint32_t thread_count, bool allow_no_vsync); 118 | void tut6_free_swapchain(VkInstance vk, struct tut2_device *dev, struct tut6_swapchain *swapchain); 119 | 120 | void tut6_print_surface_capabilities(struct tut6_swapchain *swapchain); 121 | 122 | VkImage *tut6_get_swapchain_images(struct tut2_device *dev, struct tut6_swapchain *swapchain, uint32_t *count); 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /tut7/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "tut7.h" 25 | #include "tut7_render.h" 26 | 27 | #define MAX_DEVICES 2 28 | 29 | static int process_events() 30 | { 31 | SDL_Event event; 32 | while (SDL_PollEvent(&event)) 33 | { 34 | switch (event.type) 35 | { 36 | case SDL_QUIT: 37 | return -1; 38 | case SDL_WINDOWEVENT: 39 | /* rage quit like before */ 40 | if (event.window.type == SDL_WINDOWEVENT_RESIZED) 41 | { 42 | printf("Warning: window resizing is currently not supported\n"); 43 | return -1; 44 | } 45 | break; 46 | default: 47 | break; 48 | } 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | static void render_loop(uint32_t dev_count, struct tut1_physical_device *phy_devs, struct tut2_device *devs, struct tut6_swapchain *swapchains) 55 | { 56 | int res; 57 | struct tut7_render_essentials essentials[dev_count]; 58 | 59 | /* Allocate render essentials. See this function in tut7_render.c for explanations. */ 60 | for (uint32_t i = 0; i < dev_count; ++i) 61 | { 62 | res = tut7_render_get_essentials(&essentials[i], &phy_devs[i], &devs[i], &swapchains[i]); 63 | if (res) 64 | { 65 | printf("-- failed for device %u\n", i); 66 | return; 67 | } 68 | } 69 | 70 | unsigned int frames = 0; 71 | time_t before = time(NULL); 72 | 73 | uint8_t color = 0; 74 | 75 | /* Process events from SDL and render. If process_events returns non-zero, it signals application exit. */ 76 | while (process_events() == 0) 77 | { 78 | /* 79 | * A simple imprecise FPS calculator. Try the --no-vsync option to this program to see the difference. 80 | * 81 | * On Linux, with Nvidia GTX 970, and Vulkan 1.0.8, --no-vsync got me about 12000 FPS. 82 | */ 83 | time_t now = time(NULL); 84 | if (now != before) 85 | { 86 | printf("%lds: %u frames\n", now - before, frames); 87 | frames = 0; 88 | before = now; 89 | } 90 | ++frames; 91 | 92 | /* 93 | * We are not yet ready to actually render something. For that, we would need descriptor sets and 94 | * pipelines, but we'll get to that soon. In tut7.c, we have a repository of functions to create 95 | * resources for the eventual rendering. Here, we'll ignore all that and do what we ignored in 96 | * Tutorial 6, and that is properly transitioning the swapchain images between "present src" and 97 | * something we can render to. With a graphics pipeline, we would want to transition to 98 | * "color attachment optimal". Since we don't have one, we are going to "clear" the screen which 99 | * doesn't need a graphics pipeline. In that case, the layout of the image should be GENERAL. 100 | */ 101 | 102 | for (uint32_t i = 0; i < dev_count; ++i) 103 | { 104 | uint32_t image_index; 105 | 106 | /* 107 | * To render to an image and present it on the screen, the following sequence of operations 108 | * needs to be done: 109 | * 110 | * - acquire from swapchain 111 | * - transition to color attachment optimal 112 | * - render 113 | * - transition to present src 114 | * - present the image 115 | * 116 | * One way to implement this would be to call the corresponding functions one by one, wait and 117 | * make sure the image passes through each section, and repeat. The problem with this way is 118 | * that there is wasted time between each function call. Not that function call itself takes 119 | * measurable time, but the setup and finish times of each call, especially because we are 120 | * interacting with the GPU. 121 | * 122 | * Vulkan is made for parallelism and efficiency, so naturally it's not stupid in this regard! 123 | * There are different ways to do the above in parallel, and synchronize them. One nice thing 124 | * is that command buffers can call other secondary command buffers. So, while a small part of 125 | * the command buffer requires knowledge of which presentable image it is working with, the 126 | * majority of it doesn't, so they could be pre-recorded or recorded in parallel by other 127 | * threads. Another nice thing is that many of the functions work asynchronously, such as 128 | * submission to queue for rendering. This allows the CPU to go ahead with executing the rest 129 | * of the above algorithm, only wait for the GPU to finish rendering when it has to, and let 130 | * synchronization mechanisms take care of handling the flow of execution in the back. 131 | * 132 | * One could imagine different ways of doing things, but here is a simple example: 133 | * 134 | * - acquire from swapchain, signalling semaphore A 135 | * - wait on fence C (for previous frame to finish) 136 | * - create a command buffer with 1) first transition, 2) render, 3) second transition 137 | * - submit the command buffer with semaphore A waiting in the beginning and semaphore B 138 | * signalling the end, with fence C signalling the end as well 139 | * - present to swapchain, waiting on the second semaphore 140 | * 141 | * The significance of the fence above is the following. In Tutorial 6, we used `usleep` to 142 | * avoid busy looping. That was bad, because it put a hard limit and the frame rate. The 143 | * issue is not just busy looping though. Since the submissions to queues happen 144 | * asynchronously, we risk submitting work faster than the card can actually perform them, with 145 | * the result being that frames we send now are rendered much later, after all our previous 146 | * work is finished. This delay can easily become unacceptable; imagine a player has hit the 147 | * key to move forwards, you detect this and generate the next frame accordingly, but the 148 | * player doesn't actually see her character move forward while several older frames are still 149 | * being rendered. 150 | * 151 | * The location of the fence is chosen as such, to allow maximum overlap between GPU and CPU 152 | * work. In this case, while the GPU is still rendering, the CPU can wait for the swapchain 153 | * image to be acquired. The wait on the fence could not be delayed any further, because we 154 | * can't re-record a command buffer that is being executed. Interestingly, if we use two 155 | * command buffers and alternate between them, we could also wait for the fence later! Let's 156 | * not go that far yet. 157 | */ 158 | 159 | /* See this function in tut7_render.c for explanations */ 160 | res = tut7_render_start(&essentials[i], &devs[i], &swapchains[i], VK_IMAGE_LAYOUT_GENERAL, &image_index); 161 | if (res) 162 | { 163 | printf("-- failed for device %u\n", i); 164 | goto exit_fail; 165 | } 166 | 167 | /* 168 | * We did everything just to clear the image. Like I said, it's possible to clear an image 169 | * outside a pipeline. It is also possible to clear it inside a pipeline, so fear not! When 170 | * we have a graphics pipeline, we can transition the image directly to "color attachment 171 | * optimal" and clear it, and we don't have to first transition to "general" and then 172 | * transition again to "color attachment optimal". 173 | * 174 | * Clearing the image outside the pipeline is quite straightforward, and in fact has no notion 175 | * of the image being used for presentation later. It's just clearing a general image. 176 | * 177 | * The vkCmdClearColorImage takes the command buffer, the image, the layout the image is in 178 | * (which is "general", we just transitioned it), the color to clear the image with, and a set 179 | * of "subresources" to clear. We are going to clear everything, and we have just a single mip 180 | * level and a single array layer, so the subresource range to be cleared is similar to the 181 | * `subresourceRange` in image barrier. 182 | * 183 | * The clear color needs to be specified based on the format of the image. The 184 | * `VkClearColorValue` is a union which accepts RGBA values in float, uint32_t or int32_t, and 185 | * we should choose the appropriate field based on swapchains[i].surface_format.format. If we 186 | * weren't so lazy, we could write a simple lookup table that tells us which field to use for 187 | * each format, but luckily we are lazy, so let's assume `float` is good for now and hope it's 188 | * portable enough. 189 | * 190 | * For fun, let's change the background color on each frame! 191 | */ 192 | VkImageSubresourceRange clear_subresource_range = { 193 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 194 | .baseMipLevel = 0, 195 | .levelCount = 1, 196 | .baseArrayLayer = 0, 197 | .layerCount = 1, 198 | }; 199 | VkClearColorValue clear_color = { 200 | .float32 = {color, (color + 64) % 256 / 255.0f, (color + 128) % 256 / 255.0f, 1}, 201 | }; 202 | ++color; 203 | vkCmdClearColorImage(essentials[i].cmd_buffer, essentials[i].images[image_index], VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &clear_subresource_range); 204 | 205 | /* See this function in tut7_render.c for explanations */ 206 | res = tut7_render_finish(&essentials[i], &devs[i], &swapchains[i], VK_IMAGE_LAYOUT_GENERAL, image_index); 207 | if (res) 208 | { 209 | printf("-- failed for device %u\n", i); 210 | goto exit_fail; 211 | } 212 | } 213 | } 214 | 215 | exit_fail: 216 | for (uint32_t i = 0; i < dev_count; ++i) 217 | tut7_render_cleanup_essentials(&essentials[i], &devs[i]); 218 | } 219 | 220 | int main(int argc, char **argv) 221 | { 222 | tut1_error res; 223 | int retval = EXIT_FAILURE; 224 | VkInstance vk; 225 | struct tut1_physical_device phy_devs[MAX_DEVICES]; 226 | struct tut2_device devs[MAX_DEVICES]; 227 | struct tut6_swapchain swapchains[MAX_DEVICES] = {0}; 228 | SDL_Window *windows[MAX_DEVICES] = {NULL}; 229 | uint32_t dev_count = MAX_DEVICES; 230 | 231 | bool no_vsync = false; 232 | 233 | for (int i = 1; i < argc; ++i) 234 | { 235 | if (strcmp(argv[1], "--help") == 0) 236 | { 237 | printf("Usage: %s [--no-vsync]\n\n", argv[0]); 238 | return 0; 239 | } 240 | if (strcmp(argv[1], "--no-vsync") == 0) 241 | no_vsync = true; 242 | } 243 | 244 | /* Fire up Vulkan */ 245 | res = tut6_init(&vk); 246 | if (!tut1_error_is_success(&res)) 247 | { 248 | tut1_error_printf(&res, "Could not initialize Vulkan\n"); 249 | goto exit_bad_init; 250 | } 251 | 252 | /* Enumerate devices */ 253 | res = tut1_enumerate_devices(vk, phy_devs, &dev_count); 254 | if (tut1_error_is_error(&res)) 255 | { 256 | tut1_error_printf(&res, "Could not enumerate devices\n"); 257 | goto exit_bad_enumerate; 258 | } 259 | 260 | /* Get logical devices and enable WSI extensions */ 261 | for (uint32_t i = 0; i < dev_count; ++i) 262 | { 263 | res = tut6_setup(&phy_devs[i], &devs[i], VK_QUEUE_GRAPHICS_BIT); 264 | if (tut1_error_is_error(&res)) 265 | { 266 | tut1_error_printf(&res, "Could not setup logical device %u, command pools and queues\n", i); 267 | goto exit_bad_setup; 268 | } 269 | } 270 | 271 | /* Set up SDL */ 272 | if (SDL_Init(SDL_INIT_VIDEO)) 273 | { 274 | printf("Could not initialize SDL: %s\n", SDL_GetError()); 275 | goto exit_bad_sdl; 276 | } 277 | 278 | for (uint32_t i = 0; i < dev_count; ++i) 279 | { 280 | char title[50]; 281 | snprintf(title, sizeof title, "Vk on device %u\n", i); 282 | windows[i] = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1024, 768, 0); 283 | if (windows[i] == NULL) 284 | { 285 | printf("Could not create window #%u: %s\n", i + 1, SDL_GetError()); 286 | goto exit_bad_window; 287 | } 288 | } 289 | 290 | /* Get the surface and swapchain */ 291 | for (uint32_t i = 0; i < dev_count; ++i) 292 | { 293 | /* Let's still not bother with threads and use just 1 (the current thread) */ 294 | res = tut6_get_swapchain(vk, &phy_devs[i], &devs[i], &swapchains[i], windows[i], 1, no_vsync); 295 | if (tut1_error_is_error(&res)) 296 | { 297 | tut1_error_printf(&res, "Could not create surface and swapchain for device %u\n", i); 298 | goto exit_bad_swapchain; 299 | } 300 | } 301 | 302 | /* Render loop similar to Tutorial 6 */ 303 | render_loop(dev_count, phy_devs, devs, swapchains); 304 | 305 | retval = 0; 306 | 307 | /* Cleanup after yourself */ 308 | 309 | exit_bad_swapchain: 310 | for (uint32_t i = 0; i < dev_count; ++i) 311 | tut6_free_swapchain(vk, &devs[i], &swapchains[i]); 312 | 313 | exit_bad_window: 314 | for (uint32_t i = 0; i < dev_count; ++i) 315 | if (windows[i]) 316 | SDL_DestroyWindow(windows[i]); 317 | exit_bad_sdl: 318 | SDL_Quit(); 319 | 320 | exit_bad_setup: 321 | for (uint32_t i = 0; i < dev_count; ++i) 322 | tut2_cleanup(&devs[i]); 323 | 324 | exit_bad_enumerate: 325 | tut1_exit(vk); 326 | 327 | exit_bad_init: 328 | return retval; 329 | } 330 | -------------------------------------------------------------------------------- /tut7/tut7.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT7_H 21 | #define TUT7_H 22 | 23 | #include "../tut6/tut6.h" 24 | #include "../tut4/tut4.h" 25 | 26 | struct tut7_image 27 | { 28 | /* inputs */ 29 | 30 | /* information for creating an image, and which stage to bind it to */ 31 | VkFormat format; 32 | VkExtent2D extent; 33 | VkImageUsageFlagBits usage; 34 | VkShaderStageFlagBits stage; 35 | bool make_view; 36 | bool will_be_initialized; 37 | bool host_visible; 38 | bool multisample; 39 | uint32_t *sharing_queues; 40 | uint32_t sharing_queue_count; 41 | 42 | /* outputs */ 43 | 44 | /* Vulkan image object */ 45 | VkImage image; 46 | VkDeviceMemory image_mem; 47 | VkImageView view; 48 | 49 | VkSampler sampler; 50 | }; 51 | 52 | struct tut7_buffer 53 | { 54 | /* inputs */ 55 | 56 | /* information for creating a buffer, and which stage to bind it to */ 57 | VkFormat format; 58 | uint32_t size; 59 | VkBufferUsageFlagBits usage; 60 | VkShaderStageFlagBits stage; 61 | bool make_view; 62 | bool host_visible; 63 | uint32_t *sharing_queues; 64 | uint32_t sharing_queue_count; 65 | 66 | /* outputs */ 67 | 68 | /* Vulkan buffer object */ 69 | VkBuffer buffer; 70 | VkDeviceMemory buffer_mem; 71 | VkBufferView view; 72 | }; 73 | 74 | struct tut7_shader 75 | { 76 | /* inputs */ 77 | 78 | const char *spirv_file; 79 | VkShaderStageFlagBits stage; 80 | 81 | /* outputs */ 82 | 83 | VkShaderModule shader; 84 | }; 85 | 86 | struct tut7_graphics_buffers 87 | { 88 | /* inputs */ 89 | 90 | VkExtent2D surface_size; 91 | VkImage swapchain_image; 92 | 93 | /* outputs */ 94 | 95 | VkImageView color_view; 96 | struct tut7_image depth; 97 | 98 | VkFramebuffer framebuffer; 99 | }; 100 | 101 | tut1_error tut7_create_images(struct tut1_physical_device *phy_dev, struct tut2_device *dev, 102 | struct tut7_image *images, uint32_t image_count); 103 | tut1_error tut7_create_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, 104 | struct tut7_buffer *buffers, uint32_t buffer_count); 105 | tut1_error tut7_load_shaders(struct tut2_device *dev, 106 | struct tut7_shader *shaders, uint32_t shader_count); 107 | tut1_error tut7_create_graphics_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, 108 | VkSurfaceFormatKHR surface_format, 109 | struct tut7_graphics_buffers *graphics_buffers, uint32_t graphics_buffer_count, VkRenderPass *render_pass); 110 | tut1_error tut7_get_presentable_queues(struct tut1_physical_device *phy_dev, struct tut2_device *dev, 111 | VkSurfaceKHR surface, uint32_t **presentable_queues, uint32_t *presentable_queue_count); 112 | VkFormat tut7_get_supported_depth_stencil_format(struct tut1_physical_device *phy_dev); 113 | 114 | void tut7_free_images(struct tut2_device *dev, struct tut7_image *images, uint32_t image_count); 115 | void tut7_free_buffers(struct tut2_device *dev, struct tut7_buffer *buffers, uint32_t buffer_count); 116 | void tut7_free_shaders(struct tut2_device *dev, struct tut7_shader *shaders, uint32_t shader_count); 117 | void tut7_free_graphics_buffers(struct tut2_device *dev, struct tut7_graphics_buffers *graphics_buffers, uint32_t graphics_buffer_count, 118 | VkRenderPass render_pass); 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /tut7/tut7_render.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include "tut7_render.h" 21 | 22 | int tut7_render_get_essentials(struct tut7_render_essentials *essentials, struct tut1_physical_device *phy_dev, 23 | struct tut2_device *dev, struct tut6_swapchain *swapchain) 24 | { 25 | tut1_error retval = TUT1_ERROR_NONE; 26 | VkResult res; 27 | 28 | /* Like in Tutorial 6, take the list of swapchain images for future */ 29 | essentials->images = tut6_get_swapchain_images(dev, swapchain, &essentials->image_count); 30 | if (essentials->images == NULL) 31 | return -1; 32 | 33 | /* 34 | * Take the first queue out of the first presentable queue family (and command buffer on it) to use for 35 | * presentation (for now). 36 | */ 37 | uint32_t *presentable_queues = NULL; 38 | uint32_t presentable_queue_count = 0; 39 | 40 | retval = tut7_get_presentable_queues(phy_dev, dev, swapchain->surface, &presentable_queues, &presentable_queue_count); 41 | if (!tut1_error_is_success(&retval) || presentable_queue_count == 0) 42 | { 43 | printf("No presentable queue families! What kind of graphics card is this!\n"); 44 | return -1; 45 | } 46 | 47 | essentials->present_queue = dev->command_pools[presentable_queues[0]].queues[0]; 48 | essentials->cmd_buffer = dev->command_pools[presentable_queues[0]].buffers[0]; 49 | free(presentable_queues); 50 | 51 | /* Create semaphores for synchronization (details in tut7_render_start) */ 52 | VkSemaphoreCreateInfo sem_info = { 53 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, 54 | }; 55 | 56 | res = vkCreateSemaphore(dev->device, &sem_info, NULL, &essentials->sem_post_acquire); 57 | tut1_error_set_vkresult(&retval, res); 58 | if (res) 59 | { 60 | tut1_error_printf(&retval, "Failed to create post-acquire semaphore\n"); 61 | return -1; 62 | } 63 | 64 | res = vkCreateSemaphore(dev->device, &sem_info, NULL, &essentials->sem_pre_submit); 65 | tut1_error_set_vkresult(&retval, res); 66 | if (res) 67 | { 68 | tut1_error_printf(&retval, "Failed to create pre-submit semaphore\n"); 69 | return -1; 70 | } 71 | 72 | /* Create fence for throttling the rendering (details in tut7_render_start) */ 73 | VkFenceCreateInfo fence_info = { 74 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 75 | }; 76 | 77 | res = vkCreateFence(dev->device, &fence_info, NULL, &essentials->exec_fence); 78 | tut1_error_set_vkresult(&retval, res); 79 | if (res) 80 | { 81 | tut1_error_printf(&retval, "Failed to create fence\n"); 82 | return -1; 83 | } 84 | 85 | essentials->first_render = true; 86 | 87 | return 0; 88 | } 89 | 90 | void tut7_render_cleanup_essentials(struct tut7_render_essentials *essentials, struct tut2_device *dev) 91 | { 92 | vkDeviceWaitIdle(dev->device); 93 | 94 | vkDestroySemaphore(dev->device, essentials->sem_post_acquire, NULL); 95 | vkDestroySemaphore(dev->device, essentials->sem_pre_submit, NULL); 96 | vkDestroyFence(dev->device, essentials->exec_fence, NULL); 97 | free(essentials->images); 98 | } 99 | 100 | int tut7_render_start(struct tut7_render_essentials *essentials, struct tut2_device *dev, 101 | struct tut6_swapchain *swapchain, VkImageLayout to_layout, uint32_t *image_index) 102 | { 103 | tut1_error retval = TUT1_ERROR_NONE; 104 | VkResult res; 105 | 106 | /* Use `vkAcquireNextImageKHR` to get an image to render to */ 107 | 108 | res = vkAcquireNextImageKHR(dev->device, swapchain->swapchain, 1000000000, essentials->sem_post_acquire, NULL, image_index); 109 | tut1_error_set_vkresult(&retval, res); 110 | if (res == VK_TIMEOUT) 111 | { 112 | printf("A whole second and no image. I give up.\n"); 113 | return -1; 114 | } 115 | else if (res == VK_SUBOPTIMAL_KHR) 116 | printf("Did you change the window size? I didn't recreate the swapchains,\n" 117 | "so the presentation is now suboptimal.\n"); 118 | else if (res < 0) 119 | { 120 | tut1_error_printf(&retval, "Couldn't acquire image\n"); 121 | return -1; 122 | } 123 | 124 | /* 125 | * Unless the first time we are rendering, wait for the last frame to finish rendering. Let's wait up to a 126 | * second, and if the fence is still not signalled, we'll assume something went horribly wrong and quit. 127 | * 128 | * This wait needs to be done before we start recording over the command buffer again, because, well, if not 129 | * we would be resetting it while it's being executed. 130 | */ 131 | if (!essentials->first_render) 132 | { 133 | res = vkWaitForFences(dev->device, 1, &essentials->exec_fence, true, 1000000000); 134 | tut1_error_set_vkresult(&retval, res); 135 | if (res) 136 | { 137 | tut1_error_printf(&retval, "Wait for fence failed\n"); 138 | return -1; 139 | } 140 | } 141 | essentials->first_render = false; 142 | 143 | /* 144 | * We have seen many of the command buffer functions in Tutorial 4. Here is a short recap: 145 | * 146 | * - reset: remove all previous recordings from the command buffer 147 | * - begin: start recording 148 | * - bind pipeline: specify the pipeline the commands run on (unused here) 149 | * - bind descriptor set: specify resources to use for rendering (unused here) 150 | * - end: stop recording 151 | */ 152 | vkResetCommandBuffer(essentials->cmd_buffer, 0); 153 | VkCommandBufferBeginInfo begin_info = { 154 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 155 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 156 | }; 157 | res = vkBeginCommandBuffer(essentials->cmd_buffer, &begin_info); 158 | tut1_error_set_vkresult(&retval, res); 159 | if (res) 160 | { 161 | tut1_error_printf(&retval, "Couldn't even begin recording a command buffer\n"); 162 | return -1; 163 | } 164 | 165 | /* 166 | * To transition an image to a new layout, an image barrier is used. Before we see how that is done, let's see 167 | * what it even means. 168 | * 169 | * In Vulkan, there are barriers on different kinds of resources (images, buffers and memory) and other means 170 | * to specify execution dependency. In each case, you want to make sure some actions A are all executed before 171 | * some actions B. In the specific case of barriers, A could be actions that do something to the resource and 172 | * B could be actions that need the result of those actions. 173 | * 174 | * In our specific case, we want to change the layout of a swapchain image. For the transition from present 175 | * src, we want to make sure that all writes to the image are done after the transition is done. For the 176 | * transition to present src, we want to make sure that all writes to the image are done before the transition 177 | * is done. Note: if we had a graphics pipeline, we would be talking about "color attachment writes" instead 178 | * of just "writes". Keep that in mind. 179 | * 180 | * Using a VkImageMemoryBarrier, we are not only specifying how the image layout should change (if changed at 181 | * all), but also defining the actions A and B where an execution dependency would be created. In the first 182 | * barrier (transition from present src), all reads of the image (for previous presentation) must happen before 183 | * the barrier (A is the set of READ operations), and all writes must be done after the barrier (B is the set 184 | * of WRITE operations). The situation is reversed with the second barrier (transition to present src). 185 | * 186 | * In Vulkan, actions A are referred to as `src` and actions B are referred to as `dst`. 187 | * 188 | * Using an image barrier, it's also possible to transfer one image from a queue family to another, in which 189 | * case A is the set of actions accessing the image in the first queue family and B is the set of actions 190 | * accessing the image in the second queue family. We are not moving between queue families, so we'll specify 191 | * this intention as well. 192 | * 193 | * In our layout transition, we are transitioning from present src to to_layout and back. However, the first 194 | * time the transition happens, the swapchain image layout is actually UNDEFINED. Either way, since we are not 195 | * interested in what was previously in the image when we are just about to render into it, we can set the 196 | * `oldLayout` (the layout transitioning from) to UNDEFINED. This makes the transition more efficient because 197 | * Vulkan knows it can just throw away the contents of the image. Note: in Tutorial 7, we are transition to 198 | * "general", but if we had a graphics pipeline, we would be transition to the "color attachment optimal" 199 | * layout instead. 200 | * 201 | * Finally, we need to specify which part of the image (subresource) is being transitioned. We want to 202 | * transition COLOR parts of the image (which in this case, all of the image is COLOR), and all mip levels and 203 | * arrays (which are both in this case single). 204 | */ 205 | VkImageMemoryBarrier image_barrier = { 206 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 207 | .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, 208 | .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, 209 | .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, 210 | .newLayout = to_layout, 211 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 212 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 213 | .image = essentials->images[*image_index], 214 | .subresourceRange = { 215 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 216 | .baseMipLevel = 0, 217 | .levelCount = 1, 218 | .baseArrayLayer = 0, 219 | .layerCount = 1, 220 | }, 221 | }; 222 | 223 | /* 224 | * The image barrier structure above defines the execution dependency of sets of actions A and B. When 225 | * applying the barrier, we also need to specify which pipeline stages these sets of actions are taken from. 226 | * 227 | * In our barrier, first we want to make sure all READs from the image (by the previous presentation) is done 228 | * before the barrier. These reads are not part of our rendering. In fact, they are really done before the 229 | * graphics pipeline even begins. So the pipeline stage we specify for `src` would be the top of the pipeline, 230 | * which means before the pipeline begins. Second, we want to make sure all writes to the image (for 231 | * rendering) is done after the barrier. The writes to the image are likely to happen at later stages of the 232 | * graphics pipeline, so we can specify those stages as `dst` stages of the barrier. We have already specified 233 | * that the barrier works on WRITEs, so we can also be a bit lazy and say that the `dst` stage is all graphics 234 | * pipeline stages. 235 | * 236 | * Let's rephrase the above to make sure it's clear. The vkCmdPipelineBarrier takes a src and dst stage mask. 237 | * The arguments are called srcStageMask and dstStageMask. They can contain more than one pipeline stage. 238 | * Take the combinations (srcAccessMask, srcStageMask) and (dstAccessMask, dstStageMask). Say we make a 239 | * barrier from (A, Sa) to (B, Sb) as src and dst parts of the barrier respectively. The barrier then means 240 | * that all actions A in stages Sa are done before all actions B in stages Sb. So, if Sb is all graphics 241 | * stages, it means that all actions A in stages Sa are done before all actions B anywhere. If Sa is top of 242 | * the pipeline, it means that all actions A before the pipeline are done before all actions B anywhere. 243 | * 244 | * All READs before the pipeline must be done before all WRITEs anywhere. 245 | */ 246 | vkCmdPipelineBarrier(essentials->cmd_buffer, 247 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 248 | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 249 | 0, /* no flags */ 250 | 0, NULL, /* no memory barriers */ 251 | 0, NULL, /* no buffer barriers */ 252 | 1, &image_barrier); /* our image transition */ 253 | 254 | return 0; 255 | } 256 | 257 | int tut7_render_finish(struct tut7_render_essentials *essentials, struct tut2_device *dev, 258 | struct tut6_swapchain *swapchain, VkImageLayout from_layout, uint32_t image_index) 259 | { 260 | tut1_error retval = TUT1_ERROR_NONE; 261 | VkResult res; 262 | 263 | /* The second memory barrier is similar to the first (in tut7_render_start), but inverted */ 264 | VkImageMemoryBarrier image_barrier = { 265 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 266 | .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, 267 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, 268 | .oldLayout = from_layout, 269 | .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 270 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 271 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 272 | .image = essentials->images[image_index], 273 | .subresourceRange = { 274 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 275 | .baseMipLevel = 0, 276 | .levelCount = 1, 277 | .baseArrayLayer = 0, 278 | .layerCount = 1, 279 | }, 280 | }; 281 | 282 | /* 283 | * For the second barrier, we want the opposite of the first barrier. We want to make sure image reads by the 284 | * presentation engine are done after the image is written to during rendering. Just as top of the pipeline 285 | * means before the pipeline begins, bottom of the pipeline means after it ends. 286 | * 287 | * With the explanation on the previous barrier, this should already makes sense to you: 288 | * 289 | * All WRITEs anywhere must be done before all READs after the pipeline. 290 | */ 291 | vkCmdPipelineBarrier(essentials->cmd_buffer, 292 | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 293 | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 294 | 0, /* no flags */ 295 | 0, NULL, /* no memory barriers */ 296 | 0, NULL, /* no buffer barriers */ 297 | 1, &image_barrier); /* our image transition */ 298 | 299 | vkEndCommandBuffer(essentials->cmd_buffer); 300 | 301 | res = vkResetFences(dev->device, 1, &essentials->exec_fence); 302 | tut1_error_set_vkresult(&retval, res); 303 | if (res) 304 | { 305 | tut1_error_printf(&retval, "Failed to reset fence\n"); 306 | return res; 307 | } 308 | 309 | /* 310 | * Having built the command buffer, we are ready to submit it to a queue for presentation. We wanted our 311 | * submission to wait for the image acquisition semaphore and subsequently signal the presentation semaphore, 312 | * so we'll simply specify exactly that. The semaphore wait can be done at different stages of the pipeline, 313 | * so that the pipeline could go ahead with its calculations before the stage where it really needs to wait 314 | * for the semaphore. Since we don't have a pipeline yet, we'll ask it to wait at the top of the pipeline. 315 | * 316 | * Side note: we didn't need to submit the command buffer to the same queue we use for presentation, or even a 317 | * queue in the same family, but we do that now for simplicity. 318 | */ 319 | VkPipelineStageFlags wait_sem_stages[1] = {VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT}; 320 | VkSubmitInfo submit_info = { 321 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 322 | .waitSemaphoreCount = 1, 323 | .pWaitSemaphores = &essentials->sem_post_acquire, 324 | .pWaitDstStageMask = wait_sem_stages, 325 | .commandBufferCount = 1, 326 | .pCommandBuffers = &essentials->cmd_buffer, 327 | .signalSemaphoreCount = 1, 328 | .pSignalSemaphores = &essentials->sem_pre_submit, 329 | }; 330 | vkQueueSubmit(essentials->present_queue, 1, &submit_info, essentials->exec_fence); 331 | 332 | /* Use `vkQueuePresentKHR` to give the image back for presentation */ 333 | VkPresentInfoKHR present_info = { 334 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 335 | .waitSemaphoreCount = 1, 336 | .pWaitSemaphores = &essentials->sem_pre_submit, 337 | .swapchainCount = 1, 338 | .pSwapchains = &swapchain->swapchain, 339 | .pImageIndices = &image_index, 340 | }; 341 | res = vkQueuePresentKHR(essentials->present_queue, &present_info); 342 | tut1_error_set_vkresult(&retval, res); 343 | if (res < 0) 344 | { 345 | tut1_error_printf(&retval, "Failed to queue image for presentation\n"); 346 | return -1; 347 | } 348 | 349 | return 0; 350 | } 351 | -------------------------------------------------------------------------------- /tut7/tut7_render.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT7_RENDER_H 21 | #define TUT7_RENDER_H 22 | 23 | #include "tut7.h" 24 | 25 | struct tut7_render_essentials 26 | { 27 | VkImage *images; /* Images from the swapchain */ 28 | uint32_t image_count; /* Number of `images` */ 29 | VkQueue present_queue; /* The queue to present to */ 30 | VkCommandBuffer cmd_buffer; /* The command buffer to render to */ 31 | 32 | VkSemaphore sem_post_acquire; /* The post-acquire semaphore */ 33 | VkSemaphore sem_pre_submit; /* The pre-submit semaphore */ 34 | 35 | VkFence exec_fence; /* The fence indicating when a command buffer is finished executing */ 36 | bool first_render; /* Whether this is the first render */ 37 | }; 38 | 39 | int tut7_render_get_essentials(struct tut7_render_essentials *essentials, struct tut1_physical_device *phy_dev, 40 | struct tut2_device *dev, struct tut6_swapchain *swapchain); 41 | void tut7_render_cleanup_essentials(struct tut7_render_essentials *essentials, struct tut2_device *dev); 42 | 43 | /* 44 | * Acquire an image from the swapchain, reset the command buffer, start recording, perform layout transition from 45 | * undefined to to_layout. 46 | */ 47 | int tut7_render_start(struct tut7_render_essentials *essentials, struct tut2_device *dev, 48 | struct tut6_swapchain *swapchain, VkImageLayout to_layout, uint32_t *image_index); 49 | /* 50 | * Perform layout transition from from_layout to present src, stop recording, submit to queue for rendering, submit to 51 | * presentation engine for presentation. 52 | */ 53 | int tut7_render_finish(struct tut7_render_essentials *essentials, struct tut2_device *dev, 54 | struct tut6_swapchain *swapchain, VkImageLayout from_layout, uint32_t image_index); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /tut8/tut8.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT8_H 21 | #define TUT8_H 22 | 23 | #include "../tut7/tut7.h" 24 | 25 | struct tut8_resources 26 | { 27 | /* list of images */ 28 | struct tut7_image *images; 29 | uint32_t image_count; 30 | 31 | /* list of buffers */ 32 | struct tut7_buffer *buffers; 33 | uint32_t buffer_count; 34 | 35 | /* shaders */ 36 | struct tut7_shader *shaders; 37 | uint32_t shader_count; 38 | 39 | /* push constants */ 40 | VkPushConstantRange *push_constants; 41 | uint32_t push_constant_count; 42 | 43 | /* description of buffers to render to */ 44 | VkRenderPass render_pass; 45 | }; 46 | 47 | struct tut8_layout 48 | { 49 | /* inputs */ 50 | 51 | struct tut8_resources *resources; 52 | 53 | /* outputs */ 54 | 55 | /* layouts based on resources */ 56 | VkDescriptorSetLayout set_layout; 57 | VkPipelineLayout pipeline_layout; 58 | }; 59 | 60 | struct tut8_pipeline 61 | { 62 | /* inputs */ 63 | 64 | struct tut8_layout *layout; 65 | 66 | VkPipelineVertexInputStateCreateInfo vertex_input_state; 67 | VkPipelineInputAssemblyStateCreateInfo input_assembly_state; 68 | VkPipelineTessellationStateCreateInfo tessellation_state; 69 | 70 | size_t thread_count; 71 | 72 | /* outputs */ 73 | 74 | /* one pipeline per layout (i.e. set of resources) */ 75 | VkPipeline pipeline; 76 | 77 | /* pool to allocate from */ 78 | VkDescriptorPool set_pool; 79 | }; 80 | 81 | tut1_error tut8_make_graphics_layouts(struct tut2_device *dev, struct tut8_layout *layouts, uint32_t layout_count); 82 | tut1_error tut8_make_graphics_pipelines(struct tut2_device *dev, struct tut8_pipeline *pipelines, uint32_t pipeline_count); 83 | 84 | void tut8_free_layouts(struct tut2_device *dev, struct tut8_layout *layouts, uint32_t layout_count); 85 | void tut8_free_pipelines(struct tut2_device *dev, struct tut8_pipeline *pipelines, uint32_t pipeline_count); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /tut8/tut8_render.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #include "tut8_render.h" 21 | 22 | static tut1_error fill_object(struct tut2_device *dev, VkDeviceMemory to, void *from, size_t size, const char *object, const char *name) 23 | { 24 | void *mem = NULL; 25 | tut1_error retval = TUT1_ERROR_NONE; 26 | VkResult res; 27 | 28 | res = vkMapMemory(dev->device, to, 0, size, 0, &mem); 29 | tut1_error_set_vkresult(&retval, res); 30 | if (res) 31 | { 32 | tut1_error_printf(&retval, "Failed to map memory of the %s %s\n", name, object); 33 | goto exit_failed; 34 | } 35 | 36 | memcpy(mem, from, size); 37 | 38 | vkUnmapMemory(dev->device, to); 39 | 40 | exit_failed: 41 | return retval; 42 | } 43 | 44 | tut1_error tut8_render_fill_buffer(struct tut2_device *dev, struct tut7_buffer *to, void *from, size_t size, const char *name) 45 | { 46 | return fill_object(dev, to->buffer_mem, from, size, "buffer", name); 47 | } 48 | 49 | tut1_error tut8_render_fill_image(struct tut2_device *dev, struct tut7_image *to, void *from, size_t size, const char *name) 50 | { 51 | return fill_object(dev, to->image_mem, from, size, "image", name); 52 | } 53 | 54 | static tut1_error copy_object_start(struct tut2_device *dev, struct tut7_render_essentials *essentials, const char *object, const char *name) 55 | { 56 | tut1_error retval = TUT1_ERROR_NONE; 57 | VkResult res; 58 | 59 | vkResetCommandBuffer(essentials->cmd_buffer, 0); 60 | VkCommandBufferBeginInfo begin_info = { 61 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 62 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 63 | }; 64 | res = vkBeginCommandBuffer(essentials->cmd_buffer, &begin_info); 65 | tut1_error_set_vkresult(&retval, res); 66 | if (res) 67 | tut1_error_printf(&retval, "Couldn't begin recording a command buffer to copy the %s %s\n", name, object); 68 | 69 | return retval; 70 | } 71 | 72 | static tut1_error copy_object_end(struct tut2_device *dev, struct tut7_render_essentials *essentials) 73 | { 74 | tut1_error retval = TUT1_ERROR_NONE; 75 | VkResult res; 76 | 77 | vkEndCommandBuffer(essentials->cmd_buffer); 78 | 79 | res = vkResetFences(dev->device, 1, &essentials->exec_fence); 80 | tut1_error_set_vkresult(&retval, res); 81 | if (res) 82 | { 83 | tut1_error_printf(&retval, "Failed to reset fence\n"); 84 | goto exit_failed; 85 | } 86 | 87 | /* Submit the command buffer to go ahead with the copy, and wait for it to finish */ 88 | VkSubmitInfo submit_info = { 89 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 90 | .commandBufferCount = 1, 91 | .pCommandBuffers = &essentials->cmd_buffer, 92 | }; 93 | 94 | vkQueueSubmit(essentials->present_queue, 1, &submit_info, essentials->exec_fence); 95 | res = vkWaitForFences(dev->device, 1, &essentials->exec_fence, true, 1000000000); 96 | tut1_error_set_vkresult(&retval, res); 97 | 98 | exit_failed: 99 | return retval; 100 | } 101 | 102 | tut1_error tut8_render_copy_buffer(struct tut2_device *dev, struct tut7_render_essentials *essentials, 103 | struct tut7_buffer *to, struct tut7_buffer *from, size_t size, const char *name) 104 | { 105 | tut1_error retval = TUT1_ERROR_NONE; 106 | 107 | retval = copy_object_start(dev, essentials, "buffer", name); 108 | if (!tut1_error_is_success(&retval)) 109 | return retval; 110 | 111 | /* Let's see if you can figure out this very complicated operation! */ 112 | VkBufferCopy copy_region = { 113 | .srcOffset = 0, 114 | .dstOffset = 0, 115 | .size = size, 116 | }; 117 | vkCmdCopyBuffer(essentials->cmd_buffer, from->buffer, to->buffer, 1, ©_region); 118 | 119 | return copy_object_end(dev, essentials); 120 | } 121 | 122 | tut1_error tut8_render_copy_image(struct tut2_device *dev, struct tut7_render_essentials *essentials, 123 | struct tut7_image *to, VkImageLayout to_layout, struct tut7_image *from, VkImageLayout from_layout, 124 | VkImageCopy *region, const char *name) 125 | { 126 | tut1_error retval = TUT1_ERROR_NONE; 127 | 128 | retval = copy_object_start(dev, essentials, "image", name); 129 | if (!tut1_error_is_success(&retval)) 130 | return retval; 131 | 132 | /* Note that vkCmdCopyImage doesn't do a layout transition. It just needs to know the layouts to do the copy. */ 133 | vkCmdCopyImage(essentials->cmd_buffer, from->image, from_layout, to->image, to_layout, 1, region); 134 | 135 | return copy_object_end(dev, essentials); 136 | } 137 | 138 | tut1_error tut8_render_copy_buffer_to_image(struct tut2_device *dev, struct tut7_render_essentials *essentials, 139 | struct tut7_image *to, VkImageLayout to_layout, struct tut7_buffer *from, 140 | VkBufferImageCopy *region, const char *name) 141 | { 142 | tut1_error retval = TUT1_ERROR_NONE; 143 | 144 | retval = copy_object_start(dev, essentials, "image", name); 145 | if (!tut1_error_is_success(&retval)) 146 | return retval; 147 | 148 | vkCmdCopyBufferToImage(essentials->cmd_buffer, from->buffer, to->image, to_layout, 1, region); 149 | 150 | return copy_object_end(dev, essentials); 151 | } 152 | 153 | tut1_error tut8_render_copy_image_to_buffer(struct tut2_device *dev, struct tut7_render_essentials *essentials, 154 | struct tut7_buffer *to, struct tut7_image *from, VkImageLayout from_layout, 155 | VkBufferImageCopy *region, const char *name) 156 | { 157 | tut1_error retval = TUT1_ERROR_NONE; 158 | 159 | retval = copy_object_start(dev, essentials, "buffer", name); 160 | if (!tut1_error_is_success(&retval)) 161 | return retval; 162 | 163 | vkCmdCopyImageToBuffer(essentials->cmd_buffer, from->image, from_layout, to->buffer, 1, region); 164 | 165 | return copy_object_end(dev, essentials); 166 | } 167 | 168 | tut1_error tut8_render_transition_images(struct tut2_device *dev, struct tut7_render_essentials *essentials, 169 | struct tut7_image *images, uint32_t image_count, 170 | VkImageLayout from, VkImageLayout to, VkImageAspectFlags aspect, const char *name) 171 | { 172 | tut1_error retval = TUT1_ERROR_NONE; 173 | VkResult res; 174 | 175 | vkResetCommandBuffer(essentials->cmd_buffer, 0); 176 | VkCommandBufferBeginInfo begin_info = { 177 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 178 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 179 | }; 180 | res = vkBeginCommandBuffer(essentials->cmd_buffer, &begin_info); 181 | tut1_error_set_vkresult(&retval, res); 182 | if (res) 183 | { 184 | tut1_error_printf(&retval, "Couldn't begin recording a command buffer to transition the %s image\n", name); 185 | goto exit_failed; 186 | } 187 | 188 | /* 189 | * We have already seen how image transition is done in Tutorial 7. This is very similar, and in fact simpler, 190 | * because we are only doing the transition in this command buffer submission. In other words, we don't need 191 | * to think about pipeline stages, or src and dst accesses. 192 | */ 193 | VkImageMemoryBarrier image_barrier = { 194 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 195 | .srcAccessMask = 0, 196 | .dstAccessMask = 0, 197 | .oldLayout = from, 198 | .newLayout = to, 199 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 200 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 201 | .subresourceRange = { 202 | .aspectMask = aspect, 203 | .baseMipLevel = 0, 204 | .levelCount = VK_REMAINING_MIP_LEVELS, 205 | .baseArrayLayer = 0, 206 | .layerCount = VK_REMAINING_ARRAY_LAYERS, 207 | }, 208 | }; 209 | 210 | for (uint32_t i = 0; i < image_count; ++i) 211 | { 212 | image_barrier.image = images[i].image; 213 | vkCmdPipelineBarrier(essentials->cmd_buffer, 214 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 215 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 216 | 0, /* no flags */ 217 | 0, NULL, /* no memory barriers */ 218 | 0, NULL, /* no buffer barriers */ 219 | 1, &image_barrier); /* our image transition */ 220 | } 221 | 222 | vkEndCommandBuffer(essentials->cmd_buffer); 223 | 224 | res = vkResetFences(dev->device, 1, &essentials->exec_fence); 225 | tut1_error_set_vkresult(&retval, res); 226 | if (res) 227 | { 228 | tut1_error_printf(&retval, "Failed to reset fence\n"); 229 | goto exit_failed; 230 | } 231 | 232 | /* Submit the command buffer to go ahead with the copy, and wait for it to finish */ 233 | VkSubmitInfo submit_info = { 234 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 235 | .commandBufferCount = 1, 236 | .pCommandBuffers = &essentials->cmd_buffer, 237 | }; 238 | 239 | vkQueueSubmit(essentials->present_queue, 1, &submit_info, essentials->exec_fence); 240 | res = vkWaitForFences(dev->device, 1, &essentials->exec_fence, true, 1000000000); 241 | tut1_error_set_vkresult(&retval, res); 242 | 243 | exit_failed: 244 | return retval; 245 | } 246 | -------------------------------------------------------------------------------- /tut8/tut8_render.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Shahbaz Youssefi 3 | * 4 | * This file is part of Shabi's Vulkan Tutorials. 5 | * 6 | * Shabi's Vulkan Tutorials is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Shabi's Vulkan Tutorials is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Shabi's Vulkan Tutorials. If not, see . 18 | */ 19 | 20 | #ifndef TUT8_RENDER_H 21 | #define TUT8_RENDER_H 22 | 23 | #include "tut8.h" 24 | #include "../tut7/tut7_render.h" 25 | 26 | /* Fill the contents of a host-visible buffer/image with arbitrary data */ 27 | tut1_error tut8_render_fill_buffer(struct tut2_device *dev, struct tut7_buffer *to, void *from, size_t size, const char *name); 28 | tut1_error tut8_render_fill_image(struct tut2_device *dev, struct tut7_image *to, void *from, size_t size, const char *name); 29 | 30 | /* 31 | * Copy a buffer/image to another, for example from a host-visible one to a device-local one. This uses a command 32 | * buffer, submits it, and waits for it to finish, so it's not supposed to be used while recording a command buffer. 33 | */ 34 | tut1_error tut8_render_copy_buffer(struct tut2_device *dev, struct tut7_render_essentials *essentials, 35 | struct tut7_buffer *to, struct tut7_buffer *from, size_t size, const char *name); 36 | tut1_error tut8_render_copy_image(struct tut2_device *dev, struct tut7_render_essentials *essentials, 37 | struct tut7_image *to, VkImageLayout to_layout, struct tut7_image *from, VkImageLayout from_layout, 38 | VkImageCopy *region, const char *name); 39 | tut1_error tut8_render_copy_buffer_to_image(struct tut2_device *dev, struct tut7_render_essentials *essentials, 40 | struct tut7_image *to, VkImageLayout to_layout, struct tut7_buffer *from, 41 | VkBufferImageCopy *region, const char *name); 42 | tut1_error tut8_render_copy_image_to_buffer(struct tut2_device *dev, struct tut7_render_essentials *essentials, 43 | struct tut7_buffer *to, struct tut7_image *from, VkImageLayout from_layout, 44 | VkBufferImageCopy *region, const char *name); 45 | 46 | /* 47 | * 48 | * Transition an image to a new layout. This uses a command buffer, submits it, and waits for it to finish, so it's 49 | * not supposed to be used while recording a command buffer. 50 | */ 51 | tut1_error tut8_render_transition_images(struct tut2_device *dev, struct tut7_render_essentials *essentials, 52 | struct tut7_image *images, uint32_t image_count, 53 | VkImageLayout from, VkImageLayout to, VkImageAspectFlags aspect, const char *name); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /tut9/README.md: -------------------------------------------------------------------------------- 1 | Beyond Tutorial 8 2 | ================= 3 | 4 | By Tutorial 8, we went through most of what Vulkan has to offer in their most 5 | primal form. At this point, one should be able to apply their computer 6 | graphics knowledge and use Vulkan, figuring out any details along the way. 7 | 8 | That said, the tutorials from here on focus on some of these details, and are 9 | results of my own experiments with Vulkan. 10 | 11 | If you feel you have learned enough of Vulkan, Tutorial 8 is the best point to 12 | stop. 13 | --------------------------------------------------------------------------------