├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── cmake ├── FindVulkan.cmake └── FindXCB.cmake ├── external └── CMakeLists.txt ├── img ├── blade_model.jpg ├── cube_demo.png └── grass.gif └── src ├── Blades.cpp ├── Blades.h ├── BufferUtils.cpp ├── BufferUtils.h ├── CMakeLists.txt ├── Camera.cpp ├── Camera.h ├── Device.cpp ├── Device.h ├── Image.cpp ├── Image.h ├── Instance.cpp ├── Instance.h ├── Model.cpp ├── Model.h ├── QueueFlags.h ├── Renderer.cpp ├── Renderer.h ├── Scene.cpp ├── Scene.h ├── ShaderModule.cpp ├── ShaderModule.h ├── SwapChain.cpp ├── SwapChain.h ├── Vertex.h ├── Window.cpp ├── Window.h ├── images └── grass.jpg ├── main.cpp └── shaders ├── compute.comp ├── graphics.frag ├── graphics.vert ├── grass.frag ├── grass.tesc ├── grass.tese └── grass.vert /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/GLFW"] 2 | path = external/GLFW 3 | url = https://github.com/glfw/glfw.git 4 | [submodule "external/glm"] 5 | path = external/glm 6 | url = https://github.com/g-truc/glm.git 7 | [submodule "external/stb"] 8 | path = external/stb 9 | url = https://github.com/nothings/stb.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") 3 | 4 | project(cis565_project6_vulkan_grass_rendering) 5 | 6 | OPTION(USE_D2D_WSI "Build the project using Direct to Display swapchain" OFF) 7 | 8 | find_package(Vulkan REQUIRED) 9 | 10 | IF(WIN32) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WIN32_KHR") 12 | ELSE(WIN32) 13 | find_package(Threads REQUIRED) 14 | IF(USE_D2D_WSI) 15 | MESSAGE("Using direct to display extension...") 16 | add_definitions(-D_DIRECT2DISPLAY) 17 | ELSE(USE_D2D_WSI) 18 | find_package(XCB REQUIRED) 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_XCB_KHR") 20 | ENDIF(USE_D2D_WSI) 21 | # Todo : android? 22 | ENDIF(WIN32) 23 | 24 | # Set preprocessor defines 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOMINMAX -D_USE_MATH_DEFINES") 26 | 27 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 28 | add_definitions(-std=c++11) 29 | 30 | # Enable the creation of folders for Visual Studio projects 31 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 32 | 33 | function(ExternalTarget folder target) 34 | set_property(TARGET ${target} PROPERTY FOLDER ${folder}) 35 | endfunction(ExternalTarget) 36 | 37 | function(InternalTarget folder target) 38 | ExternalTarget("${folder}" ${target}) 39 | if (MSVC) 40 | get_target_property(targetSources ${target} SOURCES) 41 | foreach(sourceFile IN ITEMS ${targetSources}) 42 | if (IS_ABSOLUTE "${sourceFile}") 43 | file(RELATIVE_PATH sourceFile "${CMAKE_CURRENT_SOURCE_DIR}" "${sourceFile}") 44 | endif() 45 | get_filename_component(sourceDir "${sourceFile}" PATH) 46 | string(REPLACE "/" "\\" sourceDir "${sourceDir}") 47 | source_group("${sourceDir}" FILES "${sourceFile}") 48 | endforeach() 49 | endif() 50 | endfunction(InternalTarget) 51 | 52 | # Compiler specific stuff 53 | IF(MSVC) 54 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") 55 | ENDIF(MSVC) 56 | 57 | IF(WIN32) 58 | # Nothing here (yet) 59 | ELSE(WIN32) 60 | link_libraries(${XCB_LIBRARIES} ${VULKAN_LIB}) 61 | ENDIF(WIN32) 62 | 63 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/") 64 | 65 | add_subdirectory(external) 66 | add_subdirectory(src) 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Instructions - Vulkan Grass Rendering 2 | ======================== 3 | 4 | This is due **Sunday 11/5, evening at midnight**. 5 | 6 | **Summary:** 7 | In this project, you will use Vulkan to implement a grass simulator and renderer. You will 8 | use compute shaders to perform physics calculations on Bezier curves that represent individual 9 | grass blades in your application. Since rendering every grass blade on every frame will is fairly 10 | inefficient, you will also use compute shaders to cull grass blades that don't contribute to a given frame. 11 | The remaining blades will be passed to a graphics pipeline, in which you will write several shaders. 12 | You will write a vertex shader to transform Bezier control points, tessellation shaders to dynamically create 13 | the grass geometry from the Bezier curves, and a fragment shader to shade the grass blades. 14 | 15 | The base code provided includes all of the basic Vulkan setup, including a compute pipeline that will run your compute 16 | shaders and two graphics pipelines, one for rendering the geometry that grass will be placed on and the other for 17 | rendering the grass itself. Your job will be to write the shaders for the grass graphics pipeline and the compute pipeline, 18 | as well as binding any resources (descriptors) you may need to accomplish the tasks described in this assignment. 19 | 20 | ![](img/grass.gif) 21 | 22 | You are not required to use this base code if you don't want 23 | to. You may also change any part of the base code as you please. 24 | **This is YOUR project.** The above .gif is just a simple example that you 25 | can use as a reference to compare to. 26 | 27 | **Important:** 28 | - If you are not in CGGT/DMD, you may replace this project with a GPU compute 29 | project. You MUST get this pre-approved by Austin Eng before continuing! 30 | 31 | ### Contents 32 | 33 | * `src/` C++/Vulkan source files. 34 | * `shaders/` glsl shader source files 35 | * `images/` images used as textures within graphics pipelines 36 | * `external/` Includes and static libraries for 3rd party libraries. 37 | * `img/` Screenshots and images to use in your READMEs 38 | 39 | ### Installing Vulkan 40 | 41 | In order to run a Vulkan project, you first need to download and install the [Vulkan SDK](https://vulkan.lunarg.com/). 42 | Make sure to run the downloaded installed as administrator so that the installer can set the appropriate environment 43 | variables for you. 44 | 45 | Once you have done this, you need to make sure your GPU driver supports Vulkan. Download and install a 46 | [Vulkan driver](https://developer.nvidia.com/vulkan-driver) from NVIDIA's website. 47 | 48 | Finally, to check that Vulkan is ready for use, go to your Vulkan SDK directory (`C:/VulkanSDK/` unless otherwise specified) 49 | and run the `cube.exe` example within the `Bin` directory. IF you see a rotating gray cube with the LunarG logo, then you 50 | are all set! 51 | 52 | ### Running the code 53 | 54 | While developing your grass renderer, you will want to keep validation layers enabled so that error checking is turned on. 55 | The project is set up such that when you are in `debug` mode, validation layers are enabled, and when you are in `release` mode, 56 | validation layers are disabled. After building the code, you should be able to run the project without any errors. You will see 57 | a plane with a grass texture on it to begin with. 58 | 59 | ![](img/cube_demo.png) 60 | 61 | ## Requirements 62 | 63 | **Ask on the mailing list for any clarifications.** 64 | 65 | In this project, you are given the following code: 66 | 67 | * The basic setup for a Vulkan project, including the swapchain, physical device, logical device, and the pipelines described above. 68 | * Structs for some of the uniform buffers you will be using. 69 | * Some buffer creation utility functions. 70 | * A simple interactive camera using the mouse. 71 | 72 | You need to implement the following features/pipeline stages: 73 | 74 | * Compute shader (`shaders/compute.comp`) 75 | * Grass pipeline stages 76 | * Vertex shader (`shaders/grass.vert') 77 | * Tessellation control shader (`shaders/grass.tesc`) 78 | * Tessellation evaluation shader (`shaders/grass.tese`) 79 | * Fragment shader (`shaders/grass.frag`) 80 | * Binding of any extra descriptors you may need 81 | 82 | See below for more guidance. 83 | 84 | ## Base Code Tour 85 | 86 | Areas that you need to complete are 87 | marked with a `TODO` comment. Functions that are useful 88 | for reference are marked with the comment `CHECKITOUT`. 89 | 90 | * `src/main.cpp` is the entry point of our application. 91 | * `src/Instance.cpp` sets up the application state, initializes the Vulkan library, and contains functions that will create our 92 | physical and logical device handles. 93 | * `src/Device.cpp` manages the logical device and sets up the queues that our command buffers will be submitted to. 94 | * `src/Renderer.cpp` contains most of the rendering implementation, including Vulkan setup and resource creation. You will 95 | likely have to make changes to this file in order to support changes to your pipelines. 96 | * `src/Camera.cpp` manages the camera state. 97 | * `src/Model.cpp` manages the state of the model that grass will be created on. Currently a plane is hardcoded, but feel free to 98 | update this with arbitrary model loading! 99 | * `src/Blades.cpp` creates the control points corresponding to the grass blades. There are many parameters that you can play with 100 | here that will change the behavior of your rendered grass blades. 101 | * `src/Scene.cpp` manages the scene state, including the model, blades, and simualtion time. 102 | * `src/BufferUtils.cpp` provides helper functions for creating buffers to be used as descriptors. 103 | 104 | We left out descriptions for a couple files that you likely won't have to modify. Feel free to investigate them to understand their 105 | importance within the scope of the project. 106 | 107 | ## Grass Rendering 108 | 109 | This project is an implementation of the paper, [Responsive Real-Time Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf). 110 | Please make sure to use this paper as a primary resource while implementing your grass renderers. It does a great job of explaining 111 | the key algorithms and math you will be using. Below is a brief description of the different components in chronological order of how your renderer will 112 | execute, but feel free to develop the components in whatever order you prefer. 113 | 114 | ### Representing Grass as Bezier Curves 115 | 116 | In this project, grass blades will be represented as Bezier curves while performing physics calculations and culling operations. 117 | Each Bezier curve has three control points. 118 | * `v0`: the position of the grass blade on the geomtry 119 | * `v1`: a Bezier curve guide that is always "above" `v0` with respect to the grass blade's up vector (explained soon) 120 | * `v2`: a physical guide for which we simulate forces on 121 | 122 | We also need to store per-blade characteristics that will help us simulate and tessellate our grass blades correctly. 123 | * `up`: the blade's up vector, which corresponds to the normal of the geometry that the grass blade resides on at `v0` 124 | * Orientation: the orientation of the grass blade's face 125 | * Height: the height of the grass blade 126 | * Width: the width of the grass blade's face 127 | * Stiffness coefficient: the stiffness of our grass blade, which will affect the force computations on our blade 128 | 129 | We can pack all this data into four `vec4`s, such that `v0.w` holds orientation, `v1.w` holds height, `v2.w` holds width, and 130 | `up.w` holds the stiffness coefficient. 131 | 132 | ![](img/blade_model.jpg) 133 | 134 | ### Simulating Forces 135 | 136 | In this project, you will be simulating forces on grass blades while they are still Bezier curves. This will be done in a compute 137 | shader using the compute pipeline that has been created for you. Remember that `v2` is our physical guide, so we will be 138 | applying transformations to `v2` initially, then correcting for potential errors. We will finally update `v1` to maintain the appropriate 139 | length of our grass blade. 140 | 141 | #### Binding Resources 142 | 143 | In order to update the state of your grass blades on every frame, you will need to create a storage buffer to maintain the grass data. 144 | You will also need to pass information about how much time has passed in the simulation and the time since the last frame. To do this, 145 | you can extend or create descriptor sets that will be bound to the compute pipeline. 146 | 147 | #### Gravity 148 | 149 | Given a gravity direction, `D.xyz`, and the magnitude of acceleration, `D.w`, we can compute the environmental gravity in 150 | our scene as `gE = normalize(D.xyz) * D.w`. 151 | 152 | We then determine the contribution of the gravity with respect to the front facing direction of the blade, `f`, 153 | as a term called the "front gravity". Front gravity is computed as `gF = (1/4) * ||gE|| * f`. 154 | 155 | We can then determine the total gravity on the grass blade as `g = gE + gF`. 156 | 157 | #### Recovery 158 | 159 | Recovery corresponds to the counter-force that brings our grass blade back into equilibrium. This is derived in the paper using Hooke's law. 160 | In order to determine the recovery force, we need to compare the current position of `v2` to its original position before 161 | simulation started, `iv2`. At the beginning of our simulation, `v1` and `v2` are initialized to be a distance of the blade height along the `up` vector. 162 | 163 | Once we have `iv2`, we can compute the recovery forces as `r = (iv2 - v2) * stiffness`. 164 | 165 | #### Wind 166 | 167 | In order to simulate wind, you are at liberty to create any wind function you want! In order to have something interesting, 168 | you can make the function depend on the position of `v0` and a function that changes with time. Consider using some combination 169 | of sine or cosine functions. 170 | 171 | Your wind function will determine a wind direction that is affecting the blade, but it is also worth noting that wind has a larger impact on 172 | grass blades whose forward directions are parallel to the wind direction. The paper describes this as a "wind alignment" term. We won't go 173 | over the exact math here, but use the paper as a reference when implementing this. It does a great job of explaining this! 174 | 175 | Once you have a wind direction and a wind alignment term, your total wind force (`w`) will be `windDirection * windAlignment`. 176 | 177 | #### Total force 178 | 179 | We can then determine a translation for `v2` based on the forces as `tv2 = (gravity + recovery + wind) * deltaTime`. However, we can't simply 180 | apply this translation and expect the simulation to be robust. Our forces might push `v2` under the ground! Similarly, moving `v2` but leaving 181 | `v1` in the same position will cause our grass blade to change length, which doesn't make sense. 182 | 183 | Read section 5.2 of the paper in order to learn how to determine the corrected final positions for `v1` and `v2`. 184 | 185 | ### Culling tests 186 | 187 | Although we need to simulate forces on every grass blade at every frame, there are many blades that we won't need to render 188 | due to a variety of reasons. Here are some heuristics we can use to cull blades that won't contribute positively to a given frame. 189 | 190 | #### Orientation culling 191 | 192 | Consider the scenario in which the front face direction of the grass blade is perpendicular to the view vector. Since our grass blades 193 | won't have width, we will end up trying to render parts of the grass that are actually smaller than the size of a pixel. This could 194 | lead to aliasing artifacts. 195 | 196 | In order to remedy this, we can cull these blades! Simply do a dot product test to see if the view vector and front face direction of 197 | the blade are perpendicular. The paper uses a threshold value of `0.9` to cull, but feel free to use what you think looks best. 198 | 199 | #### View-frustum culling 200 | 201 | We also want to cull blades that are outside of the view-frustum, considering they won't show up in the frame anyway. To determine if 202 | a grass blade is in the view-frustum, we want to compare the visibility of three points: `v0, v2, and m`, where `m = (1/4)v0 * (1/2)v1 * (1/4)v2`. 203 | Notice that we aren't using `v1` for the visibility test. This is because the `v1` is a Bezier guide that doesn't represent a position on the grass blade. 204 | We instead use `m` to approximate the midpoint of our Bezier curve. 205 | 206 | If all three points are outside of the view-frustum, we will cull the grass blade. The paper uses a tolerance value for this test so that we are culling 207 | blades a little more conservatively. This can help with cases in which the Bezier curve is technically not visible, but we might be able to see the blade 208 | if we consider its width. 209 | 210 | #### Distance culling 211 | 212 | Similarly to orientation culling, we can end up with grass blades that at large distances are smaller than the size of a pixel. This could lead to additional 213 | artifacts in our renders. In this case, we can cull grass blades as a function of their distance from the camera. 214 | 215 | You are free to define two parameters here. 216 | * A max distance afterwhich all grass blades will be culled. 217 | * A number of buckets to place grass blades between the camera and max distance into. 218 | 219 | Define a function such that the grass blades in the bucket closest to the camera are kept while an increasing number of grass blades 220 | are culled with each farther bucket. 221 | 222 | #### Occlusion culling (extra credit) 223 | 224 | This type of culling only makes sense if our scene has additional objects aside from the plane and the grass blades. We want to cull grass blades that 225 | are occluded by other geometry. Think about how you can use a depth map to accomplish this! 226 | 227 | ### Tessellating Bezier curves into grass blades 228 | 229 | In this project, you should pass in each Bezier curve as a single patch to be processed by your grass graphics pipeline. You will tessellate this patch into 230 | a quad with a shape of your choosing (as long as it looks sufficiently like grass of course). The paper has some examples of grass shapes you can use as inspiration. 231 | 232 | In the tessellation control shader, specify the amount of tessellation you want to occur. Remember that you need to provide enough detail to create the curvature of a grass blade. 233 | 234 | The generated vertices will be passed to the tessellation evaluation shader, where you will place the vertices in world space, respecting the width, height, and orientation information 235 | of each blade. Once you have determined the world space position of each vector, make sure to set the output `gl_Position` in clip space! 236 | 237 | ** Extra Credit**: Tessellate to varying levels of detail as a function of how far the grass blade is from the camera. For example, if the blade is very far, only generate four vertices in the tessellation control shader. 238 | 239 | To build more intuition on how tessellation works, I highly recommend playing with the [helloTessellation sample](https://github.com/CIS565-Fall-2017/Vulkan-Samples/tree/master/samples/5_helloTessellation) 240 | and reading this [tutorial on tessellation](http://in2gpu.com/2014/07/12/tessellation-tutorial-opengl-4-3/). 241 | 242 | ## Resources 243 | 244 | ### Links 245 | 246 | The following resources may be useful for this project. 247 | 248 | * [Responsive Real-Time Grass Grass Rendering for General 3D Scenes](https://www.cg.tuwien.ac.at/research/publications/2017/JAHRMANN-2017-RRTG/JAHRMANN-2017-RRTG-draft.pdf) 249 | * [CIS565 Vulkan samples](https://github.com/CIS565-Fall-2017/Vulkan-Samples) 250 | * [Official Vulkan documentation](https://www.khronos.org/registry/vulkan/) 251 | * [Vulkan tutorial](https://vulkan-tutorial.com/) 252 | * [RenderDoc blog on Vulkan](https://renderdoc.org/vulkan-in-30-minutes.html) 253 | * [Tessellation tutorial](http://in2gpu.com/2014/07/12/tessellation-tutorial-opengl-4-3/) 254 | 255 | 256 | ## Third-Party Code Policy 257 | 258 | * Use of any third-party code must be approved by asking on our Google Group. 259 | * If it is approved, all students are welcome to use it. Generally, we approve 260 | use of third-party code that is not a core part of the project. For example, 261 | for the path tracer, we would approve using a third-party library for loading 262 | models, but would not approve copying and pasting a CUDA function for doing 263 | refraction. 264 | * Third-party code **MUST** be credited in README.md. 265 | * Using third-party code without its approval, including using another 266 | student's code, is an academic integrity violation, and will, at minimum, 267 | result in you receiving an F for the semester. 268 | 269 | 270 | ## README 271 | 272 | * A brief description of the project and the specific features you implemented. 273 | * At least one screenshot of your project running. 274 | * A performance analysis (described below). 275 | 276 | ### Performance Analysis 277 | 278 | The performance analysis is where you will investigate how... 279 | * Your renderer handles varying numbers of grass blades 280 | * The improvement you get by culling using each of the three culling tests 281 | 282 | ## Submit 283 | 284 | If you have modified any of the `CMakeLists.txt` files at all (aside from the 285 | list of `SOURCE_FILES`), mentions it explicity. 286 | Beware of any build issues discussed on the Google Group. 287 | 288 | Open a GitHub pull request so that we can see that you have finished. 289 | The title should be "Project 6: YOUR NAME". 290 | The template of the comment section of your pull request is attached below, you can do some copy and paste: 291 | 292 | * [Repo Link](https://link-to-your-repo) 293 | * (Briefly) Mentions features that you've completed. Especially those bells and whistles you want to highlight 294 | * Feature 0 295 | * Feature 1 296 | * ... 297 | * Feedback on the project itself, if any. 298 | -------------------------------------------------------------------------------- /cmake/FindVulkan.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #.rst: 5 | # FindVulkan 6 | # ---------- 7 | # 8 | # Try to find Vulkan 9 | # 10 | # IMPORTED Targets 11 | # ^^^^^^^^^^^^^^^^ 12 | # 13 | # This module defines :prop_tgt:`IMPORTED` target ``Vulkan::Vulkan``, if 14 | # Vulkan has been found. 15 | # 16 | # Result Variables 17 | # ^^^^^^^^^^^^^^^^ 18 | # 19 | # This module defines the following variables:: 20 | # 21 | # Vulkan_FOUND - True if Vulkan was found 22 | # Vulkan_INCLUDE_DIRS - include directories for Vulkan 23 | # Vulkan_LIBRARIES - link against this library to use Vulkan 24 | # 25 | # The module will also define two cache variables:: 26 | # 27 | # Vulkan_INCLUDE_DIR - the Vulkan include directory 28 | # Vulkan_LIBRARY - the path to the Vulkan library 29 | # 30 | 31 | if(WIN32) 32 | find_path(Vulkan_INCLUDE_DIR 33 | NAMES vulkan/vulkan.h 34 | PATHS 35 | "$ENV{VULKAN_SDK}/Include" 36 | ) 37 | 38 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 39 | find_library(Vulkan_LIBRARY 40 | NAMES vulkan-1 41 | PATHS 42 | "$ENV{VULKAN_SDK}/Lib" 43 | "$ENV{VULKAN_SDK}/Bin" 44 | ) 45 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) 46 | find_library(Vulkan_LIBRARY 47 | NAMES vulkan-1 48 | PATHS 49 | "$ENV{VULKAN_SDK}/Lib32" 50 | "$ENV{VULKAN_SDK}/Bin32" 51 | NO_SYSTEM_ENVIRONMENT_PATH 52 | ) 53 | endif() 54 | else() 55 | find_path(Vulkan_INCLUDE_DIR 56 | NAMES vulkan/vulkan.h 57 | PATHS 58 | "$ENV{VULKAN_SDK}/include") 59 | find_library(Vulkan_LIBRARY 60 | NAMES vulkan 61 | PATHS 62 | "$ENV{VULKAN_SDK}/lib") 63 | endif() 64 | 65 | set(Vulkan_LIBRARIES ${Vulkan_LIBRARY}) 66 | set(Vulkan_INCLUDE_DIRS ${Vulkan_INCLUDE_DIR}) 67 | 68 | include(FindPackageHandleStandardArgs) 69 | find_package_handle_standard_args(Vulkan 70 | DEFAULT_MSG 71 | Vulkan_LIBRARY Vulkan_INCLUDE_DIR) 72 | 73 | mark_as_advanced(Vulkan_INCLUDE_DIR Vulkan_LIBRARY) 74 | 75 | if(Vulkan_FOUND AND NOT TARGET Vulkan::Vulkan) 76 | add_library(Vulkan::Vulkan UNKNOWN IMPORTED) 77 | set_target_properties(Vulkan::Vulkan PROPERTIES 78 | IMPORTED_LOCATION "${Vulkan_LIBRARIES}" 79 | INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") 80 | endif() -------------------------------------------------------------------------------- /cmake/FindXCB.cmake: -------------------------------------------------------------------------------- 1 | # - FindXCB 2 | # 3 | # Copyright 2015 Valve Coporation 4 | 5 | find_package(PkgConfig) 6 | 7 | if(NOT XCB_FIND_COMPONENTS) 8 | set(XCB_FIND_COMPONENTS xcb) 9 | endif() 10 | 11 | include(FindPackageHandleStandardArgs) 12 | set(XCB_FOUND true) 13 | set(XCB_INCLUDE_DIRS "") 14 | set(XCB_LIBRARIES "") 15 | foreach(comp ${XCB_FIND_COMPONENTS}) 16 | # component name 17 | string(TOUPPER ${comp} compname) 18 | string(REPLACE "-" "_" compname ${compname}) 19 | # header name 20 | string(REPLACE "xcb-" "" headername xcb/${comp}.h) 21 | # library name 22 | set(libname ${comp}) 23 | 24 | pkg_check_modules(PC_${comp} QUIET ${comp}) 25 | 26 | find_path(${compname}_INCLUDE_DIR NAMES ${headername} 27 | HINTS 28 | ${PC_${comp}_INCLUDEDIR} 29 | ${PC_${comp}_INCLUDE_DIRS} 30 | ) 31 | 32 | find_library(${compname}_LIBRARY NAMES ${libname} 33 | HINTS 34 | ${PC_${comp}_LIBDIR} 35 | ${PC_${comp}_LIBRARY_DIRS} 36 | ) 37 | 38 | find_package_handle_standard_args(${comp} 39 | FOUND_VAR ${comp}_FOUND 40 | REQUIRED_VARS ${compname}_INCLUDE_DIR ${compname}_LIBRARY) 41 | mark_as_advanced(${compname}_INCLUDE_DIR ${compname}_LIBRARY) 42 | 43 | list(APPEND XCB_INCLUDE_DIRS ${${compname}_INCLUDE_DIR}) 44 | list(APPEND XCB_LIBRARIES ${${compname}_LIBRARY}) 45 | 46 | if(NOT ${comp}_FOUND) 47 | set(XCB_FOUND false) 48 | endif() 49 | endforeach() 50 | 51 | list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS) -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs") 3 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "Build the GLFW test programs") 4 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "Build the GLFW documentation") 5 | set(GLFW_INSTALL OFF CACHE BOOL "Generate installation target") 6 | add_subdirectory(GLFW) 7 | 8 | set(GLM_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/glm PARENT_SCOPE) 9 | set(STB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stb PARENT_SCOPE) 10 | -------------------------------------------------------------------------------- /img/blade_model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CIS565-Fall-2017/Project6-Vulkan-Grass-Rendering/3fdcbb5ab011518591c922ea5bafc9a149cb0075/img/blade_model.jpg -------------------------------------------------------------------------------- /img/cube_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CIS565-Fall-2017/Project6-Vulkan-Grass-Rendering/3fdcbb5ab011518591c922ea5bafc9a149cb0075/img/cube_demo.png -------------------------------------------------------------------------------- /img/grass.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CIS565-Fall-2017/Project6-Vulkan-Grass-Rendering/3fdcbb5ab011518591c922ea5bafc9a149cb0075/img/grass.gif -------------------------------------------------------------------------------- /src/Blades.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Blades.h" 3 | #include "BufferUtils.h" 4 | 5 | float generateRandomFloat() { 6 | return rand() / (float)RAND_MAX; 7 | } 8 | 9 | Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Model(device, commandPool, {}, {}) { 10 | std::vector blades; 11 | blades.reserve(NUM_BLADES); 12 | 13 | for (int i = 0; i < NUM_BLADES; i++) { 14 | Blade currentBlade = Blade(); 15 | 16 | glm::vec3 bladeUp(0.0f, 1.0f, 0.0f); 17 | 18 | // Generate positions and direction (v0) 19 | float x = (generateRandomFloat() - 0.5f) * planeDim; 20 | float y = 0.0f; 21 | float z = (generateRandomFloat() - 0.5f) * planeDim; 22 | float direction = generateRandomFloat() * 2.f * 3.14159265f; 23 | glm::vec3 bladePosition(x, y, z); 24 | currentBlade.v0 = glm::vec4(bladePosition, direction); 25 | 26 | // Bezier point and height (v1) 27 | float height = MIN_HEIGHT + (generateRandomFloat() * (MAX_HEIGHT - MIN_HEIGHT)); 28 | currentBlade.v1 = glm::vec4(bladePosition + bladeUp * height, height); 29 | 30 | // Physical model guide and width (v2) 31 | float width = MIN_WIDTH + (generateRandomFloat() * (MAX_WIDTH - MIN_WIDTH)); 32 | currentBlade.v2 = glm::vec4(bladePosition + bladeUp * height, width); 33 | 34 | // Up vector and stiffness coefficient (up) 35 | float stiffness = MIN_BEND + (generateRandomFloat() * (MAX_BEND - MIN_BEND)); 36 | currentBlade.up = glm::vec4(bladeUp, stiffness); 37 | 38 | blades.push_back(currentBlade); 39 | } 40 | 41 | BladeDrawIndirect indirectDraw; 42 | indirectDraw.vertexCount = NUM_BLADES; 43 | indirectDraw.instanceCount = 1; 44 | indirectDraw.firstVertex = 0; 45 | indirectDraw.firstInstance = 0; 46 | 47 | BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory); 48 | BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory); 49 | BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory); 50 | } 51 | 52 | VkBuffer Blades::GetBladesBuffer() const { 53 | return bladesBuffer; 54 | } 55 | 56 | VkBuffer Blades::GetCulledBladesBuffer() const { 57 | return culledBladesBuffer; 58 | } 59 | 60 | VkBuffer Blades::GetNumBladesBuffer() const { 61 | return numBladesBuffer; 62 | } 63 | 64 | Blades::~Blades() { 65 | vkDestroyBuffer(device->GetVkDevice(), bladesBuffer, nullptr); 66 | vkFreeMemory(device->GetVkDevice(), bladesBufferMemory, nullptr); 67 | vkDestroyBuffer(device->GetVkDevice(), culledBladesBuffer, nullptr); 68 | vkFreeMemory(device->GetVkDevice(), culledBladesBufferMemory, nullptr); 69 | vkDestroyBuffer(device->GetVkDevice(), numBladesBuffer, nullptr); 70 | vkFreeMemory(device->GetVkDevice(), numBladesBufferMemory, nullptr); 71 | } 72 | -------------------------------------------------------------------------------- /src/Blades.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "Model.h" 6 | 7 | constexpr static unsigned int NUM_BLADES = 1 << 13; 8 | constexpr static float MIN_HEIGHT = 1.3f; 9 | constexpr static float MAX_HEIGHT = 2.5f; 10 | constexpr static float MIN_WIDTH = 0.1f; 11 | constexpr static float MAX_WIDTH = 0.14f; 12 | constexpr static float MIN_BEND = 7.0f; 13 | constexpr static float MAX_BEND = 13.0f; 14 | 15 | struct Blade { 16 | // Position and direction 17 | glm::vec4 v0; 18 | // Bezier point and height 19 | glm::vec4 v1; 20 | // Physical model guide and width 21 | glm::vec4 v2; 22 | // Up vector and stiffness coefficient 23 | glm::vec4 up; 24 | 25 | static VkVertexInputBindingDescription getBindingDescription() { 26 | VkVertexInputBindingDescription bindingDescription = {}; 27 | bindingDescription.binding = 0; 28 | bindingDescription.stride = sizeof(Blade); 29 | bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 30 | 31 | return bindingDescription; 32 | } 33 | 34 | static std::array getAttributeDescriptions() { 35 | std::array attributeDescriptions = {}; 36 | 37 | // v0 38 | attributeDescriptions[0].binding = 0; 39 | attributeDescriptions[0].location = 0; 40 | attributeDescriptions[0].format = VK_FORMAT_R32G32B32A32_SFLOAT; 41 | attributeDescriptions[0].offset = offsetof(Blade, v0); 42 | 43 | // v1 44 | attributeDescriptions[1].binding = 0; 45 | attributeDescriptions[1].location = 1; 46 | attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; 47 | attributeDescriptions[1].offset = offsetof(Blade, v1); 48 | 49 | // v2 50 | attributeDescriptions[2].binding = 0; 51 | attributeDescriptions[2].location = 2; 52 | attributeDescriptions[2].format = VK_FORMAT_R32G32B32A32_SFLOAT; 53 | attributeDescriptions[2].offset = offsetof(Blade, v2); 54 | 55 | // up 56 | attributeDescriptions[3].binding = 0; 57 | attributeDescriptions[3].location = 3; 58 | attributeDescriptions[3].format = VK_FORMAT_R32G32B32A32_SFLOAT; 59 | attributeDescriptions[3].offset = offsetof(Blade, up); 60 | 61 | return attributeDescriptions; 62 | } 63 | }; 64 | 65 | struct BladeDrawIndirect { 66 | uint32_t vertexCount; 67 | uint32_t instanceCount; 68 | uint32_t firstVertex; 69 | uint32_t firstInstance; 70 | }; 71 | 72 | class Blades : public Model { 73 | private: 74 | VkBuffer bladesBuffer; 75 | VkBuffer culledBladesBuffer; 76 | VkBuffer numBladesBuffer; 77 | 78 | VkDeviceMemory bladesBufferMemory; 79 | VkDeviceMemory culledBladesBufferMemory; 80 | VkDeviceMemory numBladesBufferMemory; 81 | 82 | public: 83 | Blades(Device* device, VkCommandPool commandPool, float planeDim); 84 | VkBuffer GetBladesBuffer() const; 85 | VkBuffer GetCulledBladesBuffer() const; 86 | VkBuffer GetNumBladesBuffer() const; 87 | ~Blades(); 88 | }; 89 | -------------------------------------------------------------------------------- /src/BufferUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferUtils.h" 2 | #include "Instance.h" 3 | 4 | void BufferUtils::CreateBuffer(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { 5 | // Create buffer 6 | VkBufferCreateInfo bufferInfo = {}; 7 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 8 | bufferInfo.size = size; 9 | bufferInfo.usage = usage; 10 | bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 11 | 12 | if (vkCreateBuffer(device->GetVkDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { 13 | throw std::runtime_error("Failed to create vertex buffer"); 14 | } 15 | 16 | // Query buffer's memory requirements 17 | VkMemoryRequirements memRequirements; 18 | vkGetBufferMemoryRequirements(device->GetVkDevice(), buffer, &memRequirements); 19 | 20 | // Allocate memory in device 21 | VkMemoryAllocateInfo allocInfo = {}; 22 | allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 23 | allocInfo.allocationSize = memRequirements.size; 24 | allocInfo.memoryTypeIndex = device->GetInstance()->GetMemoryTypeIndex(memRequirements.memoryTypeBits, properties); 25 | 26 | if (vkAllocateMemory(device->GetVkDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) { 27 | throw std::runtime_error("Failed to allocate vertex buffer"); 28 | } 29 | 30 | // Associate allocated memory with vertex buffer 31 | vkBindBufferMemory(device->GetVkDevice(), buffer, bufferMemory, 0); 32 | } 33 | 34 | void BufferUtils::CopyBuffer(Device* device, VkCommandPool commandPool, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { 35 | VkCommandBufferAllocateInfo allocInfo = {}; 36 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 37 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 38 | allocInfo.commandPool = commandPool; 39 | allocInfo.commandBufferCount = 1; 40 | 41 | VkCommandBuffer commandBuffer; 42 | vkAllocateCommandBuffers(device->GetVkDevice(), &allocInfo, &commandBuffer); 43 | 44 | VkCommandBufferBeginInfo beginInfo = {}; 45 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 46 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 47 | 48 | vkBeginCommandBuffer(commandBuffer, &beginInfo); 49 | 50 | VkBufferCopy copyRegion = {}; 51 | copyRegion.size = size; 52 | vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); 53 | 54 | vkEndCommandBuffer(commandBuffer); 55 | 56 | VkSubmitInfo submitInfo = {}; 57 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 58 | submitInfo.commandBufferCount = 1; 59 | submitInfo.pCommandBuffers = &commandBuffer; 60 | 61 | vkQueueSubmit(device->GetQueue(QueueFlags::Graphics), 1, &submitInfo, VK_NULL_HANDLE); 62 | vkQueueWaitIdle(device->GetQueue(QueueFlags::Graphics)); 63 | vkFreeCommandBuffers(device->GetVkDevice(), commandPool, 1, &commandBuffer); 64 | } 65 | 66 | void BufferUtils::CreateBufferFromData(Device* device, VkCommandPool commandPool, void* bufferData, VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { 67 | // Create the staging buffer 68 | VkBuffer stagingBuffer; 69 | VkDeviceMemory stagingBufferMemory; 70 | 71 | VkBufferUsageFlags stagingUsage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 72 | VkMemoryPropertyFlags stagingProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; 73 | BufferUtils::CreateBuffer(device, bufferSize, stagingUsage, stagingProperties, stagingBuffer, stagingBufferMemory); 74 | 75 | // Fill the staging buffer 76 | void *data; 77 | vkMapMemory(device->GetVkDevice(), stagingBufferMemory, 0, bufferSize, 0, &data); 78 | memcpy(data, bufferData, static_cast(bufferSize)); 79 | vkUnmapMemory(device->GetVkDevice(), stagingBufferMemory); 80 | 81 | // Create the buffer 82 | VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | bufferUsage; 83 | VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; 84 | BufferUtils::CreateBuffer(device, bufferSize, usage, flags, buffer, bufferMemory); 85 | 86 | // Copy data from staging to buffer 87 | BufferUtils::CopyBuffer(device, commandPool, stagingBuffer, buffer, bufferSize); 88 | 89 | // No need for the staging buffer anymore 90 | vkDestroyBuffer(device->GetVkDevice(), stagingBuffer, nullptr); 91 | vkFreeMemory(device->GetVkDevice(), stagingBufferMemory, nullptr); 92 | } 93 | -------------------------------------------------------------------------------- /src/BufferUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Device.h" 5 | 6 | namespace BufferUtils { 7 | void CreateBuffer(Device* device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory); 8 | void CopyBuffer(Device* device, VkCommandPool commandPool, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size); 9 | void CreateBufferFromData(Device* device, VkCommandPool commandPool, void* bufferData, VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, VkBuffer& buffer, VkDeviceMemory& bufferMemory); 10 | } 11 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) 2 | 3 | file(GLOB IMAGES 4 | ${CMAKE_CURRENT_SOURCE_DIR}/images/*.jpg 5 | ${CMAKE_CURRENT_SOURCE_DIR}/images/*.png 6 | ${CMAKE_CURRENT_SOURCE_DIR}/images/*.bmp 7 | ) 8 | 9 | foreach(IMAGE ${IMAGES}) 10 | get_filename_component(fname ${IMAGE} NAME) 11 | configure_file(${IMAGE} ${CMAKE_CURRENT_BINARY_DIR}/images/${fname} COPYONLY) 12 | endforeach() 13 | 14 | file(GLOB_RECURSE SHADER_SOURCES 15 | ${CMAKE_CURRENT_SOURCE_DIR}/*.vert 16 | ${CMAKE_CURRENT_SOURCE_DIR}/*.frag 17 | ${CMAKE_CURRENT_SOURCE_DIR}/*.geom 18 | ${CMAKE_CURRENT_SOURCE_DIR}/*.comp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/*.tese 20 | ${CMAKE_CURRENT_SOURCE_DIR}/*.tesc 21 | ) 22 | 23 | source_group("Shaders" FILES ${SHADER_SOURCES}) 24 | 25 | if(WIN32) 26 | add_executable(vulkan_grass_rendering WIN32 ${SOURCES} ${SHADER_SOURCES}) 27 | target_link_libraries(vulkan_grass_rendering ${WINLIBS}) 28 | else(WIN32) 29 | add_executable(vulkan_grass_rendering ${SOURCES}) 30 | target_link_libraries(vulkan_grass_rendering ${CMAKE_THREAD_LIBS_INIT}) 31 | endif(WIN32) 32 | 33 | foreach(SHADER_SOURCE ${SHADER_SOURCES}) 34 | set(SHADER_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders) 35 | 36 | if(WIN32) 37 | get_filename_component(fname ${SHADER_SOURCE} NAME) 38 | add_custom_target(${fname}.spv 39 | COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR} && 40 | $ENV{VK_SDK_PATH}/Bin/glslangValidator.exe -V ${SHADER_SOURCE} -o ${SHADER_DIR}/${fname}.spv 41 | SOURCES ${SHADER_SOURCE} 42 | ) 43 | ExternalTarget("Shaders" ${fname}.spv) 44 | add_dependencies(vulkan_grass_rendering ${fname}.spv) 45 | endif(WIN32) 46 | 47 | # TODO: Build shaders on not windows 48 | endforeach() 49 | 50 | target_link_libraries(vulkan_grass_rendering ${ASSIMP_LIBRARIES} Vulkan::Vulkan glfw) 51 | target_include_directories(vulkan_grass_rendering PRIVATE 52 | ${CMAKE_CURRENT_SOURCE_DIR} 53 | ${GLM_INCLUDE_DIR} 54 | ${STB_INCLUDE_DIR} 55 | ) 56 | 57 | InternalTarget("" vulkan_grass_rendering) 58 | -------------------------------------------------------------------------------- /src/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define GLM_FORCE_RADIANS 4 | // Use Vulkan depth range of 0.0 to 1.0 instead of OpenGL 5 | #define GLM_FORCE_DEPTH_ZERO_TO_ONE 6 | #include 7 | 8 | #include "Camera.h" 9 | #include "BufferUtils.h" 10 | 11 | Camera::Camera(Device* device, float aspectRatio) : device(device) { 12 | r = 10.0f; 13 | theta = 0.0f; 14 | phi = 0.0f; 15 | cameraBufferObject.viewMatrix = glm::lookAt(glm::vec3(0.0f, 1.0f, 10.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); 16 | cameraBufferObject.projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); 17 | cameraBufferObject.projectionMatrix[1][1] *= -1; // y-coordinate is flipped 18 | 19 | BufferUtils::CreateBuffer(device, sizeof(CameraBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, buffer, bufferMemory); 20 | vkMapMemory(device->GetVkDevice(), bufferMemory, 0, sizeof(CameraBufferObject), 0, &mappedData); 21 | memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject)); 22 | } 23 | 24 | VkBuffer Camera::GetBuffer() const { 25 | return buffer; 26 | } 27 | 28 | void Camera::UpdateOrbit(float deltaX, float deltaY, float deltaZ) { 29 | theta += deltaX; 30 | phi += deltaY; 31 | r = glm::clamp(r - deltaZ, 1.0f, 50.0f); 32 | 33 | float radTheta = glm::radians(theta); 34 | float radPhi = glm::radians(phi); 35 | 36 | glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), radTheta, glm::vec3(0.0f, 1.0f, 0.0f)) * glm::rotate(glm::mat4(1.0f), radPhi, glm::vec3(1.0f, 0.0f, 0.0f)); 37 | glm::mat4 finalTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f)) * rotation * glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 1.0f, r)); 38 | 39 | cameraBufferObject.viewMatrix = glm::inverse(finalTransform); 40 | 41 | memcpy(mappedData, &cameraBufferObject, sizeof(CameraBufferObject)); 42 | } 43 | 44 | Camera::~Camera() { 45 | vkUnmapMemory(device->GetVkDevice(), bufferMemory); 46 | vkDestroyBuffer(device->GetVkDevice(), buffer, nullptr); 47 | vkFreeMemory(device->GetVkDevice(), bufferMemory, nullptr); 48 | } 49 | -------------------------------------------------------------------------------- /src/Camera.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include "Device.h" 6 | 7 | struct CameraBufferObject { 8 | glm::mat4 viewMatrix; 9 | glm::mat4 projectionMatrix; 10 | }; 11 | 12 | class Camera { 13 | private: 14 | Device* device; 15 | 16 | CameraBufferObject cameraBufferObject; 17 | 18 | VkBuffer buffer; 19 | VkDeviceMemory bufferMemory; 20 | 21 | void* mappedData; 22 | 23 | float r, theta, phi; 24 | 25 | public: 26 | Camera(Device* device, float aspectRatio); 27 | ~Camera(); 28 | 29 | VkBuffer GetBuffer() const; 30 | 31 | void UpdateOrbit(float deltaX, float deltaY, float deltaZ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/Device.cpp: -------------------------------------------------------------------------------- 1 | #include "Device.h" 2 | #include "Instance.h" 3 | 4 | Device::Device(Instance* instance, VkDevice vkDevice, Queues queues) 5 | : instance(instance), vkDevice(vkDevice), queues(queues) { 6 | } 7 | 8 | Instance* Device::GetInstance() { 9 | return instance; 10 | } 11 | 12 | VkDevice Device::GetVkDevice() { 13 | return vkDevice; 14 | } 15 | 16 | VkQueue Device::GetQueue(QueueFlags flag) { 17 | return queues[flag]; 18 | } 19 | 20 | unsigned int Device::GetQueueIndex(QueueFlags flag) { 21 | return GetInstance()->GetQueueFamilyIndices()[flag]; 22 | } 23 | 24 | SwapChain* Device::CreateSwapChain(VkSurfaceKHR surface, unsigned int numBuffers) { 25 | return new SwapChain(this, surface, numBuffers); 26 | } 27 | 28 | Device::~Device() { 29 | vkDestroyDevice(vkDevice, nullptr); 30 | } 31 | -------------------------------------------------------------------------------- /src/Device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "QueueFlags.h" 6 | #include "SwapChain.h" 7 | 8 | class SwapChain; 9 | class Device { 10 | friend class Instance; 11 | 12 | public: 13 | SwapChain* CreateSwapChain(VkSurfaceKHR surface, unsigned int numBuffers); 14 | Instance* GetInstance(); 15 | VkDevice GetVkDevice(); 16 | VkQueue GetQueue(QueueFlags flag); 17 | unsigned int GetQueueIndex(QueueFlags flag); 18 | ~Device(); 19 | 20 | private: 21 | using Queues = std::array; 22 | 23 | Device() = delete; 24 | Device(Instance* instance, VkDevice vkDevice, Queues queues); 25 | 26 | Instance* instance; 27 | VkDevice vkDevice; 28 | Queues queues; 29 | }; 30 | -------------------------------------------------------------------------------- /src/Image.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include 3 | 4 | #include "Image.h" 5 | #include "Device.h" 6 | #include "Instance.h" 7 | #include "BufferUtils.h" 8 | 9 | void Image::Create(Device* device, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { 10 | // Create Vulkan image 11 | VkImageCreateInfo imageInfo = {}; 12 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 13 | imageInfo.imageType = VK_IMAGE_TYPE_2D; 14 | imageInfo.extent.width = width; 15 | imageInfo.extent.height = height; 16 | imageInfo.extent.depth = 1; 17 | imageInfo.mipLevels = 1; 18 | imageInfo.arrayLayers = 1; 19 | imageInfo.format = format; 20 | imageInfo.tiling = tiling; 21 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 22 | imageInfo.usage = usage; 23 | imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; 24 | imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 25 | 26 | if (vkCreateImage(device->GetVkDevice(), &imageInfo, nullptr, &image) != VK_SUCCESS) { 27 | throw std::runtime_error("Failed to create image"); 28 | } 29 | 30 | // Allocate memory for the image 31 | VkMemoryRequirements memRequirements; 32 | vkGetImageMemoryRequirements(device->GetVkDevice(), image, &memRequirements); 33 | 34 | VkMemoryAllocateInfo allocInfo = {}; 35 | allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 36 | allocInfo.allocationSize = memRequirements.size; 37 | allocInfo.memoryTypeIndex = device->GetInstance()->GetMemoryTypeIndex(memRequirements.memoryTypeBits, properties); 38 | 39 | if (vkAllocateMemory(device->GetVkDevice(), &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { 40 | throw std::runtime_error("Failed to allocate image memory"); 41 | } 42 | 43 | // Bind the image 44 | vkBindImageMemory(device->GetVkDevice(), image, imageMemory, 0); 45 | } 46 | 47 | void Image::TransitionLayout(Device* device, VkCommandPool commandPool, VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { 48 | auto hasStencilComponent = [](VkFormat format) { 49 | return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; 50 | }; 51 | 52 | // Use an image memory barrier (type of pipeline barrier) to transition image layout 53 | VkImageMemoryBarrier barrier = {}; 54 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 55 | barrier.oldLayout = oldLayout; 56 | barrier.newLayout = newLayout; 57 | barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 58 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 59 | barrier.image = image; 60 | 61 | if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { 62 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; 63 | 64 | if (hasStencilComponent(format)) { 65 | barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; 66 | } 67 | } 68 | else { 69 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 70 | } 71 | 72 | barrier.subresourceRange.baseMipLevel = 0; 73 | barrier.subresourceRange.levelCount = 1; 74 | barrier.subresourceRange.baseArrayLayer = 0; 75 | barrier.subresourceRange.layerCount = 1; 76 | 77 | VkPipelineStageFlags sourceStage; 78 | VkPipelineStageFlags destinationStage; 79 | 80 | if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { 81 | barrier.srcAccessMask = 0; 82 | barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 83 | 84 | sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; 85 | destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; 86 | } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { 87 | barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 88 | barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; 89 | 90 | sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; 91 | destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; 92 | } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { 93 | barrier.srcAccessMask = 0; 94 | barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; 95 | 96 | sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; 97 | destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; 98 | } else { 99 | throw std::invalid_argument("Unsupported layout transition"); 100 | } 101 | 102 | VkCommandBufferAllocateInfo allocInfo = {}; 103 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 104 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 105 | allocInfo.commandPool = commandPool; 106 | allocInfo.commandBufferCount = 1; 107 | 108 | VkCommandBuffer commandBuffer; 109 | vkAllocateCommandBuffers(device->GetVkDevice(), &allocInfo, &commandBuffer); 110 | 111 | VkCommandBufferBeginInfo beginInfo = {}; 112 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 113 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 114 | 115 | vkBeginCommandBuffer(commandBuffer, &beginInfo); 116 | 117 | vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); 118 | 119 | vkEndCommandBuffer(commandBuffer); 120 | 121 | VkSubmitInfo submitInfo = {}; 122 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 123 | submitInfo.commandBufferCount = 1; 124 | submitInfo.pCommandBuffers = &commandBuffer; 125 | 126 | vkQueueSubmit(device->GetQueue(QueueFlags::Graphics), 1, &submitInfo, VK_NULL_HANDLE); 127 | vkQueueWaitIdle(device->GetQueue(QueueFlags::Graphics)); 128 | vkFreeCommandBuffers(device->GetVkDevice(), commandPool, 1, &commandBuffer); 129 | } 130 | 131 | VkImageView Image::CreateView(Device* device, VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { 132 | VkImageViewCreateInfo viewInfo = {}; 133 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 134 | viewInfo.image = image; 135 | viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 136 | viewInfo.format = format; 137 | 138 | // Describe the image's purpose and which part of the image should be accessed 139 | viewInfo.subresourceRange.aspectMask = aspectFlags; 140 | viewInfo.subresourceRange.baseMipLevel = 0; 141 | viewInfo.subresourceRange.levelCount = 1; 142 | viewInfo.subresourceRange.baseArrayLayer = 0; 143 | viewInfo.subresourceRange.layerCount = 1; 144 | 145 | VkImageView imageView; 146 | if (vkCreateImageView(device->GetVkDevice(), &viewInfo, nullptr, &imageView) != VK_SUCCESS) { 147 | throw std::runtime_error("Failed to texture image view"); 148 | } 149 | 150 | return imageView; 151 | } 152 | 153 | void Image::CopyFromBuffer(Device* device, VkCommandPool commandPool, VkBuffer buffer, VkImage& image, uint32_t width, uint32_t height) { 154 | // Specify which part of the buffer is going to be copied to which part of the image 155 | VkBufferImageCopy region = {}; 156 | region.bufferOffset = 0; 157 | region.bufferRowLength = 0; 158 | region.bufferImageHeight = 0; 159 | 160 | region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 161 | region.imageSubresource.mipLevel = 0; 162 | region.imageSubresource.baseArrayLayer = 0; 163 | region.imageSubresource.layerCount = 1; 164 | 165 | region.imageOffset = { 0, 0, 0 }; 166 | region.imageExtent = { width, height, 1 }; 167 | 168 | VkCommandBufferAllocateInfo allocInfo = {}; 169 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 170 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 171 | allocInfo.commandPool = commandPool; 172 | allocInfo.commandBufferCount = 1; 173 | 174 | VkCommandBuffer commandBuffer; 175 | vkAllocateCommandBuffers(device->GetVkDevice(), &allocInfo, &commandBuffer); 176 | 177 | VkCommandBufferBeginInfo beginInfo = {}; 178 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 179 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 180 | 181 | vkBeginCommandBuffer(commandBuffer, &beginInfo); 182 | 183 | vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); 184 | 185 | vkEndCommandBuffer(commandBuffer); 186 | 187 | VkSubmitInfo submitInfo = {}; 188 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 189 | submitInfo.commandBufferCount = 1; 190 | submitInfo.pCommandBuffers = &commandBuffer; 191 | 192 | vkQueueSubmit(device->GetQueue(QueueFlags::Transfer), 1, &submitInfo, VK_NULL_HANDLE); 193 | vkQueueWaitIdle(device->GetQueue(QueueFlags::Transfer)); 194 | vkFreeCommandBuffers(device->GetVkDevice(), commandPool, 1, &commandBuffer); 195 | } 196 | 197 | void Image::FromFile(Device* device, VkCommandPool commandPool, const char* path, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkImageLayout layout, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { 198 | int texWidth, texHeight, texChannels; 199 | stbi_uc* pixels = stbi_load(path, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); 200 | VkDeviceSize imageSize = texWidth * texHeight * 4; 201 | 202 | if (!pixels) { 203 | throw std::runtime_error("Failed to load texture image"); 204 | } 205 | 206 | // Create staging buffer 207 | VkBuffer stagingBuffer; 208 | VkDeviceMemory stagingBufferMemory; 209 | 210 | VkBufferUsageFlags stagingUsage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 211 | VkMemoryPropertyFlags stagingProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; 212 | BufferUtils::CreateBuffer(device, imageSize, stagingUsage, stagingProperties, stagingBuffer, stagingBufferMemory); 213 | 214 | // Copy pixel values to the buffer 215 | void* data; 216 | vkMapMemory(device->GetVkDevice(), stagingBufferMemory, 0, imageSize, 0, &data); 217 | memcpy(data, pixels, static_cast(imageSize)); 218 | vkUnmapMemory(device->GetVkDevice(), stagingBufferMemory); 219 | 220 | // Free pixel array 221 | stbi_image_free(pixels); 222 | 223 | // Create Vulkan image 224 | Image::Create(device, texWidth, texHeight, format, tiling, VK_IMAGE_USAGE_TRANSFER_DST_BIT | usage, properties, image, imageMemory); 225 | 226 | // Copy the staging buffer to the texture image 227 | // --> First need to transition the texture image to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 228 | Image::TransitionLayout(device, commandPool, image, format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); 229 | Image::CopyFromBuffer(device, commandPool, stagingBuffer, image, static_cast(texWidth), static_cast(texHeight)); 230 | 231 | // Transition texture image for shader access 232 | Image::TransitionLayout(device, commandPool, image, format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layout); 233 | 234 | // No need for staging buffer anymore 235 | vkDestroyBuffer(device->GetVkDevice(), stagingBuffer, nullptr); 236 | vkFreeMemory(device->GetVkDevice(), stagingBufferMemory, nullptr); 237 | } 238 | -------------------------------------------------------------------------------- /src/Image.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Device.h" 5 | 6 | namespace Image { 7 | 8 | void Create(Device* device, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory); 9 | void TransitionLayout(Device* device, VkCommandPool commandPool, VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout); 10 | VkImageView CreateView(Device* device, VkImage image, VkFormat format, VkImageAspectFlags aspectFlags); 11 | void CopyFromBuffer(Device* device, VkCommandPool commandPool, VkBuffer buffer, VkImage& image, uint32_t width, uint32_t height); 12 | void FromFile(Device* device, VkCommandPool commandPool, const char* path, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkImageLayout layout, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory); 13 | } 14 | -------------------------------------------------------------------------------- /src/Instance.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Instance.h" 5 | 6 | #ifdef NDEBUG 7 | const bool ENABLE_VALIDATION = false; 8 | #else 9 | const bool ENABLE_VALIDATION = true; 10 | #endif 11 | 12 | namespace { 13 | const std::vector validationLayers = { 14 | "VK_LAYER_LUNARG_standard_validation" 15 | }; 16 | 17 | // Get the required list of extensions based on whether validation layers are enabled 18 | std::vector getRequiredExtensions() { 19 | std::vector extensions; 20 | 21 | if (ENABLE_VALIDATION) { 22 | extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); 23 | } 24 | 25 | return extensions; 26 | } 27 | 28 | // Callback function to allow messages from validation layers to be received 29 | VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( 30 | VkDebugReportFlagsEXT flags, 31 | VkDebugReportObjectTypeEXT objType, 32 | uint64_t obj, 33 | size_t location, 34 | int32_t code, 35 | const char* layerPrefix, 36 | const char* msg, 37 | void *userData) { 38 | 39 | fprintf(stderr, "Validation layer: %s\n", msg); 40 | return VK_FALSE; 41 | } 42 | } 43 | 44 | Instance::Instance(const char* applicationName, unsigned int additionalExtensionCount, const char** additionalExtensions) { 45 | // --- Specify details about our application --- 46 | VkApplicationInfo appInfo = {}; 47 | appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; 48 | appInfo.pApplicationName = applicationName; 49 | appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); 50 | appInfo.pEngineName = "No Engine"; 51 | appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); 52 | appInfo.apiVersion = VK_API_VERSION_1_0; 53 | 54 | // --- Create Vulkan instance --- 55 | VkInstanceCreateInfo createInfo = {}; 56 | createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 57 | createInfo.pApplicationInfo = &appInfo; 58 | 59 | // Get extensions necessary for Vulkan to interface with GLFW 60 | auto extensions = getRequiredExtensions(); 61 | for (unsigned int i = 0; i < additionalExtensionCount; ++i) { 62 | extensions.push_back(additionalExtensions[i]); 63 | } 64 | createInfo.enabledExtensionCount = static_cast(extensions.size()); 65 | createInfo.ppEnabledExtensionNames = extensions.data(); 66 | 67 | // Specify global validation layers 68 | if (ENABLE_VALIDATION) { 69 | createInfo.enabledLayerCount = static_cast(validationLayers.size()); 70 | createInfo.ppEnabledLayerNames = validationLayers.data(); 71 | } else { 72 | createInfo.enabledLayerCount = 0; 73 | } 74 | 75 | // Create instance 76 | if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { 77 | throw std::runtime_error("Failed to create instance"); 78 | } 79 | 80 | initDebugReport(); 81 | } 82 | 83 | VkInstance Instance::GetVkInstance() { 84 | return instance; 85 | } 86 | 87 | VkPhysicalDevice Instance::GetPhysicalDevice() { 88 | return physicalDevice; 89 | } 90 | 91 | const VkSurfaceCapabilitiesKHR& Instance::GetSurfaceCapabilities() const { 92 | return surfaceCapabilities; 93 | } 94 | 95 | const QueueFamilyIndices& Instance::GetQueueFamilyIndices() const { 96 | return queueFamilyIndices; 97 | } 98 | 99 | const std::vector& Instance::GetSurfaceFormats() const { 100 | return surfaceFormats; 101 | } 102 | 103 | const std::vector& Instance::GetPresentModes() const { 104 | return presentModes; 105 | } 106 | 107 | uint32_t Instance::GetMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) const { 108 | // Iterate over all memory types available for the device used in this example 109 | for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) { 110 | if ((typeBits & 1) == 1) { 111 | if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { 112 | return i; 113 | } 114 | } 115 | typeBits >>= 1; 116 | } 117 | throw std::runtime_error("Could not find a suitable memory type!"); 118 | } 119 | 120 | VkFormat Instance::GetSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) const { 121 | for (VkFormat format : candidates) { 122 | VkFormatProperties properties; 123 | vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &properties); 124 | 125 | if (tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) { 126 | return format; 127 | } 128 | else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) { 129 | return format; 130 | } 131 | } 132 | 133 | throw std::runtime_error("Failed to find supported format"); 134 | } 135 | 136 | void Instance::initDebugReport() { 137 | if (ENABLE_VALIDATION) { 138 | // Specify details for callback 139 | VkDebugReportCallbackCreateInfoEXT createInfo = {}; 140 | createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; 141 | createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; 142 | createInfo.pfnCallback = debugCallback; 143 | 144 | if ([&]() { 145 | auto func = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); 146 | if (func != nullptr) { 147 | return func(instance, &createInfo, nullptr, &debugReportCallback); 148 | } 149 | else { 150 | return VK_ERROR_EXTENSION_NOT_PRESENT; 151 | } 152 | }() != VK_SUCCESS) { 153 | throw std::runtime_error("Failed to set up debug callback"); 154 | } 155 | } 156 | } 157 | 158 | 159 | namespace { 160 | QueueFamilyIndices checkDeviceQueueSupport(VkPhysicalDevice device, QueueFlagBits requiredQueues, VkSurfaceKHR surface = VK_NULL_HANDLE) { 161 | uint32_t queueFamilyCount = 0; 162 | vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); 163 | 164 | std::vector queueFamilies(queueFamilyCount); 165 | vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); 166 | 167 | VkQueueFlags requiredVulkanQueues = 0; 168 | if (requiredQueues[QueueFlags::Graphics]) { 169 | requiredVulkanQueues |= VK_QUEUE_GRAPHICS_BIT; 170 | } 171 | if (requiredQueues[QueueFlags::Compute]) { 172 | requiredVulkanQueues |= VK_QUEUE_COMPUTE_BIT; 173 | } 174 | if (requiredQueues[QueueFlags::Transfer]) { 175 | requiredVulkanQueues |= VK_QUEUE_TRANSFER_BIT; 176 | } 177 | 178 | QueueFamilyIndices indices = {}; 179 | indices.fill(-1); 180 | VkQueueFlags supportedQueues = 0; 181 | bool needsPresent = requiredQueues[QueueFlags::Present]; 182 | bool presentSupported = false; 183 | 184 | int i = 0; 185 | for (const auto& queueFamily : queueFamilies) { 186 | if (queueFamily.queueCount > 0) { 187 | supportedQueues |= queueFamily.queueFlags; 188 | } 189 | 190 | if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { 191 | indices[QueueFlags::Graphics] = i; 192 | } 193 | 194 | if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) { 195 | indices[QueueFlags::Compute] = i; 196 | } 197 | 198 | if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT) { 199 | indices[QueueFlags::Transfer] = i; 200 | } 201 | 202 | if (needsPresent) { 203 | VkBool32 presentSupport = false; 204 | vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); 205 | if (queueFamily.queueCount > 0 && presentSupport) { 206 | presentSupported = true; 207 | indices[QueueFlags::Present] = i; 208 | } 209 | } 210 | 211 | if ((requiredVulkanQueues & supportedQueues) == requiredVulkanQueues && (!needsPresent || presentSupported)) { 212 | break; 213 | } 214 | 215 | i++; 216 | } 217 | 218 | return indices; 219 | } 220 | 221 | // Check the physical device for specified extension support 222 | bool checkDeviceExtensionSupport(VkPhysicalDevice device, std::vector requiredExtensions) { 223 | uint32_t extensionCount; 224 | vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); 225 | 226 | std::vector availableExtensions(extensionCount); 227 | vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); 228 | 229 | std::set requiredExtensionSet(requiredExtensions.begin(), requiredExtensions.end()); 230 | 231 | for (const auto& extension : availableExtensions) { 232 | requiredExtensionSet.erase(extension.extensionName); 233 | } 234 | 235 | return requiredExtensionSet.empty(); 236 | } 237 | } 238 | 239 | void Instance::PickPhysicalDevice(std::vector deviceExtensions, QueueFlagBits requiredQueues, VkSurfaceKHR surface) { 240 | // List the graphics cards on the machine 241 | uint32_t deviceCount = 0; 242 | vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); 243 | 244 | if (deviceCount == 0) { 245 | throw std::runtime_error("Failed to find GPUs with Vulkan support"); 246 | } 247 | 248 | std::vector devices(deviceCount); 249 | vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); 250 | 251 | // Evaluate each GPU and check if it is suitable 252 | for (const auto& device : devices) { 253 | bool queueSupport = true; 254 | queueFamilyIndices = checkDeviceQueueSupport(device, requiredQueues, surface); 255 | for (unsigned int i = 0; i < requiredQueues.size(); ++i) { 256 | if (requiredQueues[i]) { 257 | queueSupport &= (queueFamilyIndices[i] >= 0); 258 | } 259 | } 260 | 261 | if (requiredQueues[QueueFlags::Present]) { 262 | // Get basic surface capabilities 263 | vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &surfaceCapabilities); 264 | 265 | // Query supported surface formats 266 | uint32_t formatCount; 267 | vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); 268 | 269 | if (formatCount != 0) { 270 | surfaceFormats.resize(formatCount); 271 | vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, surfaceFormats.data()); 272 | } 273 | 274 | // Query supported presentation modes 275 | uint32_t presentModeCount; 276 | vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); 277 | 278 | if (presentModeCount != 0) { 279 | presentModes.resize(presentModeCount); 280 | vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, presentModes.data()); 281 | } 282 | } 283 | 284 | if (queueSupport && 285 | checkDeviceExtensionSupport(device, deviceExtensions) && 286 | (!requiredQueues[QueueFlags::Present] || (!surfaceFormats.empty() && ! presentModes.empty())) 287 | ) { 288 | physicalDevice = device; 289 | break; 290 | } 291 | } 292 | 293 | this->deviceExtensions = deviceExtensions; 294 | 295 | if (physicalDevice == VK_NULL_HANDLE) { 296 | throw std::runtime_error("Failed to find a suitable GPU"); 297 | } 298 | 299 | vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); 300 | } 301 | 302 | Device* Instance::CreateDevice(QueueFlagBits requiredQueues, VkPhysicalDeviceFeatures deviceFeatures) { 303 | std::set uniqueQueueFamilies; 304 | bool queueSupport = true; 305 | for (unsigned int i = 0; i < requiredQueues.size(); ++i) { 306 | if (requiredQueues[i]) { 307 | queueSupport &= (queueFamilyIndices[i] >= 0); 308 | uniqueQueueFamilies.insert(queueFamilyIndices[i]); 309 | } 310 | } 311 | 312 | if (!queueSupport) { 313 | throw std::runtime_error("Device does not support requested queues"); 314 | } 315 | 316 | std::vector queueCreateInfos; 317 | float queuePriority = 1.0f; 318 | for (int queueFamily : uniqueQueueFamilies) { 319 | VkDeviceQueueCreateInfo queueCreateInfo = {}; 320 | queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 321 | queueCreateInfo.queueFamilyIndex = queueFamily; 322 | queueCreateInfo.queueCount = 1; 323 | queueCreateInfo.pQueuePriorities = &queuePriority; 324 | queueCreateInfos.push_back(queueCreateInfo); 325 | } 326 | 327 | // --- Create logical device --- 328 | VkDeviceCreateInfo createInfo = {}; 329 | createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 330 | 331 | createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); 332 | createInfo.pQueueCreateInfos = queueCreateInfos.data(); 333 | 334 | createInfo.pEnabledFeatures = &deviceFeatures; 335 | 336 | // Enable device-specific extensions and validation layers 337 | createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); 338 | createInfo.ppEnabledExtensionNames = deviceExtensions.data(); 339 | 340 | if (ENABLE_VALIDATION) { 341 | createInfo.enabledLayerCount = static_cast(validationLayers.size()); 342 | createInfo.ppEnabledLayerNames = validationLayers.data(); 343 | } else { 344 | createInfo.enabledLayerCount = 0; 345 | } 346 | 347 | VkDevice vkDevice; 348 | // Create logical device 349 | if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &vkDevice) != VK_SUCCESS) { 350 | throw std::runtime_error("Failed to create logical device"); 351 | } 352 | 353 | Device::Queues queues; 354 | for (unsigned int i = 0; i < requiredQueues.size(); ++i) { 355 | if (requiredQueues[i]) { 356 | vkGetDeviceQueue(vkDevice, queueFamilyIndices[i], 0, &queues[i]); 357 | } 358 | } 359 | 360 | return new Device(this, vkDevice, queues); 361 | } 362 | 363 | Instance::~Instance() { 364 | if (ENABLE_VALIDATION) { 365 | auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); 366 | if (func != nullptr) { 367 | func(instance, debugReportCallback, nullptr); 368 | } 369 | } 370 | 371 | vkDestroyInstance(instance, nullptr); 372 | } 373 | -------------------------------------------------------------------------------- /src/Instance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "QueueFlags.h" 7 | #include "Device.h" 8 | 9 | extern const bool ENABLE_VALIDATION; 10 | 11 | class Instance { 12 | 13 | public: 14 | Instance() = delete; 15 | Instance(const char* applicationName, unsigned int additionalExtensionCount = 0, const char** additionalExtensions = nullptr); 16 | 17 | VkInstance GetVkInstance(); 18 | VkPhysicalDevice GetPhysicalDevice(); 19 | const QueueFamilyIndices& GetQueueFamilyIndices() const; 20 | const VkSurfaceCapabilitiesKHR& GetSurfaceCapabilities() const; 21 | const std::vector& GetSurfaceFormats() const; 22 | const std::vector& GetPresentModes() const; 23 | 24 | uint32_t GetMemoryTypeIndex(uint32_t types, VkMemoryPropertyFlags properties) const; 25 | VkFormat GetSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) const; 26 | 27 | void PickPhysicalDevice(std::vector deviceExtensions, QueueFlagBits requiredQueues, VkSurfaceKHR surface = VK_NULL_HANDLE); 28 | 29 | Device* CreateDevice(QueueFlagBits requiredQueues, VkPhysicalDeviceFeatures deviceFeatures); 30 | 31 | ~Instance(); 32 | 33 | private: 34 | 35 | void initDebugReport(); 36 | 37 | VkInstance instance; 38 | VkDebugReportCallbackEXT debugReportCallback; 39 | std::vector deviceExtensions; 40 | VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; 41 | QueueFamilyIndices queueFamilyIndices; 42 | VkSurfaceCapabilitiesKHR surfaceCapabilities; 43 | std::vector surfaceFormats; 44 | std::vector presentModes; 45 | VkPhysicalDeviceMemoryProperties deviceMemoryProperties; 46 | }; 47 | -------------------------------------------------------------------------------- /src/Model.cpp: -------------------------------------------------------------------------------- 1 | #include "Model.h" 2 | #include "BufferUtils.h" 3 | #include "Image.h" 4 | 5 | Model::Model(Device* device, VkCommandPool commandPool, const std::vector &vertices, const std::vector &indices) 6 | : device(device), vertices(vertices), indices(indices) { 7 | 8 | if (vertices.size() > 0) { 9 | BufferUtils::CreateBufferFromData(device, commandPool, this->vertices.data(), vertices.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vertexBuffer, vertexBufferMemory); 10 | } 11 | 12 | if (indices.size() > 0) { 13 | BufferUtils::CreateBufferFromData(device, commandPool, this->indices.data(), indices.size() * sizeof(uint32_t), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, indexBuffer, indexBufferMemory); 14 | } 15 | 16 | modelBufferObject.modelMatrix = glm::mat4(1.0f); 17 | BufferUtils::CreateBufferFromData(device, commandPool, &modelBufferObject, sizeof(ModelBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, modelBuffer, modelBufferMemory); 18 | } 19 | 20 | Model::~Model() { 21 | if (indices.size() > 0) { 22 | vkDestroyBuffer(device->GetVkDevice(), indexBuffer, nullptr); 23 | vkFreeMemory(device->GetVkDevice(), indexBufferMemory, nullptr); 24 | } 25 | 26 | if (vertices.size() > 0) { 27 | vkDestroyBuffer(device->GetVkDevice(), vertexBuffer, nullptr); 28 | vkFreeMemory(device->GetVkDevice(), vertexBufferMemory, nullptr); 29 | } 30 | 31 | vkDestroyBuffer(device->GetVkDevice(), modelBuffer, nullptr); 32 | vkFreeMemory(device->GetVkDevice(), modelBufferMemory, nullptr); 33 | 34 | if (textureView != VK_NULL_HANDLE) { 35 | vkDestroyImageView(device->GetVkDevice(), textureView, nullptr); 36 | } 37 | 38 | if (textureSampler != VK_NULL_HANDLE) { 39 | vkDestroySampler(device->GetVkDevice(), textureSampler, nullptr); 40 | } 41 | } 42 | 43 | void Model::SetTexture(VkImage texture) { 44 | this->texture = texture; 45 | this->textureView = Image::CreateView(device, texture, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); 46 | 47 | // --- Specify all filters and transformations --- 48 | VkSamplerCreateInfo samplerInfo = {}; 49 | samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; 50 | 51 | // Interpolation of texels that are magnified or minified 52 | samplerInfo.magFilter = VK_FILTER_LINEAR; 53 | samplerInfo.minFilter = VK_FILTER_LINEAR; 54 | 55 | // Addressing mode 56 | samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; 57 | samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; 58 | samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; 59 | 60 | // Anisotropic filtering 61 | samplerInfo.anisotropyEnable = VK_TRUE; 62 | samplerInfo.maxAnisotropy = 16; 63 | 64 | // Border color 65 | samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; 66 | 67 | // Choose coordinate system for addressing texels --> [0, 1) here 68 | samplerInfo.unnormalizedCoordinates = VK_FALSE; 69 | 70 | // Comparison function used for filtering operations 71 | samplerInfo.compareEnable = VK_FALSE; 72 | samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; 73 | 74 | // Mipmapping 75 | samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; 76 | samplerInfo.mipLodBias = 0.0f; 77 | samplerInfo.minLod = 0.0f; 78 | samplerInfo.maxLod = 0.0f; 79 | 80 | if (vkCreateSampler(device->GetVkDevice(), &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) { 81 | throw std::runtime_error("Failed to create texture sampler"); 82 | } 83 | } 84 | 85 | const std::vector& Model::getVertices() const { 86 | return vertices; 87 | } 88 | 89 | VkBuffer Model::getVertexBuffer() const { 90 | return vertexBuffer; 91 | } 92 | 93 | const std::vector& Model::getIndices() const { 94 | return indices; 95 | } 96 | 97 | VkBuffer Model::getIndexBuffer() const { 98 | return indexBuffer; 99 | } 100 | 101 | const ModelBufferObject& Model::getModelBufferObject() const { 102 | return modelBufferObject; 103 | } 104 | 105 | VkBuffer Model::GetModelBuffer() const { 106 | return modelBuffer; 107 | } 108 | 109 | VkImageView Model::GetTextureView() const { 110 | return textureView; 111 | } 112 | 113 | VkSampler Model::GetTextureSampler() const { 114 | return textureSampler; 115 | } 116 | -------------------------------------------------------------------------------- /src/Model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Vertex.h" 8 | #include "Device.h" 9 | 10 | struct ModelBufferObject { 11 | glm::mat4 modelMatrix; 12 | }; 13 | 14 | class Model { 15 | protected: 16 | Device* device; 17 | 18 | std::vector vertices; 19 | VkBuffer vertexBuffer; 20 | VkDeviceMemory vertexBufferMemory; 21 | 22 | std::vector indices; 23 | VkBuffer indexBuffer; 24 | VkDeviceMemory indexBufferMemory; 25 | 26 | VkBuffer modelBuffer; 27 | VkDeviceMemory modelBufferMemory; 28 | 29 | ModelBufferObject modelBufferObject; 30 | 31 | VkImage texture = VK_NULL_HANDLE; 32 | VkImageView textureView = VK_NULL_HANDLE; 33 | VkSampler textureSampler = VK_NULL_HANDLE; 34 | 35 | public: 36 | Model() = delete; 37 | Model(Device* device, VkCommandPool commandPool, const std::vector &vertices, const std::vector &indices); 38 | virtual ~Model(); 39 | 40 | void SetTexture(VkImage texture); 41 | 42 | const std::vector& getVertices() const; 43 | 44 | VkBuffer getVertexBuffer() const; 45 | 46 | const std::vector& getIndices() const; 47 | 48 | VkBuffer getIndexBuffer() const; 49 | 50 | const ModelBufferObject& getModelBufferObject() const; 51 | 52 | VkBuffer GetModelBuffer() const; 53 | VkImageView GetTextureView() const; 54 | VkSampler GetTextureSampler() const; 55 | }; 56 | -------------------------------------------------------------------------------- /src/QueueFlags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum QueueFlags { 7 | Graphics, 8 | Compute, 9 | Transfer, 10 | Present, 11 | }; 12 | 13 | namespace QueueFlagBit { 14 | static constexpr unsigned int GraphicsBit = 1 << 0; 15 | static constexpr unsigned int ComputeBit = 1 << 1; 16 | static constexpr unsigned int TransferBit = 1 << 2; 17 | static constexpr unsigned int PresentBit = 1 << 3; 18 | } 19 | 20 | using QueueFlagBits = std::bitset; 21 | using QueueFamilyIndices = std::array; 22 | -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | #include "Instance.h" 3 | #include "ShaderModule.h" 4 | #include "Vertex.h" 5 | #include "Blades.h" 6 | #include "Camera.h" 7 | #include "Image.h" 8 | 9 | static constexpr unsigned int WORKGROUP_SIZE = 32; 10 | 11 | Renderer::Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera) 12 | : device(device), 13 | logicalDevice(device->GetVkDevice()), 14 | swapChain(swapChain), 15 | scene(scene), 16 | camera(camera) { 17 | 18 | CreateCommandPools(); 19 | CreateRenderPass(); 20 | CreateCameraDescriptorSetLayout(); 21 | CreateModelDescriptorSetLayout(); 22 | CreateTimeDescriptorSetLayout(); 23 | CreateComputeDescriptorSetLayout(); 24 | CreateDescriptorPool(); 25 | CreateCameraDescriptorSet(); 26 | CreateModelDescriptorSets(); 27 | CreateGrassDescriptorSets(); 28 | CreateTimeDescriptorSet(); 29 | CreateComputeDescriptorSets(); 30 | CreateFrameResources(); 31 | CreateGraphicsPipeline(); 32 | CreateGrassPipeline(); 33 | CreateComputePipeline(); 34 | RecordCommandBuffers(); 35 | RecordComputeCommandBuffer(); 36 | } 37 | 38 | void Renderer::CreateCommandPools() { 39 | VkCommandPoolCreateInfo graphicsPoolInfo = {}; 40 | graphicsPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 41 | graphicsPoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Graphics]; 42 | graphicsPoolInfo.flags = 0; 43 | 44 | if (vkCreateCommandPool(logicalDevice, &graphicsPoolInfo, nullptr, &graphicsCommandPool) != VK_SUCCESS) { 45 | throw std::runtime_error("Failed to create command pool"); 46 | } 47 | 48 | VkCommandPoolCreateInfo computePoolInfo = {}; 49 | computePoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 50 | computePoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Compute]; 51 | computePoolInfo.flags = 0; 52 | 53 | if (vkCreateCommandPool(logicalDevice, &computePoolInfo, nullptr, &computeCommandPool) != VK_SUCCESS) { 54 | throw std::runtime_error("Failed to create command pool"); 55 | } 56 | } 57 | 58 | void Renderer::CreateRenderPass() { 59 | // Color buffer attachment represented by one of the images from the swap chain 60 | VkAttachmentDescription colorAttachment = {}; 61 | colorAttachment.format = swapChain->GetVkImageFormat(); 62 | colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; 63 | colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 64 | colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 65 | colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 66 | colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 67 | colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 68 | colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 69 | 70 | // Create a color attachment reference to be used with subpass 71 | VkAttachmentReference colorAttachmentRef = {}; 72 | colorAttachmentRef.attachment = 0; 73 | colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 74 | 75 | // Depth buffer attachment 76 | VkFormat depthFormat = device->GetInstance()->GetSupportedFormat({ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); 77 | VkAttachmentDescription depthAttachment = {}; 78 | depthAttachment.format = depthFormat; 79 | depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT; 80 | depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 81 | depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 82 | depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 83 | depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 84 | depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 85 | depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 86 | 87 | // Create a depth attachment reference 88 | VkAttachmentReference depthAttachmentRef = {}; 89 | depthAttachmentRef.attachment = 1; 90 | depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 91 | 92 | // Create subpass description 93 | VkSubpassDescription subpass = {}; 94 | subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 95 | subpass.colorAttachmentCount = 1; 96 | subpass.pColorAttachments = &colorAttachmentRef; 97 | subpass.pDepthStencilAttachment = &depthAttachmentRef; 98 | 99 | std::array attachments = { colorAttachment, depthAttachment }; 100 | 101 | // Specify subpass dependency 102 | VkSubpassDependency dependency = {}; 103 | dependency.srcSubpass = VK_SUBPASS_EXTERNAL; 104 | dependency.dstSubpass = 0; 105 | dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 106 | dependency.srcAccessMask = 0; 107 | dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 108 | dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; 109 | 110 | // Create render pass 111 | VkRenderPassCreateInfo renderPassInfo = {}; 112 | renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 113 | renderPassInfo.attachmentCount = static_cast(attachments.size()); 114 | renderPassInfo.pAttachments = attachments.data(); 115 | renderPassInfo.subpassCount = 1; 116 | renderPassInfo.pSubpasses = &subpass; 117 | renderPassInfo.dependencyCount = 1; 118 | renderPassInfo.pDependencies = &dependency; 119 | 120 | if (vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { 121 | throw std::runtime_error("Failed to create render pass"); 122 | } 123 | } 124 | 125 | void Renderer::CreateCameraDescriptorSetLayout() { 126 | // Describe the binding of the descriptor set layout 127 | VkDescriptorSetLayoutBinding uboLayoutBinding = {}; 128 | uboLayoutBinding.binding = 0; 129 | uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 130 | uboLayoutBinding.descriptorCount = 1; 131 | uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL; 132 | uboLayoutBinding.pImmutableSamplers = nullptr; 133 | 134 | std::vector bindings = { uboLayoutBinding }; 135 | 136 | // Create the descriptor set layout 137 | VkDescriptorSetLayoutCreateInfo layoutInfo = {}; 138 | layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; 139 | layoutInfo.bindingCount = static_cast(bindings.size()); 140 | layoutInfo.pBindings = bindings.data(); 141 | 142 | if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &cameraDescriptorSetLayout) != VK_SUCCESS) { 143 | throw std::runtime_error("Failed to create descriptor set layout"); 144 | } 145 | } 146 | 147 | void Renderer::CreateModelDescriptorSetLayout() { 148 | VkDescriptorSetLayoutBinding uboLayoutBinding = {}; 149 | uboLayoutBinding.binding = 0; 150 | uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 151 | uboLayoutBinding.descriptorCount = 1; 152 | uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; 153 | uboLayoutBinding.pImmutableSamplers = nullptr; 154 | 155 | VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; 156 | samplerLayoutBinding.binding = 1; 157 | samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; 158 | samplerLayoutBinding.descriptorCount = 1; 159 | samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; 160 | samplerLayoutBinding.pImmutableSamplers = nullptr; 161 | 162 | std::vector bindings = { uboLayoutBinding, samplerLayoutBinding }; 163 | 164 | // Create the descriptor set layout 165 | VkDescriptorSetLayoutCreateInfo layoutInfo = {}; 166 | layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; 167 | layoutInfo.bindingCount = static_cast(bindings.size()); 168 | layoutInfo.pBindings = bindings.data(); 169 | 170 | if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &modelDescriptorSetLayout) != VK_SUCCESS) { 171 | throw std::runtime_error("Failed to create descriptor set layout"); 172 | } 173 | } 174 | 175 | void Renderer::CreateTimeDescriptorSetLayout() { 176 | // Describe the binding of the descriptor set layout 177 | VkDescriptorSetLayoutBinding uboLayoutBinding = {}; 178 | uboLayoutBinding.binding = 0; 179 | uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 180 | uboLayoutBinding.descriptorCount = 1; 181 | uboLayoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; 182 | uboLayoutBinding.pImmutableSamplers = nullptr; 183 | 184 | std::vector bindings = { uboLayoutBinding }; 185 | 186 | // Create the descriptor set layout 187 | VkDescriptorSetLayoutCreateInfo layoutInfo = {}; 188 | layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; 189 | layoutInfo.bindingCount = static_cast(bindings.size()); 190 | layoutInfo.pBindings = bindings.data(); 191 | 192 | if (vkCreateDescriptorSetLayout(logicalDevice, &layoutInfo, nullptr, &timeDescriptorSetLayout) != VK_SUCCESS) { 193 | throw std::runtime_error("Failed to create descriptor set layout"); 194 | } 195 | } 196 | 197 | void Renderer::CreateComputeDescriptorSetLayout() { 198 | // TODO: Create the descriptor set layout for the compute pipeline 199 | // Remember this is like a class definition stating why types of information 200 | // will be stored at each binding 201 | } 202 | 203 | void Renderer::CreateDescriptorPool() { 204 | // Describe which descriptor types that the descriptor sets will contain 205 | std::vector poolSizes = { 206 | // Camera 207 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1}, 208 | 209 | // Models + Blades 210 | { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, 211 | 212 | // Models + Blades 213 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , static_cast(scene->GetModels().size() + scene->GetBlades().size()) }, 214 | 215 | // Time (compute) 216 | { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 1 }, 217 | 218 | // TODO: Add any additional types and counts of descriptors you will need to allocate 219 | }; 220 | 221 | VkDescriptorPoolCreateInfo poolInfo = {}; 222 | poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; 223 | poolInfo.poolSizeCount = static_cast(poolSizes.size()); 224 | poolInfo.pPoolSizes = poolSizes.data(); 225 | poolInfo.maxSets = 5; 226 | 227 | if (vkCreateDescriptorPool(logicalDevice, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { 228 | throw std::runtime_error("Failed to create descriptor pool"); 229 | } 230 | } 231 | 232 | void Renderer::CreateCameraDescriptorSet() { 233 | // Describe the desciptor set 234 | VkDescriptorSetLayout layouts[] = { cameraDescriptorSetLayout }; 235 | VkDescriptorSetAllocateInfo allocInfo = {}; 236 | allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; 237 | allocInfo.descriptorPool = descriptorPool; 238 | allocInfo.descriptorSetCount = 1; 239 | allocInfo.pSetLayouts = layouts; 240 | 241 | // Allocate descriptor sets 242 | if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, &cameraDescriptorSet) != VK_SUCCESS) { 243 | throw std::runtime_error("Failed to allocate descriptor set"); 244 | } 245 | 246 | // Configure the descriptors to refer to buffers 247 | VkDescriptorBufferInfo cameraBufferInfo = {}; 248 | cameraBufferInfo.buffer = camera->GetBuffer(); 249 | cameraBufferInfo.offset = 0; 250 | cameraBufferInfo.range = sizeof(CameraBufferObject); 251 | 252 | std::array descriptorWrites = {}; 253 | descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 254 | descriptorWrites[0].dstSet = cameraDescriptorSet; 255 | descriptorWrites[0].dstBinding = 0; 256 | descriptorWrites[0].dstArrayElement = 0; 257 | descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 258 | descriptorWrites[0].descriptorCount = 1; 259 | descriptorWrites[0].pBufferInfo = &cameraBufferInfo; 260 | descriptorWrites[0].pImageInfo = nullptr; 261 | descriptorWrites[0].pTexelBufferView = nullptr; 262 | 263 | // Update descriptor sets 264 | vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); 265 | } 266 | 267 | void Renderer::CreateModelDescriptorSets() { 268 | modelDescriptorSets.resize(scene->GetModels().size()); 269 | 270 | // Describe the desciptor set 271 | VkDescriptorSetLayout layouts[] = { modelDescriptorSetLayout }; 272 | VkDescriptorSetAllocateInfo allocInfo = {}; 273 | allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; 274 | allocInfo.descriptorPool = descriptorPool; 275 | allocInfo.descriptorSetCount = static_cast(modelDescriptorSets.size()); 276 | allocInfo.pSetLayouts = layouts; 277 | 278 | // Allocate descriptor sets 279 | if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, modelDescriptorSets.data()) != VK_SUCCESS) { 280 | throw std::runtime_error("Failed to allocate descriptor set"); 281 | } 282 | 283 | std::vector descriptorWrites(2 * modelDescriptorSets.size()); 284 | 285 | for (uint32_t i = 0; i < scene->GetModels().size(); ++i) { 286 | VkDescriptorBufferInfo modelBufferInfo = {}; 287 | modelBufferInfo.buffer = scene->GetModels()[i]->GetModelBuffer(); 288 | modelBufferInfo.offset = 0; 289 | modelBufferInfo.range = sizeof(ModelBufferObject); 290 | 291 | // Bind image and sampler resources to the descriptor 292 | VkDescriptorImageInfo imageInfo = {}; 293 | imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 294 | imageInfo.imageView = scene->GetModels()[i]->GetTextureView(); 295 | imageInfo.sampler = scene->GetModels()[i]->GetTextureSampler(); 296 | 297 | descriptorWrites[2 * i + 0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 298 | descriptorWrites[2 * i + 0].dstSet = modelDescriptorSets[i]; 299 | descriptorWrites[2 * i + 0].dstBinding = 0; 300 | descriptorWrites[2 * i + 0].dstArrayElement = 0; 301 | descriptorWrites[2 * i + 0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 302 | descriptorWrites[2 * i + 0].descriptorCount = 1; 303 | descriptorWrites[2 * i + 0].pBufferInfo = &modelBufferInfo; 304 | descriptorWrites[2 * i + 0].pImageInfo = nullptr; 305 | descriptorWrites[2 * i + 0].pTexelBufferView = nullptr; 306 | 307 | descriptorWrites[2 * i + 1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 308 | descriptorWrites[2 * i + 1].dstSet = modelDescriptorSets[i]; 309 | descriptorWrites[2 * i + 1].dstBinding = 1; 310 | descriptorWrites[2 * i + 1].dstArrayElement = 0; 311 | descriptorWrites[2 * i + 1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; 312 | descriptorWrites[2 * i + 1].descriptorCount = 1; 313 | descriptorWrites[2 * i + 1].pImageInfo = &imageInfo; 314 | } 315 | 316 | // Update descriptor sets 317 | vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); 318 | } 319 | 320 | void Renderer::CreateGrassDescriptorSets() { 321 | // TODO: Create Descriptor sets for the grass. 322 | // This should involve creating descriptor sets which point to the model matrix of each group of grass blades 323 | } 324 | 325 | void Renderer::CreateTimeDescriptorSet() { 326 | // Describe the desciptor set 327 | VkDescriptorSetLayout layouts[] = { timeDescriptorSetLayout }; 328 | VkDescriptorSetAllocateInfo allocInfo = {}; 329 | allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; 330 | allocInfo.descriptorPool = descriptorPool; 331 | allocInfo.descriptorSetCount = 1; 332 | allocInfo.pSetLayouts = layouts; 333 | 334 | // Allocate descriptor sets 335 | if (vkAllocateDescriptorSets(logicalDevice, &allocInfo, &timeDescriptorSet) != VK_SUCCESS) { 336 | throw std::runtime_error("Failed to allocate descriptor set"); 337 | } 338 | 339 | // Configure the descriptors to refer to buffers 340 | VkDescriptorBufferInfo timeBufferInfo = {}; 341 | timeBufferInfo.buffer = scene->GetTimeBuffer(); 342 | timeBufferInfo.offset = 0; 343 | timeBufferInfo.range = sizeof(Time); 344 | 345 | std::array descriptorWrites = {}; 346 | descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; 347 | descriptorWrites[0].dstSet = timeDescriptorSet; 348 | descriptorWrites[0].dstBinding = 0; 349 | descriptorWrites[0].dstArrayElement = 0; 350 | descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; 351 | descriptorWrites[0].descriptorCount = 1; 352 | descriptorWrites[0].pBufferInfo = &timeBufferInfo; 353 | descriptorWrites[0].pImageInfo = nullptr; 354 | descriptorWrites[0].pTexelBufferView = nullptr; 355 | 356 | // Update descriptor sets 357 | vkUpdateDescriptorSets(logicalDevice, static_cast(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr); 358 | } 359 | 360 | void Renderer::CreateComputeDescriptorSets() { 361 | // TODO: Create Descriptor sets for the compute pipeline 362 | // The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades 363 | } 364 | 365 | void Renderer::CreateGraphicsPipeline() { 366 | VkShaderModule vertShaderModule = ShaderModule::Create("shaders/graphics.vert.spv", logicalDevice); 367 | VkShaderModule fragShaderModule = ShaderModule::Create("shaders/graphics.frag.spv", logicalDevice); 368 | 369 | // Assign each shader module to the appropriate stage in the pipeline 370 | VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; 371 | vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 372 | vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; 373 | vertShaderStageInfo.module = vertShaderModule; 374 | vertShaderStageInfo.pName = "main"; 375 | 376 | VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; 377 | fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 378 | fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; 379 | fragShaderStageInfo.module = fragShaderModule; 380 | fragShaderStageInfo.pName = "main"; 381 | 382 | VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; 383 | 384 | // --- Set up fixed-function stages --- 385 | 386 | // Vertex input 387 | VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; 388 | vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 389 | 390 | auto bindingDescription = Vertex::getBindingDescription(); 391 | auto attributeDescriptions = Vertex::getAttributeDescriptions(); 392 | 393 | vertexInputInfo.vertexBindingDescriptionCount = 1; 394 | vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; 395 | vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); 396 | vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); 397 | 398 | // Input assembly 399 | VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; 400 | inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 401 | inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 402 | inputAssembly.primitiveRestartEnable = VK_FALSE; 403 | 404 | // Viewports and Scissors (rectangles that define in which regions pixels are stored) 405 | VkViewport viewport = {}; 406 | viewport.x = 0.0f; 407 | viewport.y = 0.0f; 408 | viewport.width = static_cast(swapChain->GetVkExtent().width); 409 | viewport.height = static_cast(swapChain->GetVkExtent().height); 410 | viewport.minDepth = 0.0f; 411 | viewport.maxDepth = 1.0f; 412 | 413 | VkRect2D scissor = {}; 414 | scissor.offset = { 0, 0 }; 415 | scissor.extent = swapChain->GetVkExtent(); 416 | 417 | VkPipelineViewportStateCreateInfo viewportState = {}; 418 | viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 419 | viewportState.viewportCount = 1; 420 | viewportState.pViewports = &viewport; 421 | viewportState.scissorCount = 1; 422 | viewportState.pScissors = &scissor; 423 | 424 | // Rasterizer 425 | VkPipelineRasterizationStateCreateInfo rasterizer = {}; 426 | rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 427 | rasterizer.depthClampEnable = VK_FALSE; 428 | rasterizer.rasterizerDiscardEnable = VK_FALSE; 429 | rasterizer.polygonMode = VK_POLYGON_MODE_FILL; 430 | rasterizer.lineWidth = 1.0f; 431 | rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; 432 | rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; 433 | rasterizer.depthBiasEnable = VK_FALSE; 434 | rasterizer.depthBiasConstantFactor = 0.0f; 435 | rasterizer.depthBiasClamp = 0.0f; 436 | rasterizer.depthBiasSlopeFactor = 0.0f; 437 | 438 | // Multisampling (turned off here) 439 | VkPipelineMultisampleStateCreateInfo multisampling = {}; 440 | multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 441 | multisampling.sampleShadingEnable = VK_FALSE; 442 | multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 443 | multisampling.minSampleShading = 1.0f; 444 | multisampling.pSampleMask = nullptr; 445 | multisampling.alphaToCoverageEnable = VK_FALSE; 446 | multisampling.alphaToOneEnable = VK_FALSE; 447 | 448 | // Depth testing 449 | VkPipelineDepthStencilStateCreateInfo depthStencil = {}; 450 | depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; 451 | depthStencil.depthTestEnable = VK_TRUE; 452 | depthStencil.depthWriteEnable = VK_TRUE; 453 | depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; 454 | depthStencil.depthBoundsTestEnable = VK_FALSE; 455 | depthStencil.minDepthBounds = 0.0f; 456 | depthStencil.maxDepthBounds = 1.0f; 457 | depthStencil.stencilTestEnable = VK_FALSE; 458 | 459 | // Color blending (turned off here, but showing options for learning) 460 | // --> Configuration per attached framebuffer 461 | VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; 462 | colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; 463 | colorBlendAttachment.blendEnable = VK_FALSE; 464 | colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; 465 | colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; 466 | colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; 467 | colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; 468 | colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; 469 | colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; 470 | 471 | // --> Global color blending settings 472 | VkPipelineColorBlendStateCreateInfo colorBlending = {}; 473 | colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; 474 | colorBlending.logicOpEnable = VK_FALSE; 475 | colorBlending.logicOp = VK_LOGIC_OP_COPY; 476 | colorBlending.attachmentCount = 1; 477 | colorBlending.pAttachments = &colorBlendAttachment; 478 | colorBlending.blendConstants[0] = 0.0f; 479 | colorBlending.blendConstants[1] = 0.0f; 480 | colorBlending.blendConstants[2] = 0.0f; 481 | colorBlending.blendConstants[3] = 0.0f; 482 | 483 | std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; 484 | 485 | // Pipeline layout: used to specify uniform values 486 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; 487 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 488 | pipelineLayoutInfo.setLayoutCount = static_cast(descriptorSetLayouts.size()); 489 | pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data(); 490 | pipelineLayoutInfo.pushConstantRangeCount = 0; 491 | pipelineLayoutInfo.pPushConstantRanges = 0; 492 | 493 | if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &graphicsPipelineLayout) != VK_SUCCESS) { 494 | throw std::runtime_error("Failed to create pipeline layout"); 495 | } 496 | 497 | // --- Create graphics pipeline --- 498 | VkGraphicsPipelineCreateInfo pipelineInfo = {}; 499 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 500 | pipelineInfo.stageCount = 2; 501 | pipelineInfo.pStages = shaderStages; 502 | pipelineInfo.pVertexInputState = &vertexInputInfo; 503 | pipelineInfo.pInputAssemblyState = &inputAssembly; 504 | pipelineInfo.pViewportState = &viewportState; 505 | pipelineInfo.pRasterizationState = &rasterizer; 506 | pipelineInfo.pMultisampleState = &multisampling; 507 | pipelineInfo.pDepthStencilState = &depthStencil; 508 | pipelineInfo.pColorBlendState = &colorBlending; 509 | pipelineInfo.pDynamicState = nullptr; 510 | pipelineInfo.layout = graphicsPipelineLayout; 511 | pipelineInfo.renderPass = renderPass; 512 | pipelineInfo.subpass = 0; 513 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; 514 | pipelineInfo.basePipelineIndex = -1; 515 | 516 | if (vkCreateGraphicsPipelines(logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { 517 | throw std::runtime_error("Failed to create graphics pipeline"); 518 | } 519 | 520 | vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); 521 | vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); 522 | } 523 | 524 | void Renderer::CreateGrassPipeline() { 525 | // --- Set up programmable shaders --- 526 | VkShaderModule vertShaderModule = ShaderModule::Create("shaders/grass.vert.spv", logicalDevice); 527 | VkShaderModule tescShaderModule = ShaderModule::Create("shaders/grass.tesc.spv", logicalDevice); 528 | VkShaderModule teseShaderModule = ShaderModule::Create("shaders/grass.tese.spv", logicalDevice); 529 | VkShaderModule fragShaderModule = ShaderModule::Create("shaders/grass.frag.spv", logicalDevice); 530 | 531 | // Assign each shader module to the appropriate stage in the pipeline 532 | VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; 533 | vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 534 | vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; 535 | vertShaderStageInfo.module = vertShaderModule; 536 | vertShaderStageInfo.pName = "main"; 537 | 538 | VkPipelineShaderStageCreateInfo tescShaderStageInfo = {}; 539 | tescShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 540 | tescShaderStageInfo.stage = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; 541 | tescShaderStageInfo.module = tescShaderModule; 542 | tescShaderStageInfo.pName = "main"; 543 | 544 | VkPipelineShaderStageCreateInfo teseShaderStageInfo = {}; 545 | teseShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 546 | teseShaderStageInfo.stage = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; 547 | teseShaderStageInfo.module = teseShaderModule; 548 | teseShaderStageInfo.pName = "main"; 549 | 550 | VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; 551 | fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 552 | fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; 553 | fragShaderStageInfo.module = fragShaderModule; 554 | fragShaderStageInfo.pName = "main"; 555 | 556 | VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, tescShaderStageInfo, teseShaderStageInfo, fragShaderStageInfo }; 557 | 558 | // --- Set up fixed-function stages --- 559 | 560 | // Vertex input 561 | VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; 562 | vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 563 | 564 | auto bindingDescription = Blade::getBindingDescription(); 565 | auto attributeDescriptions = Blade::getAttributeDescriptions(); 566 | 567 | vertexInputInfo.vertexBindingDescriptionCount = 1; 568 | vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; 569 | vertexInputInfo.vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()); 570 | vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); 571 | 572 | // Input Assembly 573 | VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; 574 | inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 575 | inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; 576 | inputAssembly.primitiveRestartEnable = VK_FALSE; 577 | 578 | // Viewports and Scissors (rectangles that define in which regions pixels are stored) 579 | VkViewport viewport = {}; 580 | viewport.x = 0.0f; 581 | viewport.y = 0.0f; 582 | viewport.width = static_cast(swapChain->GetVkExtent().width); 583 | viewport.height = static_cast(swapChain->GetVkExtent().height); 584 | viewport.minDepth = 0.0f; 585 | viewport.maxDepth = 1.0f; 586 | 587 | VkRect2D scissor = {}; 588 | scissor.offset = { 0, 0 }; 589 | scissor.extent = swapChain->GetVkExtent(); 590 | 591 | VkPipelineViewportStateCreateInfo viewportState = {}; 592 | viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 593 | viewportState.viewportCount = 1; 594 | viewportState.pViewports = &viewport; 595 | viewportState.scissorCount = 1; 596 | viewportState.pScissors = &scissor; 597 | 598 | // Rasterizer 599 | VkPipelineRasterizationStateCreateInfo rasterizer = {}; 600 | rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 601 | rasterizer.depthClampEnable = VK_FALSE; 602 | rasterizer.rasterizerDiscardEnable = VK_FALSE; 603 | rasterizer.polygonMode = VK_POLYGON_MODE_FILL; 604 | rasterizer.lineWidth = 1.0f; 605 | rasterizer.cullMode = VK_CULL_MODE_NONE; 606 | rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; 607 | rasterizer.depthBiasEnable = VK_FALSE; 608 | rasterizer.depthBiasConstantFactor = 0.0f; 609 | rasterizer.depthBiasClamp = 0.0f; 610 | rasterizer.depthBiasSlopeFactor = 0.0f; 611 | 612 | // Multisampling (turned off here) 613 | VkPipelineMultisampleStateCreateInfo multisampling = {}; 614 | multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 615 | multisampling.sampleShadingEnable = VK_FALSE; 616 | multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; 617 | multisampling.minSampleShading = 1.0f; 618 | multisampling.pSampleMask = nullptr; 619 | multisampling.alphaToCoverageEnable = VK_FALSE; 620 | multisampling.alphaToOneEnable = VK_FALSE; 621 | 622 | // Depth testing 623 | VkPipelineDepthStencilStateCreateInfo depthStencil = {}; 624 | depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; 625 | depthStencil.depthTestEnable = VK_TRUE; 626 | depthStencil.depthWriteEnable = VK_TRUE; 627 | depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; 628 | depthStencil.depthBoundsTestEnable = VK_FALSE; 629 | depthStencil.minDepthBounds = 0.0f; 630 | depthStencil.maxDepthBounds = 1.0f; 631 | depthStencil.stencilTestEnable = VK_FALSE; 632 | 633 | // Color blending (turned off here, but showing options for learning) 634 | // --> Configuration per attached framebuffer 635 | VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; 636 | colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; 637 | colorBlendAttachment.blendEnable = VK_FALSE; 638 | colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; 639 | colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; 640 | colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; 641 | colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; 642 | colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; 643 | colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; 644 | 645 | // --> Global color blending settings 646 | VkPipelineColorBlendStateCreateInfo colorBlending = {}; 647 | colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; 648 | colorBlending.logicOpEnable = VK_FALSE; 649 | colorBlending.logicOp = VK_LOGIC_OP_COPY; 650 | colorBlending.attachmentCount = 1; 651 | colorBlending.pAttachments = &colorBlendAttachment; 652 | colorBlending.blendConstants[0] = 0.0f; 653 | colorBlending.blendConstants[1] = 0.0f; 654 | colorBlending.blendConstants[2] = 0.0f; 655 | colorBlending.blendConstants[3] = 0.0f; 656 | 657 | std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, modelDescriptorSetLayout }; 658 | 659 | // Pipeline layout: used to specify uniform values 660 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; 661 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 662 | pipelineLayoutInfo.setLayoutCount = static_cast(descriptorSetLayouts.size()); 663 | pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data(); 664 | pipelineLayoutInfo.pushConstantRangeCount = 0; 665 | pipelineLayoutInfo.pPushConstantRanges = 0; 666 | 667 | if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &grassPipelineLayout) != VK_SUCCESS) { 668 | throw std::runtime_error("Failed to create pipeline layout"); 669 | } 670 | 671 | // Tessellation state 672 | VkPipelineTessellationStateCreateInfo tessellationInfo = {}; 673 | tessellationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; 674 | tessellationInfo.pNext = NULL; 675 | tessellationInfo.flags = 0; 676 | tessellationInfo.patchControlPoints = 1; 677 | 678 | // --- Create graphics pipeline --- 679 | VkGraphicsPipelineCreateInfo pipelineInfo = {}; 680 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 681 | pipelineInfo.stageCount = 4; 682 | pipelineInfo.pStages = shaderStages; 683 | pipelineInfo.pVertexInputState = &vertexInputInfo; 684 | pipelineInfo.pInputAssemblyState = &inputAssembly; 685 | pipelineInfo.pViewportState = &viewportState; 686 | pipelineInfo.pRasterizationState = &rasterizer; 687 | pipelineInfo.pMultisampleState = &multisampling; 688 | pipelineInfo.pDepthStencilState = &depthStencil; 689 | pipelineInfo.pColorBlendState = &colorBlending; 690 | pipelineInfo.pTessellationState = &tessellationInfo; 691 | pipelineInfo.pDynamicState = nullptr; 692 | pipelineInfo.layout = grassPipelineLayout; 693 | pipelineInfo.renderPass = renderPass; 694 | pipelineInfo.subpass = 0; 695 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; 696 | pipelineInfo.basePipelineIndex = -1; 697 | 698 | if (vkCreateGraphicsPipelines(logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &grassPipeline) != VK_SUCCESS) { 699 | throw std::runtime_error("Failed to create graphics pipeline"); 700 | } 701 | 702 | // No need for the shader modules anymore 703 | vkDestroyShaderModule(logicalDevice, vertShaderModule, nullptr); 704 | vkDestroyShaderModule(logicalDevice, tescShaderModule, nullptr); 705 | vkDestroyShaderModule(logicalDevice, teseShaderModule, nullptr); 706 | vkDestroyShaderModule(logicalDevice, fragShaderModule, nullptr); 707 | } 708 | 709 | void Renderer::CreateComputePipeline() { 710 | // Set up programmable shaders 711 | VkShaderModule computeShaderModule = ShaderModule::Create("shaders/compute.comp.spv", logicalDevice); 712 | 713 | VkPipelineShaderStageCreateInfo computeShaderStageInfo = {}; 714 | computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; 715 | computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; 716 | computeShaderStageInfo.module = computeShaderModule; 717 | computeShaderStageInfo.pName = "main"; 718 | 719 | // TODO: Add the compute dsecriptor set layout you create to this list 720 | std::vector descriptorSetLayouts = { cameraDescriptorSetLayout, timeDescriptorSetLayout }; 721 | 722 | // Create pipeline layout 723 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; 724 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 725 | pipelineLayoutInfo.setLayoutCount = static_cast(descriptorSetLayouts.size()); 726 | pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data(); 727 | pipelineLayoutInfo.pushConstantRangeCount = 0; 728 | pipelineLayoutInfo.pPushConstantRanges = 0; 729 | 730 | if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &computePipelineLayout) != VK_SUCCESS) { 731 | throw std::runtime_error("Failed to create pipeline layout"); 732 | } 733 | 734 | // Create compute pipeline 735 | VkComputePipelineCreateInfo pipelineInfo = {}; 736 | pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; 737 | pipelineInfo.stage = computeShaderStageInfo; 738 | pipelineInfo.layout = computePipelineLayout; 739 | pipelineInfo.pNext = nullptr; 740 | pipelineInfo.flags = 0; 741 | pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; 742 | pipelineInfo.basePipelineIndex = -1; 743 | 744 | if (vkCreateComputePipelines(logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) { 745 | throw std::runtime_error("Failed to create compute pipeline"); 746 | } 747 | 748 | // No need for shader modules anymore 749 | vkDestroyShaderModule(logicalDevice, computeShaderModule, nullptr); 750 | } 751 | 752 | void Renderer::CreateFrameResources() { 753 | imageViews.resize(swapChain->GetCount()); 754 | 755 | for (uint32_t i = 0; i < swapChain->GetCount(); i++) { 756 | // --- Create an image view for each swap chain image --- 757 | VkImageViewCreateInfo createInfo = {}; 758 | createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 759 | createInfo.image = swapChain->GetVkImage(i); 760 | 761 | // Specify how the image data should be interpreted 762 | createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; 763 | createInfo.format = swapChain->GetVkImageFormat(); 764 | 765 | // Specify color channel mappings (can be used for swizzling) 766 | createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; 767 | createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; 768 | createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; 769 | createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 770 | 771 | // Describe the image's purpose and which part of the image should be accessed 772 | createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 773 | createInfo.subresourceRange.baseMipLevel = 0; 774 | createInfo.subresourceRange.levelCount = 1; 775 | createInfo.subresourceRange.baseArrayLayer = 0; 776 | createInfo.subresourceRange.layerCount = 1; 777 | 778 | // Create the image view 779 | if (vkCreateImageView(logicalDevice, &createInfo, nullptr, &imageViews[i]) != VK_SUCCESS) { 780 | throw std::runtime_error("Failed to create image views"); 781 | } 782 | } 783 | 784 | VkFormat depthFormat = device->GetInstance()->GetSupportedFormat({ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); 785 | // CREATE DEPTH IMAGE 786 | Image::Create(device, 787 | swapChain->GetVkExtent().width, 788 | swapChain->GetVkExtent().height, 789 | depthFormat, 790 | VK_IMAGE_TILING_OPTIMAL, 791 | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 792 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 793 | depthImage, 794 | depthImageMemory 795 | ); 796 | 797 | depthImageView = Image::CreateView(device, depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); 798 | 799 | // Transition the image for use as depth-stencil 800 | Image::TransitionLayout(device, graphicsCommandPool, depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); 801 | 802 | 803 | // CREATE FRAMEBUFFERS 804 | framebuffers.resize(swapChain->GetCount()); 805 | for (size_t i = 0; i < swapChain->GetCount(); i++) { 806 | std::vector attachments = { 807 | imageViews[i], 808 | depthImageView 809 | }; 810 | 811 | VkFramebufferCreateInfo framebufferInfo = {}; 812 | framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 813 | framebufferInfo.renderPass = renderPass; 814 | framebufferInfo.attachmentCount = static_cast(attachments.size()); 815 | framebufferInfo.pAttachments = attachments.data(); 816 | framebufferInfo.width = swapChain->GetVkExtent().width; 817 | framebufferInfo.height = swapChain->GetVkExtent().height; 818 | framebufferInfo.layers = 1; 819 | 820 | if (vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &framebuffers[i]) != VK_SUCCESS) { 821 | throw std::runtime_error("Failed to create framebuffer"); 822 | } 823 | 824 | } 825 | } 826 | 827 | void Renderer::DestroyFrameResources() { 828 | for (size_t i = 0; i < imageViews.size(); i++) { 829 | vkDestroyImageView(logicalDevice, imageViews[i], nullptr); 830 | } 831 | 832 | vkDestroyImageView(logicalDevice, depthImageView, nullptr); 833 | vkFreeMemory(logicalDevice, depthImageMemory, nullptr); 834 | vkDestroyImage(logicalDevice, depthImage, nullptr); 835 | 836 | for (size_t i = 0; i < framebuffers.size(); i++) { 837 | vkDestroyFramebuffer(logicalDevice, framebuffers[i], nullptr); 838 | } 839 | } 840 | 841 | void Renderer::RecreateFrameResources() { 842 | vkDestroyPipeline(logicalDevice, graphicsPipeline, nullptr); 843 | vkDestroyPipeline(logicalDevice, grassPipeline, nullptr); 844 | vkDestroyPipelineLayout(logicalDevice, graphicsPipelineLayout, nullptr); 845 | vkDestroyPipelineLayout(logicalDevice, grassPipelineLayout, nullptr); 846 | vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); 847 | 848 | DestroyFrameResources(); 849 | CreateFrameResources(); 850 | CreateGraphicsPipeline(); 851 | CreateGrassPipeline(); 852 | RecordCommandBuffers(); 853 | } 854 | 855 | void Renderer::RecordComputeCommandBuffer() { 856 | // Specify the command pool and number of buffers to allocate 857 | VkCommandBufferAllocateInfo allocInfo = {}; 858 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 859 | allocInfo.commandPool = computeCommandPool; 860 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 861 | allocInfo.commandBufferCount = 1; 862 | 863 | if (vkAllocateCommandBuffers(logicalDevice, &allocInfo, &computeCommandBuffer) != VK_SUCCESS) { 864 | throw std::runtime_error("Failed to allocate command buffers"); 865 | } 866 | 867 | VkCommandBufferBeginInfo beginInfo = {}; 868 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 869 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; 870 | beginInfo.pInheritanceInfo = nullptr; 871 | 872 | // ~ Start recording ~ 873 | if (vkBeginCommandBuffer(computeCommandBuffer, &beginInfo) != VK_SUCCESS) { 874 | throw std::runtime_error("Failed to begin recording compute command buffer"); 875 | } 876 | 877 | // Bind to the compute pipeline 878 | vkCmdBindPipeline(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); 879 | 880 | // Bind camera descriptor set 881 | vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &cameraDescriptorSet, 0, nullptr); 882 | 883 | // Bind descriptor set for time uniforms 884 | vkCmdBindDescriptorSets(computeCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 1, 1, &timeDescriptorSet, 0, nullptr); 885 | 886 | // TODO: For each group of blades bind its descriptor set and dispatch 887 | 888 | // ~ End recording ~ 889 | if (vkEndCommandBuffer(computeCommandBuffer) != VK_SUCCESS) { 890 | throw std::runtime_error("Failed to record compute command buffer"); 891 | } 892 | } 893 | 894 | void Renderer::RecordCommandBuffers() { 895 | commandBuffers.resize(swapChain->GetCount()); 896 | 897 | // Specify the command pool and number of buffers to allocate 898 | VkCommandBufferAllocateInfo allocInfo = {}; 899 | allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 900 | allocInfo.commandPool = graphicsCommandPool; 901 | allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 902 | allocInfo.commandBufferCount = static_cast(commandBuffers.size()); 903 | 904 | if (vkAllocateCommandBuffers(logicalDevice, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { 905 | throw std::runtime_error("Failed to allocate command buffers"); 906 | } 907 | 908 | // Start command buffer recording 909 | for (size_t i = 0; i < commandBuffers.size(); i++) { 910 | VkCommandBufferBeginInfo beginInfo = {}; 911 | beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 912 | beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; 913 | beginInfo.pInheritanceInfo = nullptr; 914 | 915 | // ~ Start recording ~ 916 | if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { 917 | throw std::runtime_error("Failed to begin recording command buffer"); 918 | } 919 | 920 | // Begin the render pass 921 | VkRenderPassBeginInfo renderPassInfo = {}; 922 | renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 923 | renderPassInfo.renderPass = renderPass; 924 | renderPassInfo.framebuffer = framebuffers[i]; 925 | renderPassInfo.renderArea.offset = { 0, 0 }; 926 | renderPassInfo.renderArea.extent = swapChain->GetVkExtent(); 927 | 928 | std::array clearValues = {}; 929 | clearValues[0].color = { 0.0f, 0.0f, 0.0f, 1.0f }; 930 | clearValues[1].depthStencil = { 1.0f, 0 }; 931 | renderPassInfo.clearValueCount = static_cast(clearValues.size()); 932 | renderPassInfo.pClearValues = clearValues.data(); 933 | 934 | std::vector barriers(scene->GetBlades().size()); 935 | for (uint32_t j = 0; j < barriers.size(); ++j) { 936 | barriers[j].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; 937 | barriers[j].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; 938 | barriers[j].dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT; 939 | barriers[j].srcQueueFamilyIndex = device->GetQueueIndex(QueueFlags::Compute); 940 | barriers[j].dstQueueFamilyIndex = device->GetQueueIndex(QueueFlags::Graphics); 941 | barriers[j].buffer = scene->GetBlades()[j]->GetNumBladesBuffer(); 942 | barriers[j].offset = 0; 943 | barriers[j].size = sizeof(BladeDrawIndirect); 944 | } 945 | 946 | vkCmdPipelineBarrier(commandBuffers[i], VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, 0, nullptr, barriers.size(), barriers.data(), 0, nullptr); 947 | 948 | // Bind the camera descriptor set. This is set 0 in all pipelines so it will be inherited 949 | vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 0, 1, &cameraDescriptorSet, 0, nullptr); 950 | 951 | vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); 952 | 953 | // Bind the graphics pipeline 954 | vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); 955 | 956 | for (uint32_t j = 0; j < scene->GetModels().size(); ++j) { 957 | // Bind the vertex and index buffers 958 | VkBuffer vertexBuffers[] = { scene->GetModels()[j]->getVertexBuffer() }; 959 | VkDeviceSize offsets[] = { 0 }; 960 | vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); 961 | 962 | vkCmdBindIndexBuffer(commandBuffers[i], scene->GetModels()[j]->getIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); 963 | 964 | // Bind the descriptor set for each model 965 | vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipelineLayout, 1, 1, &modelDescriptorSets[j], 0, nullptr); 966 | 967 | // Draw 968 | std::vector indices = scene->GetModels()[j]->getIndices(); 969 | vkCmdDrawIndexed(commandBuffers[i], static_cast(indices.size()), 1, 0, 0, 0); 970 | } 971 | 972 | // Bind the grass pipeline 973 | vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, grassPipeline); 974 | 975 | for (uint32_t j = 0; j < scene->GetBlades().size(); ++j) { 976 | VkBuffer vertexBuffers[] = { scene->GetBlades()[j]->GetCulledBladesBuffer() }; 977 | VkDeviceSize offsets[] = { 0 }; 978 | // TODO: Uncomment this when the buffers are populated 979 | // vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); 980 | 981 | // TODO: Bind the descriptor set for each grass blades model 982 | 983 | // Draw 984 | // TODO: Uncomment this when the buffers are populated 985 | // vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect)); 986 | } 987 | 988 | // End render pass 989 | vkCmdEndRenderPass(commandBuffers[i]); 990 | 991 | // ~ End recording ~ 992 | if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { 993 | throw std::runtime_error("Failed to record command buffer"); 994 | } 995 | } 996 | } 997 | 998 | void Renderer::Frame() { 999 | 1000 | VkSubmitInfo computeSubmitInfo = {}; 1001 | computeSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 1002 | 1003 | computeSubmitInfo.commandBufferCount = 1; 1004 | computeSubmitInfo.pCommandBuffers = &computeCommandBuffer; 1005 | 1006 | if (vkQueueSubmit(device->GetQueue(QueueFlags::Compute), 1, &computeSubmitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { 1007 | throw std::runtime_error("Failed to submit draw command buffer"); 1008 | } 1009 | 1010 | if (!swapChain->Acquire()) { 1011 | RecreateFrameResources(); 1012 | return; 1013 | } 1014 | 1015 | // Submit the command buffer 1016 | VkSubmitInfo submitInfo = {}; 1017 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 1018 | 1019 | VkSemaphore waitSemaphores[] = { swapChain->GetImageAvailableVkSemaphore() }; 1020 | VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; 1021 | submitInfo.waitSemaphoreCount = 1; 1022 | submitInfo.pWaitSemaphores = waitSemaphores; 1023 | submitInfo.pWaitDstStageMask = waitStages; 1024 | 1025 | submitInfo.commandBufferCount = 1; 1026 | submitInfo.pCommandBuffers = &commandBuffers[swapChain->GetIndex()]; 1027 | 1028 | VkSemaphore signalSemaphores[] = { swapChain->GetRenderFinishedVkSemaphore() }; 1029 | submitInfo.signalSemaphoreCount = 1; 1030 | submitInfo.pSignalSemaphores = signalSemaphores; 1031 | 1032 | if (vkQueueSubmit(device->GetQueue(QueueFlags::Graphics), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { 1033 | throw std::runtime_error("Failed to submit draw command buffer"); 1034 | } 1035 | 1036 | if (!swapChain->Present()) { 1037 | RecreateFrameResources(); 1038 | } 1039 | } 1040 | 1041 | Renderer::~Renderer() { 1042 | vkDeviceWaitIdle(logicalDevice); 1043 | 1044 | // TODO: destroy any resources you created 1045 | 1046 | vkFreeCommandBuffers(logicalDevice, graphicsCommandPool, static_cast(commandBuffers.size()), commandBuffers.data()); 1047 | vkFreeCommandBuffers(logicalDevice, computeCommandPool, 1, &computeCommandBuffer); 1048 | 1049 | vkDestroyPipeline(logicalDevice, graphicsPipeline, nullptr); 1050 | vkDestroyPipeline(logicalDevice, grassPipeline, nullptr); 1051 | vkDestroyPipeline(logicalDevice, computePipeline, nullptr); 1052 | 1053 | vkDestroyPipelineLayout(logicalDevice, graphicsPipelineLayout, nullptr); 1054 | vkDestroyPipelineLayout(logicalDevice, grassPipelineLayout, nullptr); 1055 | vkDestroyPipelineLayout(logicalDevice, computePipelineLayout, nullptr); 1056 | 1057 | vkDestroyDescriptorSetLayout(logicalDevice, cameraDescriptorSetLayout, nullptr); 1058 | vkDestroyDescriptorSetLayout(logicalDevice, modelDescriptorSetLayout, nullptr); 1059 | vkDestroyDescriptorSetLayout(logicalDevice, timeDescriptorSetLayout, nullptr); 1060 | 1061 | vkDestroyDescriptorPool(logicalDevice, descriptorPool, nullptr); 1062 | 1063 | vkDestroyRenderPass(logicalDevice, renderPass, nullptr); 1064 | DestroyFrameResources(); 1065 | vkDestroyCommandPool(logicalDevice, computeCommandPool, nullptr); 1066 | vkDestroyCommandPool(logicalDevice, graphicsCommandPool, nullptr); 1067 | } 1068 | -------------------------------------------------------------------------------- /src/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Device.h" 4 | #include "SwapChain.h" 5 | #include "Scene.h" 6 | #include "Camera.h" 7 | 8 | class Renderer { 9 | public: 10 | Renderer() = delete; 11 | Renderer(Device* device, SwapChain* swapChain, Scene* scene, Camera* camera); 12 | ~Renderer(); 13 | 14 | void CreateCommandPools(); 15 | 16 | void CreateRenderPass(); 17 | 18 | void CreateCameraDescriptorSetLayout(); 19 | void CreateModelDescriptorSetLayout(); 20 | void CreateTimeDescriptorSetLayout(); 21 | void CreateComputeDescriptorSetLayout(); 22 | 23 | void CreateDescriptorPool(); 24 | 25 | void CreateCameraDescriptorSet(); 26 | void CreateModelDescriptorSets(); 27 | void CreateGrassDescriptorSets(); 28 | void CreateTimeDescriptorSet(); 29 | void CreateComputeDescriptorSets(); 30 | 31 | void CreateGraphicsPipeline(); 32 | void CreateGrassPipeline(); 33 | void CreateComputePipeline(); 34 | 35 | void CreateFrameResources(); 36 | void DestroyFrameResources(); 37 | void RecreateFrameResources(); 38 | 39 | void RecordCommandBuffers(); 40 | void RecordComputeCommandBuffer(); 41 | 42 | void Frame(); 43 | 44 | private: 45 | Device* device; 46 | VkDevice logicalDevice; 47 | SwapChain* swapChain; 48 | Scene* scene; 49 | Camera* camera; 50 | 51 | VkCommandPool graphicsCommandPool; 52 | VkCommandPool computeCommandPool; 53 | 54 | VkRenderPass renderPass; 55 | 56 | VkDescriptorSetLayout cameraDescriptorSetLayout; 57 | VkDescriptorSetLayout modelDescriptorSetLayout; 58 | VkDescriptorSetLayout timeDescriptorSetLayout; 59 | 60 | VkDescriptorPool descriptorPool; 61 | 62 | VkDescriptorSet cameraDescriptorSet; 63 | std::vector modelDescriptorSets; 64 | VkDescriptorSet timeDescriptorSet; 65 | 66 | VkPipelineLayout graphicsPipelineLayout; 67 | VkPipelineLayout grassPipelineLayout; 68 | VkPipelineLayout computePipelineLayout; 69 | 70 | VkPipeline graphicsPipeline; 71 | VkPipeline grassPipeline; 72 | VkPipeline computePipeline; 73 | 74 | std::vector imageViews; 75 | VkImage depthImage; 76 | VkDeviceMemory depthImageMemory; 77 | VkImageView depthImageView; 78 | std::vector framebuffers; 79 | 80 | std::vector commandBuffers; 81 | VkCommandBuffer computeCommandBuffer; 82 | }; 83 | -------------------------------------------------------------------------------- /src/Scene.cpp: -------------------------------------------------------------------------------- 1 | #include "Scene.h" 2 | #include "BufferUtils.h" 3 | 4 | Scene::Scene(Device* device) : device(device) { 5 | BufferUtils::CreateBuffer(device, sizeof(Time), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, timeBuffer, timeBufferMemory); 6 | vkMapMemory(device->GetVkDevice(), timeBufferMemory, 0, sizeof(Time), 0, &mappedData); 7 | memcpy(mappedData, &time, sizeof(Time)); 8 | } 9 | 10 | const std::vector& Scene::GetModels() const { 11 | return models; 12 | } 13 | 14 | const std::vector& Scene::GetBlades() const { 15 | return blades; 16 | } 17 | 18 | void Scene::AddModel(Model* model) { 19 | models.push_back(model); 20 | } 21 | 22 | void Scene::AddBlades(Blades* blades) { 23 | this->blades.push_back(blades); 24 | } 25 | 26 | void Scene::UpdateTime() { 27 | high_resolution_clock::time_point currentTime = high_resolution_clock::now(); 28 | duration nextDeltaTime = duration_cast>(currentTime - startTime); 29 | startTime = currentTime; 30 | 31 | time.deltaTime = nextDeltaTime.count(); 32 | time.totalTime += time.deltaTime; 33 | 34 | memcpy(mappedData, &time, sizeof(Time)); 35 | } 36 | 37 | VkBuffer Scene::GetTimeBuffer() const { 38 | return timeBuffer; 39 | } 40 | 41 | Scene::~Scene() { 42 | vkUnmapMemory(device->GetVkDevice(), timeBufferMemory); 43 | vkDestroyBuffer(device->GetVkDevice(), timeBuffer, nullptr); 44 | vkFreeMemory(device->GetVkDevice(), timeBufferMemory, nullptr); 45 | } 46 | -------------------------------------------------------------------------------- /src/Scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Model.h" 7 | #include "Blades.h" 8 | 9 | using namespace std::chrono; 10 | 11 | struct Time { 12 | float deltaTime = 0.0f; 13 | float totalTime = 0.0f; 14 | }; 15 | 16 | class Scene { 17 | private: 18 | Device* device; 19 | 20 | VkBuffer timeBuffer; 21 | VkDeviceMemory timeBufferMemory; 22 | Time time; 23 | 24 | void* mappedData; 25 | 26 | std::vector models; 27 | std::vector blades; 28 | 29 | high_resolution_clock::time_point startTime = high_resolution_clock::now(); 30 | 31 | public: 32 | Scene() = delete; 33 | Scene(Device* device); 34 | ~Scene(); 35 | 36 | const std::vector& GetModels() const; 37 | const std::vector& GetBlades() const; 38 | 39 | void AddModel(Model* model); 40 | void AddBlades(Blades* blades); 41 | 42 | VkBuffer GetTimeBuffer() const; 43 | 44 | void UpdateTime(); 45 | }; 46 | -------------------------------------------------------------------------------- /src/ShaderModule.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ShaderModule.h" 3 | 4 | namespace { 5 | std::vector readFile(const std::string& filename) { 6 | std::ifstream file(filename, std::ios::ate | std::ios::binary); 7 | 8 | if (!file.is_open()) { 9 | throw std::runtime_error("Failed to open file"); 10 | } 11 | 12 | size_t fileSize = (size_t)file.tellg(); 13 | std::vector buffer(fileSize); 14 | 15 | file.seekg(0); 16 | file.read(buffer.data(), fileSize); 17 | 18 | file.close(); 19 | return buffer; 20 | } 21 | } 22 | 23 | // Wrap the shaders in shader modules 24 | VkShaderModule ShaderModule::Create(const std::vector& code, VkDevice logicalDevice) { 25 | VkShaderModuleCreateInfo createInfo = {}; 26 | createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 27 | createInfo.codeSize = code.size(); 28 | createInfo.pCode = reinterpret_cast(code.data()); 29 | 30 | VkShaderModule shaderModule; 31 | if (vkCreateShaderModule(logicalDevice, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { 32 | throw std::runtime_error("Failed to create shader module"); 33 | } 34 | 35 | return shaderModule; 36 | } 37 | 38 | VkShaderModule ShaderModule::Create(const std::string& filename, VkDevice logicalDevice) { 39 | return ShaderModule::Create(readFile(filename), logicalDevice); 40 | } 41 | -------------------------------------------------------------------------------- /src/ShaderModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ShaderModule { 8 | VkShaderModule Create(const std::vector& code, VkDevice logicalDevice); 9 | VkShaderModule Create(const std::string& filename, VkDevice logicalDevice); 10 | } 11 | -------------------------------------------------------------------------------- /src/SwapChain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SwapChain.h" 3 | #include "Instance.h" 4 | #include "Device.h" 5 | #include "Window.h" 6 | 7 | namespace { 8 | // Specify the color channel format and color space type 9 | VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { 10 | // VK_FORMAT_UNDEFINED indicates that the surface has no preferred format, so we can choose any 11 | if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { 12 | return{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; 13 | } 14 | 15 | // Otherwise, choose a preferred combination 16 | for (const auto& availableFormat : availableFormats) { 17 | // Ideal format and color space 18 | if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { 19 | return availableFormat; 20 | } 21 | } 22 | 23 | // Otherwise, return any format 24 | return availableFormats[0]; 25 | } 26 | 27 | // Specify the presentation mode of the swap chain 28 | VkPresentModeKHR chooseSwapPresentMode(const std::vector availablePresentModes) { 29 | // Second choice 30 | VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; 31 | 32 | for (const auto& availablePresentMode : availablePresentModes) { 33 | if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { 34 | // First choice 35 | return availablePresentMode; 36 | } 37 | else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { 38 | // Third choice 39 | bestMode = availablePresentMode; 40 | } 41 | } 42 | 43 | return bestMode; 44 | } 45 | 46 | // Specify the swap extent (resolution) of the swap chain 47 | VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) { 48 | if (capabilities.currentExtent.width != std::numeric_limits::max()) { 49 | return capabilities.currentExtent; 50 | } else { 51 | int width, height; 52 | glfwGetWindowSize(window, &width, &height); 53 | VkExtent2D actualExtent = { static_cast(width), static_cast(height) }; 54 | 55 | actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); 56 | actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); 57 | 58 | return actualExtent; 59 | } 60 | } 61 | } 62 | 63 | SwapChain::SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers) 64 | : device(device), vkSurface(vkSurface), numBuffers(numBuffers) { 65 | 66 | Create(); 67 | 68 | VkSemaphoreCreateInfo semaphoreInfo = {}; 69 | semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; 70 | 71 | if (vkCreateSemaphore(device->GetVkDevice(), &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || 72 | vkCreateSemaphore(device->GetVkDevice(), &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) { 73 | throw std::runtime_error("Failed to create semaphores"); 74 | } 75 | } 76 | 77 | void SwapChain::Create() { 78 | auto* instance = device->GetInstance(); 79 | 80 | const auto& surfaceCapabilities = instance->GetSurfaceCapabilities(); 81 | 82 | VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(instance->GetSurfaceFormats()); 83 | VkPresentModeKHR presentMode = chooseSwapPresentMode(instance->GetPresentModes()); 84 | VkExtent2D extent = chooseSwapExtent(surfaceCapabilities, GetGLFWWindow()); 85 | 86 | uint32_t imageCount = surfaceCapabilities.minImageCount + 1; 87 | imageCount = numBuffers > imageCount ? numBuffers : imageCount; 88 | if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) { 89 | imageCount = surfaceCapabilities.maxImageCount; 90 | } 91 | 92 | // --- Create swap chain --- 93 | VkSwapchainCreateInfoKHR createInfo = {}; 94 | createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; 95 | 96 | // Specify surface to be tied to 97 | createInfo.surface = vkSurface; 98 | 99 | // Add details of the swap chain 100 | createInfo.minImageCount = imageCount; 101 | createInfo.imageFormat = surfaceFormat.format; 102 | createInfo.imageColorSpace = surfaceFormat.colorSpace; 103 | createInfo.imageExtent = extent; 104 | createInfo.imageArrayLayers = 1; 105 | createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; 106 | 107 | const auto& queueFamilyIndices = instance->GetQueueFamilyIndices(); 108 | if (queueFamilyIndices[QueueFlags::Graphics] != queueFamilyIndices[QueueFlags::Present]) { 109 | // Images can be used across multiple queue families without explicit ownership transfers 110 | createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; 111 | createInfo.queueFamilyIndexCount = 2; 112 | unsigned int indices[] = { 113 | static_cast(queueFamilyIndices[QueueFlags::Graphics]), 114 | static_cast(queueFamilyIndices[QueueFlags::Present]) 115 | }; 116 | createInfo.pQueueFamilyIndices = indices; 117 | } 118 | else { 119 | // An image is owned by one queue family at a time and ownership must be explicitly transfered between uses 120 | createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; 121 | createInfo.queueFamilyIndexCount = 0; 122 | createInfo.pQueueFamilyIndices = nullptr; 123 | } 124 | 125 | // Specify transform on images in the swap chain (no transformation done here) 126 | createInfo.preTransform = surfaceCapabilities.currentTransform; 127 | 128 | // Specify alpha channel usage (set to be ignored here) 129 | createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; 130 | 131 | // Specify presentation mode 132 | createInfo.presentMode = presentMode; 133 | 134 | // Specify whether we can clip pixels that are obscured by other windows 135 | createInfo.clipped = VK_TRUE; 136 | 137 | // Reference to old swap chain in case current one becomes invalid 138 | createInfo.oldSwapchain = VK_NULL_HANDLE; 139 | 140 | // Create swap chain 141 | if (vkCreateSwapchainKHR(device->GetVkDevice(), &createInfo, nullptr, &vkSwapChain) != VK_SUCCESS) { 142 | throw std::runtime_error("Failed to create swap chain"); 143 | } 144 | 145 | // --- Retrieve swap chain images --- 146 | vkGetSwapchainImagesKHR(device->GetVkDevice(), vkSwapChain, &imageCount, nullptr); 147 | vkSwapChainImages.resize(imageCount); 148 | vkGetSwapchainImagesKHR(device->GetVkDevice(), vkSwapChain, &imageCount, vkSwapChainImages.data()); 149 | 150 | vkSwapChainImageFormat = surfaceFormat.format; 151 | vkSwapChainExtent = extent; 152 | } 153 | 154 | void SwapChain::Destroy() { 155 | vkDestroySwapchainKHR(device->GetVkDevice(), vkSwapChain, nullptr); 156 | } 157 | 158 | VkSwapchainKHR SwapChain::GetVkSwapChain() const { 159 | return vkSwapChain; 160 | } 161 | 162 | VkFormat SwapChain::GetVkImageFormat() const { 163 | return vkSwapChainImageFormat; 164 | } 165 | 166 | VkExtent2D SwapChain::GetVkExtent() const { 167 | return vkSwapChainExtent; 168 | } 169 | 170 | uint32_t SwapChain::GetIndex() const { 171 | return imageIndex; 172 | } 173 | 174 | uint32_t SwapChain::GetCount() const { 175 | return static_cast(vkSwapChainImages.size()); 176 | } 177 | 178 | VkImage SwapChain::GetVkImage(uint32_t index) const { 179 | return vkSwapChainImages[index]; 180 | } 181 | 182 | VkSemaphore SwapChain::GetImageAvailableVkSemaphore() const { 183 | return imageAvailableSemaphore; 184 | 185 | } 186 | 187 | VkSemaphore SwapChain::GetRenderFinishedVkSemaphore() const { 188 | return renderFinishedSemaphore; 189 | } 190 | 191 | void SwapChain::Recreate() { 192 | Destroy(); 193 | Create(); 194 | } 195 | 196 | bool SwapChain::Acquire() { 197 | if (ENABLE_VALIDATION) { 198 | // the validation layer implementation expects the application to explicitly synchronize with the GPU 199 | vkQueueWaitIdle(device->GetQueue(QueueFlags::Present)); 200 | } 201 | VkResult result = vkAcquireNextImageKHR(device->GetVkDevice(), vkSwapChain, std::numeric_limits::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); 202 | if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { 203 | throw std::runtime_error("Failed to acquire swap chain image"); 204 | } 205 | 206 | if (result == VK_ERROR_OUT_OF_DATE_KHR) { 207 | Recreate(); 208 | return false; 209 | } 210 | 211 | return true; 212 | } 213 | 214 | bool SwapChain::Present() { 215 | VkSemaphore signalSemaphores[] = { renderFinishedSemaphore }; 216 | 217 | // Submit result back to swap chain for presentation 218 | VkPresentInfoKHR presentInfo = {}; 219 | presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; 220 | presentInfo.waitSemaphoreCount = 1; 221 | presentInfo.pWaitSemaphores = signalSemaphores; 222 | 223 | VkSwapchainKHR swapChains[] = { vkSwapChain }; 224 | presentInfo.swapchainCount = 1; 225 | presentInfo.pSwapchains = swapChains; 226 | presentInfo.pImageIndices = &imageIndex; 227 | presentInfo.pResults = nullptr; 228 | 229 | VkResult result = vkQueuePresentKHR(device->GetQueue(QueueFlags::Present), &presentInfo); 230 | 231 | if (result != VK_SUCCESS) { 232 | throw std::runtime_error("Failed to present swap chain image"); 233 | } 234 | 235 | if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { 236 | Recreate(); 237 | return false; 238 | } 239 | 240 | return true; 241 | } 242 | 243 | SwapChain::~SwapChain() { 244 | vkDestroySemaphore(device->GetVkDevice(), imageAvailableSemaphore, nullptr); 245 | vkDestroySemaphore(device->GetVkDevice(), renderFinishedSemaphore, nullptr); 246 | Destroy(); 247 | } 248 | -------------------------------------------------------------------------------- /src/SwapChain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Device.h" 5 | 6 | class Device; 7 | class SwapChain { 8 | friend class Device; 9 | 10 | public: 11 | VkSwapchainKHR GetVkSwapChain() const; 12 | VkFormat GetVkImageFormat() const; 13 | VkExtent2D GetVkExtent() const; 14 | uint32_t GetIndex() const; 15 | uint32_t GetCount() const; 16 | VkImage GetVkImage(uint32_t index) const; 17 | VkSemaphore GetImageAvailableVkSemaphore() const; 18 | VkSemaphore GetRenderFinishedVkSemaphore() const; 19 | 20 | void Recreate(); 21 | bool Acquire(); 22 | bool Present(); 23 | ~SwapChain(); 24 | 25 | private: 26 | SwapChain(Device* device, VkSurfaceKHR vkSurface, unsigned int numBuffers); 27 | void Create(); 28 | void Destroy(); 29 | 30 | Device* device; 31 | VkSurfaceKHR vkSurface; 32 | unsigned int numBuffers; 33 | VkSwapchainKHR vkSwapChain; 34 | std::vector vkSwapChainImages; 35 | VkFormat vkSwapChainImageFormat; 36 | VkExtent2D vkSwapChainExtent; 37 | uint32_t imageIndex = 0; 38 | 39 | VkSemaphore imageAvailableSemaphore; 40 | VkSemaphore renderFinishedSemaphore; 41 | }; 42 | -------------------------------------------------------------------------------- /src/Vertex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | struct Vertex { 9 | glm::vec3 pos; 10 | glm::vec3 color; 11 | glm::vec2 texCoord; 12 | 13 | // Get the binding description, which describes the rate to load data from memory 14 | static VkVertexInputBindingDescription getBindingDescription() { 15 | VkVertexInputBindingDescription bindingDescription = {}; 16 | bindingDescription.binding = 0; 17 | bindingDescription.stride = sizeof(Vertex); 18 | bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 19 | 20 | return bindingDescription; 21 | } 22 | 23 | // Get the attribute descriptions, which describe how to handle vertex input 24 | static std::array getAttributeDescriptions() { 25 | std::array attributeDescriptions = {}; 26 | 27 | // Position 28 | attributeDescriptions[0].binding = 0; 29 | attributeDescriptions[0].location = 0; 30 | attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; 31 | attributeDescriptions[0].offset = offsetof(Vertex, pos); 32 | 33 | // Color 34 | attributeDescriptions[1].binding = 0; 35 | attributeDescriptions[1].location = 1; 36 | attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; 37 | attributeDescriptions[1].offset = offsetof(Vertex, color); 38 | 39 | // Texture coordinate 40 | attributeDescriptions[2].binding = 0; 41 | attributeDescriptions[2].location = 2; 42 | attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; 43 | attributeDescriptions[2].offset = offsetof(Vertex, texCoord); 44 | 45 | return attributeDescriptions; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/Window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Window.h" 3 | 4 | namespace { 5 | GLFWwindow* window = nullptr; 6 | } 7 | 8 | GLFWwindow* GetGLFWWindow() { 9 | return window; 10 | } 11 | 12 | void InitializeWindow(int width, int height, const char* name) { 13 | if (!glfwInit()) { 14 | fprintf(stderr, "Failed to initialize GLFW\n"); 15 | exit(EXIT_FAILURE); 16 | } 17 | 18 | if (!glfwVulkanSupported()){ 19 | fprintf(stderr, "Vulkan not supported\n"); 20 | exit(EXIT_FAILURE); 21 | } 22 | 23 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 24 | window = glfwCreateWindow(width, height, name, nullptr, nullptr); 25 | 26 | if (!window) { 27 | fprintf(stderr, "Failed to initialize GLFW window\n"); 28 | glfwTerminate(); 29 | exit(EXIT_FAILURE); 30 | } 31 | } 32 | 33 | bool ShouldQuit() { 34 | return !!glfwWindowShouldClose(window); 35 | } 36 | 37 | void DestroyWindow() { 38 | glfwDestroyWindow(window); 39 | glfwTerminate(); 40 | } 41 | -------------------------------------------------------------------------------- /src/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #pragma comment(linker, "/subsystem:console") 5 | #include 6 | #elif defined(__linux__) 7 | #include 8 | #endif 9 | 10 | #define GLFW_INCLUDE_VULKAN 11 | #include 12 | 13 | struct GLFWwindow; 14 | struct GLFWwindow* GetGLFWWindow(); 15 | 16 | void InitializeWindow(int width, int height, const char* name); 17 | bool ShouldQuit(); 18 | void DestroyWindow(); 19 | -------------------------------------------------------------------------------- /src/images/grass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CIS565-Fall-2017/Project6-Vulkan-Grass-Rendering/3fdcbb5ab011518591c922ea5bafc9a149cb0075/src/images/grass.jpg -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Instance.h" 3 | #include "Window.h" 4 | #include "Renderer.h" 5 | #include "Camera.h" 6 | #include "Scene.h" 7 | #include "Image.h" 8 | 9 | Device* device; 10 | SwapChain* swapChain; 11 | Renderer* renderer; 12 | Camera* camera; 13 | 14 | namespace { 15 | void resizeCallback(GLFWwindow* window, int width, int height) { 16 | if (width == 0 || height == 0) return; 17 | 18 | vkDeviceWaitIdle(device->GetVkDevice()); 19 | swapChain->Recreate(); 20 | renderer->RecreateFrameResources(); 21 | } 22 | 23 | bool leftMouseDown = false; 24 | bool rightMouseDown = false; 25 | double previousX = 0.0; 26 | double previousY = 0.0; 27 | 28 | void mouseDownCallback(GLFWwindow* window, int button, int action, int mods) { 29 | if (button == GLFW_MOUSE_BUTTON_LEFT) { 30 | if (action == GLFW_PRESS) { 31 | leftMouseDown = true; 32 | glfwGetCursorPos(window, &previousX, &previousY); 33 | } 34 | else if (action == GLFW_RELEASE) { 35 | leftMouseDown = false; 36 | } 37 | } else if (button == GLFW_MOUSE_BUTTON_RIGHT) { 38 | if (action == GLFW_PRESS) { 39 | rightMouseDown = true; 40 | glfwGetCursorPos(window, &previousX, &previousY); 41 | } 42 | else if (action == GLFW_RELEASE) { 43 | rightMouseDown = false; 44 | } 45 | } 46 | } 47 | 48 | void mouseMoveCallback(GLFWwindow* window, double xPosition, double yPosition) { 49 | if (leftMouseDown) { 50 | double sensitivity = 0.5; 51 | float deltaX = static_cast((previousX - xPosition) * sensitivity); 52 | float deltaY = static_cast((previousY - yPosition) * sensitivity); 53 | 54 | camera->UpdateOrbit(deltaX, deltaY, 0.0f); 55 | 56 | previousX = xPosition; 57 | previousY = yPosition; 58 | } else if (rightMouseDown) { 59 | double deltaZ = static_cast((previousY - yPosition) * 0.05); 60 | 61 | camera->UpdateOrbit(0.0f, 0.0f, deltaZ); 62 | 63 | previousY = yPosition; 64 | } 65 | } 66 | } 67 | 68 | int main() { 69 | static constexpr char* applicationName = "Vulkan Grass Rendering"; 70 | InitializeWindow(640, 480, applicationName); 71 | 72 | unsigned int glfwExtensionCount = 0; 73 | const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 74 | 75 | Instance* instance = new Instance(applicationName, glfwExtensionCount, glfwExtensions); 76 | 77 | VkSurfaceKHR surface; 78 | if (glfwCreateWindowSurface(instance->GetVkInstance(), GetGLFWWindow(), nullptr, &surface) != VK_SUCCESS) { 79 | throw std::runtime_error("Failed to create window surface"); 80 | } 81 | 82 | instance->PickPhysicalDevice({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }, QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, surface); 83 | 84 | VkPhysicalDeviceFeatures deviceFeatures = {}; 85 | deviceFeatures.tessellationShader = VK_TRUE; 86 | deviceFeatures.fillModeNonSolid = VK_TRUE; 87 | deviceFeatures.samplerAnisotropy = VK_TRUE; 88 | 89 | device = instance->CreateDevice(QueueFlagBit::GraphicsBit | QueueFlagBit::TransferBit | QueueFlagBit::ComputeBit | QueueFlagBit::PresentBit, deviceFeatures); 90 | 91 | swapChain = device->CreateSwapChain(surface, 5); 92 | 93 | camera = new Camera(device, 640.f / 480.f); 94 | 95 | VkCommandPoolCreateInfo transferPoolInfo = {}; 96 | transferPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 97 | transferPoolInfo.queueFamilyIndex = device->GetInstance()->GetQueueFamilyIndices()[QueueFlags::Transfer]; 98 | transferPoolInfo.flags = 0; 99 | 100 | VkCommandPool transferCommandPool; 101 | if (vkCreateCommandPool(device->GetVkDevice(), &transferPoolInfo, nullptr, &transferCommandPool) != VK_SUCCESS) { 102 | throw std::runtime_error("Failed to create command pool"); 103 | } 104 | 105 | VkImage grassImage; 106 | VkDeviceMemory grassImageMemory; 107 | Image::FromFile(device, 108 | transferCommandPool, 109 | "images/grass.jpg", 110 | VK_FORMAT_R8G8B8A8_UNORM, 111 | VK_IMAGE_TILING_OPTIMAL, 112 | VK_IMAGE_USAGE_SAMPLED_BIT, 113 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 114 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 115 | grassImage, 116 | grassImageMemory 117 | ); 118 | 119 | float planeDim = 15.f; 120 | float halfWidth = planeDim * 0.5f; 121 | Model* plane = new Model(device, transferCommandPool, 122 | { 123 | { { -halfWidth, 0.0f, halfWidth }, { 1.0f, 0.0f, 0.0f },{ 1.0f, 0.0f } }, 124 | { { halfWidth, 0.0f, halfWidth }, { 0.0f, 1.0f, 0.0f },{ 0.0f, 0.0f } }, 125 | { { halfWidth, 0.0f, -halfWidth }, { 0.0f, 0.0f, 1.0f },{ 0.0f, 1.0f } }, 126 | { { -halfWidth, 0.0f, -halfWidth }, { 1.0f, 1.0f, 1.0f },{ 1.0f, 1.0f } } 127 | }, 128 | { 0, 1, 2, 2, 3, 0 } 129 | ); 130 | plane->SetTexture(grassImage); 131 | 132 | Blades* blades = new Blades(device, transferCommandPool, planeDim); 133 | 134 | vkDestroyCommandPool(device->GetVkDevice(), transferCommandPool, nullptr); 135 | 136 | Scene* scene = new Scene(device); 137 | scene->AddModel(plane); 138 | scene->AddBlades(blades); 139 | 140 | renderer = new Renderer(device, swapChain, scene, camera); 141 | 142 | glfwSetWindowSizeCallback(GetGLFWWindow(), resizeCallback); 143 | glfwSetMouseButtonCallback(GetGLFWWindow(), mouseDownCallback); 144 | glfwSetCursorPosCallback(GetGLFWWindow(), mouseMoveCallback); 145 | 146 | while (!ShouldQuit()) { 147 | glfwPollEvents(); 148 | scene->UpdateTime(); 149 | renderer->Frame(); 150 | } 151 | 152 | vkDeviceWaitIdle(device->GetVkDevice()); 153 | 154 | vkDestroyImage(device->GetVkDevice(), grassImage, nullptr); 155 | vkFreeMemory(device->GetVkDevice(), grassImageMemory, nullptr); 156 | 157 | delete scene; 158 | delete plane; 159 | delete blades; 160 | delete camera; 161 | delete renderer; 162 | delete swapChain; 163 | delete device; 164 | delete instance; 165 | DestroyWindow(); 166 | return 0; 167 | } 168 | -------------------------------------------------------------------------------- /src/shaders/compute.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | #define WORKGROUP_SIZE 32 5 | layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in; 6 | 7 | layout(set = 0, binding = 0) uniform CameraBufferObject { 8 | mat4 view; 9 | mat4 proj; 10 | } camera; 11 | 12 | layout(set = 1, binding = 0) uniform Time { 13 | float deltaTime; 14 | float totalTime; 15 | }; 16 | 17 | struct Blade { 18 | vec4 v0; 19 | vec4 v1; 20 | vec4 v2; 21 | vec4 up; 22 | }; 23 | 24 | // TODO: Add bindings to: 25 | // 1. Store the input blades 26 | // 2. Write out the culled blades 27 | // 3. Write the total number of blades remaining 28 | 29 | // The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call 30 | // This is sort of an advanced feature so we've showed you what this buffer should look like 31 | // 32 | // layout(set = ???, binding = ???) buffer NumBlades { 33 | // uint vertexCount; // Write the number of blades remaining here 34 | // uint instanceCount; // = 1 35 | // uint firstVertex; // = 0 36 | // uint firstInstance; // = 0 37 | // } numBlades; 38 | 39 | bool inBounds(float value, float bounds) { 40 | return (value >= -bounds) && (value <= bounds); 41 | } 42 | 43 | void main() { 44 | // Reset the number of blades to 0 45 | if (gl_GlobalInvocationID.x == 0) { 46 | // numBlades.vertexCount = 0; 47 | } 48 | barrier(); // Wait till all threads reach this point 49 | 50 | // TODO: Apply forces on every blade and update the vertices in the buffer 51 | 52 | // TODO: Cull blades that are too far away or not in the camera frustum and write them 53 | // to the culled blades buffer 54 | // Note: to do this, you will need to use an atomic operation to read and update numBlades.vertexCount 55 | // You want to write the visible blades to the buffer without write conflicts between threads 56 | } 57 | -------------------------------------------------------------------------------- /src/shaders/graphics.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 1, binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | outColor = texture(texSampler, fragTexCoord); 13 | } 14 | -------------------------------------------------------------------------------- /src/shaders/graphics.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform CameraBufferObject { 5 | mat4 view; 6 | mat4 proj; 7 | } camera; 8 | 9 | layout(set = 1, binding = 0) uniform ModelBufferObject { 10 | mat4 model; 11 | }; 12 | 13 | layout(location = 0) in vec3 inPosition; 14 | layout(location = 1) in vec3 inColor; 15 | layout(location = 2) in vec2 inTexCoord; 16 | 17 | layout(location = 0) out vec3 fragColor; 18 | layout(location = 1) out vec2 fragTexCoord; 19 | 20 | out gl_PerVertex { 21 | vec4 gl_Position; 22 | }; 23 | 24 | void main() { 25 | gl_Position = camera.proj * camera.view * model * vec4(inPosition, 1.0); 26 | fragColor = inColor; 27 | fragTexCoord = inTexCoord; 28 | } 29 | -------------------------------------------------------------------------------- /src/shaders/grass.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(set = 0, binding = 0) uniform CameraBufferObject { 5 | mat4 view; 6 | mat4 proj; 7 | } camera; 8 | 9 | // TODO: Declare fragment shader inputs 10 | 11 | layout(location = 0) out vec4 outColor; 12 | 13 | void main() { 14 | // TODO: Compute fragment color 15 | 16 | outColor = vec4(1.0); 17 | } 18 | -------------------------------------------------------------------------------- /src/shaders/grass.tesc: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(vertices = 1) out; 5 | 6 | layout(set = 0, binding = 0) uniform CameraBufferObject { 7 | mat4 view; 8 | mat4 proj; 9 | } camera; 10 | 11 | // TODO: Declare tessellation control shader inputs and outputs 12 | 13 | void main() { 14 | // Don't move the origin location of the patch 15 | gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; 16 | 17 | // TODO: Write any shader outputs 18 | 19 | // TODO: Set level of tesselation 20 | // gl_TessLevelInner[0] = ??? 21 | // gl_TessLevelInner[1] = ??? 22 | // gl_TessLevelOuter[0] = ??? 23 | // gl_TessLevelOuter[1] = ??? 24 | // gl_TessLevelOuter[2] = ??? 25 | // gl_TessLevelOuter[3] = ??? 26 | } 27 | -------------------------------------------------------------------------------- /src/shaders/grass.tese: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(quads, equal_spacing, ccw) in; 5 | 6 | layout(set = 0, binding = 0) uniform CameraBufferObject { 7 | mat4 view; 8 | mat4 proj; 9 | } camera; 10 | 11 | // TODO: Declare tessellation evaluation shader inputs and outputs 12 | 13 | void main() { 14 | float u = gl_TessCoord.x; 15 | float v = gl_TessCoord.y; 16 | 17 | // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade 18 | } 19 | -------------------------------------------------------------------------------- /src/shaders/grass.vert: -------------------------------------------------------------------------------- 1 | 2 | #version 450 3 | #extension GL_ARB_separate_shader_objects : enable 4 | 5 | layout(set = 1, binding = 0) uniform ModelBufferObject { 6 | mat4 model; 7 | }; 8 | 9 | // TODO: Declare vertex shader inputs and outputs 10 | 11 | out gl_PerVertex { 12 | vec4 gl_Position; 13 | }; 14 | 15 | void main() { 16 | // TODO: Write gl_Position and any other shader outputs 17 | } 18 | --------------------------------------------------------------------------------