Project
This commit is contained in:
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterUnion: false
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
BeforeLambdaBody: false
|
||||||
|
BeforeWhile: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
ColumnLimit: 120
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ContinuationIndentWidth: 8
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^<.*'
|
||||||
|
Priority: 1
|
||||||
|
- Regex: '^".*'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 3
|
||||||
|
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
InsertNewlineAtEOF: true
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: All
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInConditionalStatement: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
TabWidth: 4
|
||||||
|
...
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Add: -Wno-unknown-warning-option
|
||||||
|
Remove: [-m*, -f*]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
build/
|
||||||
|
.idea
|
||||||
|
.zed
|
||||||
|
!deps/JoltPhysics/Build
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
|
build-clion/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
build/
|
||||||
|
.idea
|
||||||
|
.zed
|
||||||
|
!deps/JoltPhysics/Build
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
Executable
+42
@@ -0,0 +1,42 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.1)
|
||||||
|
project(cheesesandwich)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
##### Project settings ######
|
||||||
|
|
||||||
|
# Set the assets directory
|
||||||
|
set(ASSETS_DIR "assets")
|
||||||
|
set(SHADERS_DIR "assets/shaders")
|
||||||
|
|
||||||
|
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||||
|
set(BUILD_SHARED_LIBS ON)
|
||||||
|
else()
|
||||||
|
set(BUILD_SHARED_LIBS OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
##### Dependencies #####
|
||||||
|
set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
|
||||||
|
option(ENABLE_CPP20_MODULE "Enable C++ 20 module support for Vulkan" OFF)
|
||||||
|
file(GLOB CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/cmake/*.cmake")
|
||||||
|
foreach(cmake_file ${CMAKE_FILES})
|
||||||
|
include(${cmake_file})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
##### Project actions #####
|
||||||
|
|
||||||
|
# Define out executable
|
||||||
|
file(GLOB SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
||||||
|
file(GLOB HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")
|
||||||
|
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEADER_FILES})
|
||||||
|
|
||||||
|
# Add the shaders dependency
|
||||||
|
add_dependencies(${PROJECT_NAME} slang_shaders)
|
||||||
|
add_dependencies(${PROJECT_NAME} glsl_shaders)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC "${glfw_SOURCE_DIR}/include")
|
||||||
|
|
||||||
|
# Link with libs
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
glfw
|
||||||
|
Vulkan::cppm
|
||||||
|
)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
static float2 positions[3] = float2[] (
|
||||||
|
float2(0.0, -0.5),
|
||||||
|
float2(0.5, 0.5),
|
||||||
|
float2(-0.5, 0.5)
|
||||||
|
);
|
||||||
|
|
||||||
|
static float3 colors[3] = float3[](
|
||||||
|
float3(1.0, 0.0, 0.0),
|
||||||
|
float3(0.0, 1.0, 0.0),
|
||||||
|
float3(0.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
float3 color;
|
||||||
|
float4 sv_position : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
[shader("vertex")]
|
||||||
|
VertexOutput vertMain(uint vid: SV_VertexID) {
|
||||||
|
VertexOutput output;
|
||||||
|
output.sv_position = float4(positions[vid], 0.0, 1.0);
|
||||||
|
output.color = colors[vid];
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragMain(VertexOutput inVert) : SV_Target {
|
||||||
|
float3 color = inVert.color;
|
||||||
|
return float4(color, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
|
||||||
|
|
||||||
|
set(CPM_DOWNLOAD_VERSION 0.42.1)
|
||||||
|
set(CPM_HASH_SUM "f3a6dcc6a04ce9e7f51a127307fa4f699fb2bade357a8eb4c5b45df76e1dc6a5")
|
||||||
|
|
||||||
|
if(CPM_SOURCE_CACHE)
|
||||||
|
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||||
|
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
|
||||||
|
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||||
|
else()
|
||||||
|
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Expand relative path. This is important if the provided path contains a tilde (~)
|
||||||
|
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
|
||||||
|
|
||||||
|
file(DOWNLOAD
|
||||||
|
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
|
||||||
|
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
|
||||||
|
)
|
||||||
|
|
||||||
|
include(${CPM_DOWNLOAD_LOCATION})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# add check for CPM
|
||||||
|
|
||||||
|
|
||||||
|
CPMAddPackage (
|
||||||
|
URI "https://github.com/glfw/glfw/releases/download/3.4/glfw-3.4.zip"
|
||||||
|
OPTIONS "GLFW_BUILD_DOCS OFF"
|
||||||
|
OPTIONS "GLFW_BUILD_TEST OFF"
|
||||||
|
OPTIONS "GLFW_BUILD_EXAMPLES OFF"
|
||||||
|
OPTIONS "GLFW_BUILD_X11 OFF"
|
||||||
|
OPTIONS "GLFW_BUILD_WAYLAND ON"
|
||||||
|
)
|
||||||
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
# Vulkan #
|
||||||
|
if(ENABLE_CPP20_MODULE)
|
||||||
|
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(Vulkan REQUIRED)
|
||||||
|
if(ENABLE_CPP20_MODULE)
|
||||||
|
add_library(VulkanCppModule)
|
||||||
|
add_library(Vulkan::cppm ALIAS VulkanCppModule)
|
||||||
|
|
||||||
|
target_compile_definitions(VulkanCppModule
|
||||||
|
PUBLIC VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1
|
||||||
|
)
|
||||||
|
target_include_directories(VulkanCppModule
|
||||||
|
PUBLIC
|
||||||
|
"${Vulkan_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(VulkanCppModule
|
||||||
|
PUBLIC
|
||||||
|
Vulkan::Vulkan
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(VulkanCppModule PROPERTIES CXX_STANDARD 20)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(VulkanCppModule PRIVATE
|
||||||
|
/std:c++latest
|
||||||
|
/permissive-
|
||||||
|
/Zc:__cplusplus
|
||||||
|
/EHsc
|
||||||
|
/Zc:preprocessor
|
||||||
|
/translateInclude
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(VulkanCppModule
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET cxx_modules TYPE CXX_MODULES
|
||||||
|
BASE_DIRS
|
||||||
|
"${Vulkan_INCLUDE_DIR}"
|
||||||
|
FILES
|
||||||
|
"${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(VulkanCppModule
|
||||||
|
PRIVATE
|
||||||
|
"${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_library(VulkanCppModule INTERFACE)
|
||||||
|
add_library(Vulkan::cppm ALIAS VulkanCppModule)
|
||||||
|
target_link_libraries(VulkanCppModule INTERFACE Vulkan::Vulkan)
|
||||||
|
target_compile_definitions(VulkanCppModule
|
||||||
|
INTERFACE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
##### SETUP COMPILE COMMANDS #####
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS on)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json")
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
file(
|
||||||
|
COPY "${CMAKE_BINARY_DIR}/compile_commands.json"
|
||||||
|
DESTINATION "${CMAKE_BINARY_DIR}/../"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
option(SHADERS_DIR "Directory to look for GLSL shaders" "assets/shaders")
|
||||||
|
|
||||||
|
find_program(GLSLC_EXECUTABLE NAMES glslc)
|
||||||
|
if(NOT GLSLC_EXECUTABLE)
|
||||||
|
message(FATAL_ERROR "glslc executable not found.")
|
||||||
|
else()
|
||||||
|
message(STATUS "Found glslc executable: ${GLSLC_EXECUTABLE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
function(create_glslc_shader_target TARGET SOURCE_DIR OUTPUT_DIR)
|
||||||
|
file(MAKE_DIRECTORY ${OUTPUT_DIR})
|
||||||
|
message(STATUS "GLSL Shader output directory: ${OUTPUT_DIR}")
|
||||||
|
file (GLOB SHADER_FILES "${SOURCE_DIR}/*.vert")
|
||||||
|
file(GLOB FRAG_FILES "${SOURCE_DIR}/*.frag")
|
||||||
|
list(APPEND SHADER_FILES ${FRAG_FILES})
|
||||||
|
message(STATUS "Found glsl shaders: ${SHADER_FILES}")
|
||||||
|
set(OUTPUT_FILES "")
|
||||||
|
foreach(SHADER_FILE ${SHADER_FILES})
|
||||||
|
get_filename_component(SHADER_NAME ${SHADER_FILE} NAME)
|
||||||
|
set(OUTPUT_FILE "${OUTPUT_DIR}/${SHADER_NAME}.spv")
|
||||||
|
list(APPEND OUTPUT_FILES ${OUTPUT_FILE})
|
||||||
|
message(STATUS "Compiling ${SHADER_FILE} to ${OUTPUT_FILE}")
|
||||||
|
add_custom_command (
|
||||||
|
OUTPUT ${OUTPUT_FILE}
|
||||||
|
COMMAND ${GLSLC_EXECUTABLE} ${SHADER_FILE} -o ${OUTPUT_FILE}
|
||||||
|
DEPENDS ${SHADER_FILE}
|
||||||
|
COMMENT "Compiling ${SHADER_FILE} -> ${OUTPUT_FILE}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
add_custom_target(${TARGET} ALL DEPENDS ${OUTPUT_FILES})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
create_glslc_shader_target(glsl_shaders
|
||||||
|
"${SHADERS_DIR}"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/${SHADERS_DIR}"
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
message("System name: ${CMAKE_HOST_SYSTEM_NAME}")
|
||||||
|
if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
|
||||||
|
add_compile_definitions(PLATFORM_WINDOWS)
|
||||||
|
elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
add_compile_definitions(PLATFORM_LINUX)
|
||||||
|
elseif(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Darwin")
|
||||||
|
add_compile_definitions(PLATFORM_MACOS)
|
||||||
|
else()
|
||||||
|
add_compile_definitions(PLATFORM_UNKNOWN)
|
||||||
|
endif()
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
option(SHADERS_DIR "Directory to look for SLANG shaders" "assets/shaders")
|
||||||
|
|
||||||
|
find_program(SLANGC_EXECUTABLE NAMES slangc)
|
||||||
|
if(NOT SLANGC_EXECUTABLE)
|
||||||
|
message(FATAL_ERROR "slangc executable not found.")
|
||||||
|
else()
|
||||||
|
message(STATUS "Found slangc executable: ${SLANGC_EXECUTABLE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
function(create_slang_shader_target TARGET SOURCE_DIR OUTPUT_DIR)
|
||||||
|
file(MAKE_DIRECTORY ${OUTPUT_DIR})
|
||||||
|
message(STATUS "Shader output directory: ${OUTPUT_DIR}")
|
||||||
|
file(GLOB SHADER_FILES "${SOURCE_DIR}/*.slang")
|
||||||
|
message(STATUS "Found shaders: ${SHADER_FILES}")
|
||||||
|
set(ENTRY_POINTS -entry vertMain -entry fragMain)
|
||||||
|
set(OUTPUT_FILES "")
|
||||||
|
foreach(SHADER_FILE ${SHADER_FILES})
|
||||||
|
get_filename_component(SHADER_NAME ${SHADER_FILE} NAME_WE)
|
||||||
|
set(OUTPUT_FILE "${OUTPUT_DIR}/${SHADER_NAME}.spv")
|
||||||
|
list(APPEND OUTPUT_FILES ${OUTPUT_FILE})
|
||||||
|
message(STATUS "Compiling ${SHADER_FILE} to ${OUTPUT_FILE}")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${OUTPUT_FILE}
|
||||||
|
COMMAND ${SLANGC_EXECUTABLE} ${SHADER_FILE} -target spirv -profile spirv_1_4 -emit-spirv-directly -fvk-use-entrypoint-name ${ENTRY_POINTS} -o ${OUTPUT_FILE}
|
||||||
|
DEPENDS ${SHADER_FILE}
|
||||||
|
COMMENT "Compiling ${SHADER_FILE} -> ${OUTPUT_FILE}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
add_custom_target(${TARGET} ALL DEPENDS ${OUTPUT_FILES})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
create_slang_shader_target(slang_shaders
|
||||||
|
"${SHADERS_DIR}"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/${SHADERS_DIR}"
|
||||||
|
)
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p debug
|
||||||
|
cd debug
|
||||||
|
ninja
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p release
|
||||||
|
cd release
|
||||||
|
ninja
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p relwithdeb
|
||||||
|
cd relwithdeb
|
||||||
|
ninja
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ../
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p debug
|
||||||
|
cd debug
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
ninja
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p release
|
||||||
|
cd release
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||||
|
ninja
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p relwithdeb
|
||||||
|
cd relwithdeb
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
ninja
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cd build/debug || exit
|
||||||
|
ninja clean
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cd release || exit
|
||||||
|
ninja clean
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
cd relwithdeb || exit
|
||||||
|
ninja clean
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p debug
|
||||||
|
cd debug
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p release
|
||||||
|
cd release
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
mkdir -p relwithdeb
|
||||||
|
cd relwithdeb
|
||||||
|
cmake ../../ -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
#include "app.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
void Application::run() {
|
||||||
|
initWindow();
|
||||||
|
initVulkan();
|
||||||
|
mainLoop();
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::initVulkan() {
|
||||||
|
createInstance();
|
||||||
|
setupDebugMessenger();
|
||||||
|
createSurface();
|
||||||
|
pickPhysicalDevice();
|
||||||
|
createLogicalDevice();
|
||||||
|
createSwapChain();
|
||||||
|
createImageViews();
|
||||||
|
createGraphicsPipeline();
|
||||||
|
createCommandPool();
|
||||||
|
createCommandBuffer();
|
||||||
|
createSyncObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::mainLoop() {
|
||||||
|
// Loop until the window should be closed
|
||||||
|
while (!glfwWindowShouldClose(m_window)) {
|
||||||
|
// Poll for new window events
|
||||||
|
glfwPollEvents();
|
||||||
|
// Draw the next frame
|
||||||
|
drawFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the window is closed, its time to clean up everything. Which once we are out of this function GPU objects
|
||||||
|
// are de-allocated Since there is a high chance the GPU is still working on stuff, we need to wait for the device
|
||||||
|
// to idle before we return.
|
||||||
|
m_device.waitIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::drawFrame() {
|
||||||
|
// Wait for the previous frame to end
|
||||||
|
auto fenceResult = m_device.waitForFences(*m_inFlightFences[m_frameIndex], vk::True, UINT64_MAX);
|
||||||
|
// Get the next image in the swapchain
|
||||||
|
auto [result, imageIndex] =
|
||||||
|
m_swapchain.acquireNextImage(UINT64_MAX, *m_presentCompleteSemaphores[m_frameIndex], nullptr);
|
||||||
|
|
||||||
|
// Check to make sure the swapchain didn't report an eErrorOutOfDateKHR error
|
||||||
|
if (result == vk::Result::eErrorOutOfDateKHR) {
|
||||||
|
// Recreate the swapchain
|
||||||
|
recreateSwapchain();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the result isn't a eSuccess or eSuboptimalKHR, something has gone wrong and we should report on it.
|
||||||
|
if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) {
|
||||||
|
throw std::runtime_error("Failed to acquire next swapchain image");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the fence of the current image
|
||||||
|
m_device.resetFences(*m_inFlightFences[m_frameIndex]);
|
||||||
|
|
||||||
|
// Record the command buffer
|
||||||
|
recordCommandBuffer(imageIndex);
|
||||||
|
|
||||||
|
// Submit the command buffer to the queue
|
||||||
|
vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput);
|
||||||
|
const vk::SubmitInfo submitInfo{// Wait for the current frames present semaphore
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitSemaphores = &*m_presentCompleteSemaphores[m_frameIndex],
|
||||||
|
// Set our destination
|
||||||
|
.pWaitDstStageMask = &waitDestinationStageMask,
|
||||||
|
// Pass the command buffer/s to submit
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
.pCommandBuffers = &*m_commandBuffers[m_frameIndex],
|
||||||
|
// Pass the rendering semaphore to signal when done
|
||||||
|
.signalSemaphoreCount = 1,
|
||||||
|
.pSignalSemaphores = &*m_renderFinishedSemaphores[imageIndex]};
|
||||||
|
|
||||||
|
// Submit to the graphics queue
|
||||||
|
m_queue.submit(submitInfo, *m_inFlightFences[m_frameIndex]);
|
||||||
|
|
||||||
|
|
||||||
|
// Present the image
|
||||||
|
try {
|
||||||
|
const vk::PresentInfoKHR presentInfo{
|
||||||
|
// Wait for the rendering semaphore
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitSemaphores = &*m_renderFinishedSemaphores[imageIndex],
|
||||||
|
// Pass the swapchain we want to present with
|
||||||
|
.swapchainCount = 1,
|
||||||
|
.pSwapchains = &*m_swapchain,
|
||||||
|
// Pass the imageIndex of image we want to present in the swapchain
|
||||||
|
.pImageIndices = &imageIndex,
|
||||||
|
.pResults = nullptr // Optional, allows you to specify an array of vk::Result values to check for every
|
||||||
|
// swapchain. We only have one so we can just use the vk::Result returned from the
|
||||||
|
// present function
|
||||||
|
};
|
||||||
|
result = m_queue.presentKHR(presentInfo);
|
||||||
|
|
||||||
|
// If the result is eErrorOutOfDateKHR || eSuboptimalKHR || if the windows framebuffer has been resized we need
|
||||||
|
// to recreate the swapchain
|
||||||
|
if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || m_framebufferResized) {
|
||||||
|
m_framebufferResized = false;
|
||||||
|
recreateSwapchain();
|
||||||
|
} else if (result != vk::Result::eSuccess) {
|
||||||
|
// If we get any other error, we should error out
|
||||||
|
throw std::runtime_error("Failed to present swapchain image");
|
||||||
|
}
|
||||||
|
} catch (const vk::SystemError &e) {
|
||||||
|
// There is a chance that a SystemError will be thrown when presenting. The SystemError.code() could also be a
|
||||||
|
// eErrorOutOfDateKHR as well, so that should also be checked for so we can just recreate the swapchain as
|
||||||
|
// normal. All other errors should crash out.
|
||||||
|
if (e.code().value() == static_cast<int>(vk::Result::eErrorOutOfDateKHR)) {
|
||||||
|
recreateSwapchain();
|
||||||
|
} else {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate the frame index
|
||||||
|
m_frameIndex = (m_frameIndex + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::cleanup() {
|
||||||
|
// Destroy the swapchain
|
||||||
|
cleanupSwapchain();
|
||||||
|
|
||||||
|
// Destroy the window
|
||||||
|
glfwDestroyWindow(m_window);
|
||||||
|
// Terminate GLFW
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
|
||||||
|
// INCLUDES //
|
||||||
|
|
||||||
|
// Include vulkan
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES)
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#else
|
||||||
|
import vulkan_hpp;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_VULKAN
|
||||||
|
#if defined(PLATFORM_WINDOWS)
|
||||||
|
#define VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
||||||
|
#elif defined(PLATFORM_LINUX)
|
||||||
|
#define GLFW_EXPOSE_NATIVE_WAYLAND
|
||||||
|
#elif defined(PLATFORM_MACOS)
|
||||||
|
#define VK_USE_PLATFORM_MACOS_MVK
|
||||||
|
#defined GLFW_EXPOSE_NATIVE_COCOA
|
||||||
|
#endif
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <GLFW/glfw3native.h>
|
||||||
|
|
||||||
|
// The window width and height
|
||||||
|
constexpr uint32_t WIDTH = 800;
|
||||||
|
constexpr uint32_t HEIGHT = 600;
|
||||||
|
constexpr int MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
|
const std::vector<char const *> validationLayers = {"VK_LAYER_KHRONOS_validation"};
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
constexpr bool enableValidationLayers = false;
|
||||||
|
#else
|
||||||
|
constexpr bool enableValidationLayers = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Application {
|
||||||
|
public:
|
||||||
|
// FUNCTIONS //
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// MEMBERS //
|
||||||
|
|
||||||
|
GLFWwindow *m_window = nullptr;
|
||||||
|
vk::raii::Context m_context;
|
||||||
|
vk::raii::Instance m_instance = nullptr;
|
||||||
|
vk::raii::DebugUtilsMessengerEXT m_debugMessenger = nullptr;
|
||||||
|
vk::raii::PhysicalDevice m_physicalDevice = nullptr;
|
||||||
|
vk::raii::Device m_device = nullptr;
|
||||||
|
vk::raii::Queue m_queue = nullptr;
|
||||||
|
uint32_t m_queueIndex;
|
||||||
|
vk::raii::SurfaceKHR m_surface = nullptr;
|
||||||
|
vk::raii::SwapchainKHR m_swapchain = nullptr;
|
||||||
|
std::vector<vk::Image> m_swapchainImages;
|
||||||
|
vk::SurfaceFormatKHR m_swapchainSurfaceFormat;
|
||||||
|
vk::Extent2D m_swapchainExtent;
|
||||||
|
std::vector<vk::raii::ImageView> m_swapchainImageViews;
|
||||||
|
vk::raii::PipelineLayout m_pipelineLayout = nullptr;
|
||||||
|
vk::raii::Pipeline m_graphicsPipeline = nullptr;
|
||||||
|
vk::raii::CommandPool m_commandPool = nullptr;
|
||||||
|
std::vector<vk::raii::CommandBuffer> m_commandBuffers;
|
||||||
|
std::vector<vk::raii::Semaphore> m_presentCompleteSemaphores;
|
||||||
|
std::vector<vk::raii::Semaphore> m_renderFinishedSemaphores;
|
||||||
|
std::vector<vk::raii::Fence> m_inFlightFences;
|
||||||
|
bool m_framebufferResized = false;
|
||||||
|
uint32_t m_frameIndex = 0;
|
||||||
|
|
||||||
|
// FUNCTIONS //
|
||||||
|
|
||||||
|
void initWindow(); // Create the GLFW window
|
||||||
|
void initVulkan(); // Init vulkan and create all the vulkan objects we need
|
||||||
|
void mainLoop(); // Run the main loop
|
||||||
|
void drawFrame(); // Draw a new frame
|
||||||
|
void cleanup(); // Clean up all objects
|
||||||
|
|
||||||
|
// GLFW //
|
||||||
|
static void framebufferResizeCallback(GLFWwindow *window, int width, int height);
|
||||||
|
|
||||||
|
// VULKAN //
|
||||||
|
|
||||||
|
// Instance //
|
||||||
|
void createInstance();
|
||||||
|
std::vector<const char *> getRequiredExtensions();
|
||||||
|
void setupDebugMessenger();
|
||||||
|
// Surface //
|
||||||
|
void createSurface();
|
||||||
|
// Device //
|
||||||
|
void pickPhysicalDevice();
|
||||||
|
// Logical device //
|
||||||
|
void createLogicalDevice();
|
||||||
|
// Swapchain //
|
||||||
|
vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR> &availableFormats);
|
||||||
|
vk::PresentModeKHR chooseSwapPresentMode(const std::vector<vk::PresentModeKHR> &availablePresentModes);
|
||||||
|
vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities);
|
||||||
|
static uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities);
|
||||||
|
void createSwapChain();
|
||||||
|
void createImageViews();
|
||||||
|
void recreateSwapchain();
|
||||||
|
void cleanupSwapchain();
|
||||||
|
|
||||||
|
// Shader //
|
||||||
|
static std::vector<char> readFile(const std::string &filename);
|
||||||
|
[[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector<char> &code) const;
|
||||||
|
|
||||||
|
// Pipeline //
|
||||||
|
void createGraphicsPipeline();
|
||||||
|
|
||||||
|
// Commands //
|
||||||
|
void createCommandPool();
|
||||||
|
void createCommandBuffer();
|
||||||
|
void recordCommandBuffer(uint32_t imageIndex);
|
||||||
|
void transition_image_layout(uint32_t imageIndex, vk::ImageLayout oldLayout, vk::ImageLayout newLayout,
|
||||||
|
vk::AccessFlags2 srcAccessMask, vk::AccessFlags2 dstAccessMask,
|
||||||
|
vk::PipelineStageFlags2 srcStageMask, vk::PipelineStageFlags2 dstStageMask);
|
||||||
|
|
||||||
|
// Synchronization //
|
||||||
|
void createSyncObjects();
|
||||||
|
};
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
void Application::createCommandPool() {
|
||||||
|
// Create the command pool
|
||||||
|
vk::CommandPoolCreateInfo poolInfo{
|
||||||
|
// Theres a few flags we can set here, mostly eResetCommandBuffer and eTransient
|
||||||
|
// eResetCommandBuffer will allow all command buffers to be reset individually, you normally have to reset
|
||||||
|
// all of them together
|
||||||
|
// eTransient hints that command buffers are rerecorded very often and changes memory allocation
|
||||||
|
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||||
|
// Make sure we pass in our queue index as well
|
||||||
|
.queueFamilyIndex = m_queueIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the command pool
|
||||||
|
m_commandPool = vk::raii::CommandPool(m_device, poolInfo, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createCommandBuffer() {
|
||||||
|
// Clear the vector of command buffers
|
||||||
|
m_commandBuffers.clear();
|
||||||
|
// Define the buffers
|
||||||
|
vk::CommandBufferAllocateInfo allocInfo{
|
||||||
|
// Pass in our command pool
|
||||||
|
.commandPool = m_commandPool,
|
||||||
|
// Theres a few options here, ePrimary and eSecondary. ePrimary buffers can be submitted to a queue for
|
||||||
|
// execution and can not be called on by other buffers, but eSecondary can't be executed by a queue but can
|
||||||
|
// by another buffer
|
||||||
|
.level = vk::CommandBufferLevel::ePrimary,
|
||||||
|
// Each frame gets its own command buffer
|
||||||
|
.commandBufferCount = MAX_FRAMES_IN_FLIGHT};
|
||||||
|
|
||||||
|
// Create the buffers
|
||||||
|
m_commandBuffers = vk::raii::CommandBuffers(m_device, allocInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::recordCommandBuffer(uint32_t imageIndex) {
|
||||||
|
auto &commandBuffer = m_commandBuffers[m_frameIndex];
|
||||||
|
commandBuffer.begin({});
|
||||||
|
|
||||||
|
// Set the image layout to eColorAttachmentOptimal and access to eColorAttachmentWrite
|
||||||
|
transition_image_layout(imageIndex, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, {},
|
||||||
|
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||||
|
vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
|
vk::PipelineStageFlagBits2::eColorAttachmentOutput);
|
||||||
|
|
||||||
|
// Configure the clear color
|
||||||
|
vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
// Configure the attachment info
|
||||||
|
vk::RenderingAttachmentInfo attachmentInfo{.imageView = m_swapchainImageViews[imageIndex],
|
||||||
|
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
||||||
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||||
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||||
|
.clearValue = clearColor};
|
||||||
|
// Configure the rendering info
|
||||||
|
vk::RenderingInfo renderingInfo{.renderArea = {.offset = {0, 0}, .extent = m_swapchainExtent},
|
||||||
|
.layerCount = 1,
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachments = &attachmentInfo};
|
||||||
|
|
||||||
|
// Begin rendering
|
||||||
|
commandBuffer.beginRendering(renderingInfo);
|
||||||
|
|
||||||
|
// Set the viewport and scissor sizes *dynamically*
|
||||||
|
commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast<float>(m_swapchainExtent.width),
|
||||||
|
static_cast<float>(m_swapchainExtent.height), 0.0f, 1.0f));
|
||||||
|
commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), m_swapchainExtent));
|
||||||
|
|
||||||
|
// Bind the pipeline we want to use
|
||||||
|
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, m_graphicsPipeline);
|
||||||
|
// Draw!
|
||||||
|
commandBuffer.draw(3, 1, 0, 0);
|
||||||
|
|
||||||
|
commandBuffer.endRendering();
|
||||||
|
|
||||||
|
// Set the image layout back to ePresentSrcKHR so it can be presented to the screen
|
||||||
|
transition_image_layout(imageIndex, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR,
|
||||||
|
vk::AccessFlagBits2::eColorAttachmentWrite, {},
|
||||||
|
vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||||
|
vk::PipelineStageFlagBits2::eBottomOfPipe);
|
||||||
|
|
||||||
|
commandBuffer.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::transition_image_layout(uint32_t imageIndex, vk::ImageLayout oldLayout, vk::ImageLayout newLayout,
|
||||||
|
vk::AccessFlags2 srcAccessMask, vk::AccessFlags2 dstAccessMask,
|
||||||
|
vk::PipelineStageFlags2 srcStageMask, vk::PipelineStageFlags2 dstStageMask) {
|
||||||
|
vk::ImageMemoryBarrier2 barrier = {.srcStageMask = srcStageMask,
|
||||||
|
.srcAccessMask = srcAccessMask,
|
||||||
|
.dstStageMask = dstStageMask,
|
||||||
|
.dstAccessMask = dstAccessMask,
|
||||||
|
.oldLayout = oldLayout,
|
||||||
|
.newLayout = newLayout,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.image = m_swapchainImages[imageIndex],
|
||||||
|
.subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = 1}};
|
||||||
|
|
||||||
|
vk::DependencyInfo dependencyInfo{
|
||||||
|
.dependencyFlags = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier};
|
||||||
|
|
||||||
|
m_commandBuffers[m_frameIndex].pipelineBarrier2(dependencyInfo);
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
// List of extensions we need
|
||||||
|
std::vector<const char *> requiredDeviceExtensions = {
|
||||||
|
// Make sure we have a swapchain
|
||||||
|
vk::KHRSwapchainExtensionName,
|
||||||
|
// Make sure we can use spirv
|
||||||
|
vk::KHRSpirv14ExtensionName,
|
||||||
|
// Make sure we can do synchronization
|
||||||
|
vk::KHRSynchronization2ExtensionName,
|
||||||
|
// Make sure we can create renderpasses
|
||||||
|
vk::KHRCreateRenderpass2ExtensionName};
|
||||||
|
|
||||||
|
void Application::pickPhysicalDevice() {
|
||||||
|
// Get an array of all device
|
||||||
|
std::vector<vk::raii::PhysicalDevice> devices = m_instance.enumeratePhysicalDevices();
|
||||||
|
// Loop through the devices
|
||||||
|
const auto devIter = std::ranges::find_if(devices, [&](auto const &device) {
|
||||||
|
// Make sure the device supports vulkan 1.4 or higher
|
||||||
|
bool supportsVulkan13 = device.getProperties().apiVersion >= VK_API_VERSION_1_4;
|
||||||
|
|
||||||
|
// Make sure the device supports graphics
|
||||||
|
auto queueFamilies = device.getQueueFamilyProperties();
|
||||||
|
bool supportsGraphics = std::ranges::any_of(
|
||||||
|
queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); });
|
||||||
|
|
||||||
|
// Make sure the device supports all the extensions we want to enable
|
||||||
|
auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties();
|
||||||
|
bool supportsAllRequiredExtensions = std::ranges::all_of(
|
||||||
|
requiredDeviceExtensions, [&availableDeviceExtensions](auto const &requiredDeviceExtension) {
|
||||||
|
return std::ranges::any_of(
|
||||||
|
availableDeviceExtensions, [requiredDeviceExtension](auto const &availableDeviceExtension) {
|
||||||
|
return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure the device supports the features we want to enable
|
||||||
|
auto features = device.template getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features,
|
||||||
|
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>();
|
||||||
|
bool supportsRequiredFeatures =
|
||||||
|
features.template get<vk::PhysicalDeviceVulkan13Features>().dynamicRendering &&
|
||||||
|
features.template get<vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>().extendedDynamicState;
|
||||||
|
|
||||||
|
// Return a 1 if everything is supported
|
||||||
|
return supportsVulkan13 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures;
|
||||||
|
});
|
||||||
|
|
||||||
|
// In the case we found a device, set it as our physical device
|
||||||
|
if (devIter != devices.end()) {
|
||||||
|
m_physicalDevice = *devIter;
|
||||||
|
} else {
|
||||||
|
// If we didn't find a device, error out
|
||||||
|
throw std::runtime_error("Failed to find a suitable GPU");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createLogicalDevice() {
|
||||||
|
// Get the supported queue families from the physical device
|
||||||
|
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = m_physicalDevice.getQueueFamilyProperties();
|
||||||
|
|
||||||
|
// Loop through and get the graphics and present queue
|
||||||
|
m_queueIndex = ~0;
|
||||||
|
for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) {
|
||||||
|
if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) &&
|
||||||
|
m_physicalDevice.getSurfaceSupportKHR(qfpIndex, *m_surface)) {
|
||||||
|
m_queueIndex = qfpIndex;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a graphics and present queue are not found, error out
|
||||||
|
if (m_queueIndex == ~0) {
|
||||||
|
throw std::runtime_error("Failed to find a queue that supports graphics and presentation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the queue
|
||||||
|
float queuePriority = 0.5;
|
||||||
|
vk::DeviceQueueCreateInfo deviceQueueCreateInfo{
|
||||||
|
// Pass the index of the queue we want to use
|
||||||
|
.queueFamilyIndex = m_queueIndex,
|
||||||
|
.queueCount = 1,
|
||||||
|
.pQueuePriorities = &queuePriority,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We don't need any specific device features at the moment, so just default to
|
||||||
|
// everything being set to false
|
||||||
|
vk::PhysicalDeviceFeatures deviceFeatures;
|
||||||
|
|
||||||
|
// Most vulkan structures have a .pNext value that is used to chain multiple structures
|
||||||
|
// together. Using a structure chain we can do this chaining automatically instead of
|
||||||
|
// having to manually define the .pNext for each element.
|
||||||
|
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features,
|
||||||
|
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>
|
||||||
|
featureChain = {
|
||||||
|
{}, // PhysicalDeviceFeatures1
|
||||||
|
{.dynamicRendering = true}, // PhysicalDeviceVulkan12Features
|
||||||
|
{.extendedDynamicState = true} // PhysicalDeviceExtendedDynamicStateFeaturesEXT
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the logical device
|
||||||
|
vk::DeviceCreateInfo deviceCreateInfo{
|
||||||
|
// Make sure we pass the structure chain of the features we want to enable
|
||||||
|
.pNext = &featureChain.get<vk::PhysicalDeviceFeatures2>(),
|
||||||
|
// Pass the queue info
|
||||||
|
.queueCreateInfoCount = 1,
|
||||||
|
.pQueueCreateInfos = &deviceQueueCreateInfo,
|
||||||
|
// Set the logical device extensions
|
||||||
|
.enabledExtensionCount = static_cast<uint32_t>(requiredDeviceExtensions.size()),
|
||||||
|
.ppEnabledExtensionNames = requiredDeviceExtensions.data(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the logical device and queue
|
||||||
|
m_device = vk::raii::Device(m_physicalDevice, deviceCreateInfo);
|
||||||
|
m_queue = vk::raii::Queue(m_device, m_queueIndex, 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vk_platform.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include <vulkan/vulkan_to_string.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
void Application::createInstance() {
|
||||||
|
// Create the app info
|
||||||
|
constexpr vk::ApplicationInfo appInfo{.pApplicationName = "Vulk",
|
||||||
|
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||||
|
.pEngineName = "No Engine",
|
||||||
|
.engineVersion = VK_MAKE_API_VERSION(1, 0, 0, 0),
|
||||||
|
// Define what version of vulkan we want to use
|
||||||
|
.apiVersion = vk::ApiVersion14};
|
||||||
|
|
||||||
|
// Get the validation layers
|
||||||
|
std::vector<char const *> requiredLayers;
|
||||||
|
if (enableValidationLayers) {
|
||||||
|
requiredLayers.assign(validationLayers.begin(), validationLayers.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through and make sure all required layers are supported
|
||||||
|
auto layerProperties = m_context.enumerateInstanceLayerProperties();
|
||||||
|
if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const &requiredLayer) {
|
||||||
|
return std::ranges::none_of(layerProperties, [requiredLayer](auto const &layerProperty) {
|
||||||
|
return strcmp(layerProperty.layerName, requiredLayer) == 0;
|
||||||
|
});
|
||||||
|
})) {
|
||||||
|
throw std::runtime_error("One or more required layers not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a vector of the extensions we want to enable
|
||||||
|
auto requiredExtensions = getRequiredExtensions();
|
||||||
|
// Get the extensions that are supported
|
||||||
|
auto extensionProperties = m_context.enumerateInstanceExtensionProperties();
|
||||||
|
// Loop through all required extensions
|
||||||
|
for (auto const &requiredExtension: requiredExtensions) {
|
||||||
|
if (std::ranges::none_of(extensionProperties, [requiredExtension](auto const &extensionProperty) {
|
||||||
|
return strcmp(extensionProperty.extensionName, requiredExtension) == 0;
|
||||||
|
})) {
|
||||||
|
throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create the instance crate info
|
||||||
|
vk::InstanceCreateInfo createInfo{// Pass our application info
|
||||||
|
.pApplicationInfo = &appInfo,
|
||||||
|
// Define the layers we want
|
||||||
|
.enabledLayerCount = static_cast<uint32_t>(requiredLayers.size()),
|
||||||
|
.ppEnabledLayerNames = requiredLayers.data(),
|
||||||
|
// Define the extensions we want
|
||||||
|
.enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size()),
|
||||||
|
.ppEnabledExtensionNames = requiredExtensions.data()};
|
||||||
|
|
||||||
|
// Create the instance
|
||||||
|
m_instance = vk::raii::Instance(m_context, createInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const char *> Application::getRequiredExtensions() {
|
||||||
|
// Get the list of extensions that GLFW needs
|
||||||
|
uint32_t glfwExtensionCount = 0;
|
||||||
|
auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
|
||||||
|
// GLFW returns it as a const char** so convert to a vector
|
||||||
|
std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
|
||||||
|
|
||||||
|
// Make sure we include validation layers if enabled
|
||||||
|
if (enableValidationLayers) {
|
||||||
|
extensions.push_back(vk::EXTDebugUtilsExtensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the list
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||||
|
vk::DebugUtilsMessageTypeFlagsEXT type,
|
||||||
|
const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData,
|
||||||
|
void *) {
|
||||||
|
// Simply just print the message to stdout
|
||||||
|
std::cerr << "Validation layer: type " << vk::to_string(type) << " msg: " << pCallbackData->pMessage << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setupDebugMessenger() {
|
||||||
|
// If validation layers are disabled, we can just skip this step
|
||||||
|
if (!enableValidationLayers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define what severity we want
|
||||||
|
vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
|
||||||
|
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
|
||||||
|
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError);
|
||||||
|
// Define the type of validation messages we want
|
||||||
|
vk::DebugUtilsMessageTypeFlagsEXT typeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
|
||||||
|
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
|
||||||
|
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance);
|
||||||
|
// Create the messenger
|
||||||
|
vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo{
|
||||||
|
.messageSeverity = severityFlags, .messageType = typeFlags, .pfnUserCallback = &debugCallback};
|
||||||
|
m_debugMessenger = m_instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfo);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "app.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Create the application
|
||||||
|
Application app;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run le app
|
||||||
|
app.run();
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
// If we have an error just print it out and exit
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
void Application::createGraphicsPipeline() {
|
||||||
|
// Load in the shader
|
||||||
|
auto shaderCode = readFile("assets/shaders/shader.spv");
|
||||||
|
// Create the shader module
|
||||||
|
vk::raii::ShaderModule shaderModule = createShaderModule(shaderCode);
|
||||||
|
|
||||||
|
// Define the fragment and vertex shader stages, making sure to specify "vertMain" and "fragMain" are the entry
|
||||||
|
// points
|
||||||
|
vk::PipelineShaderStageCreateInfo vertShaderStageInfo{
|
||||||
|
.stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain"};
|
||||||
|
vk::PipelineShaderStageCreateInfo fragShaderStageInfo{
|
||||||
|
.stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain"};
|
||||||
|
// Make sure the stages are in an array so its easier to pass on later on
|
||||||
|
vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
|
||||||
|
|
||||||
|
|
||||||
|
// Set the dynamic states. *Normally* pipelines are completely immutable and require full recreation if anything
|
||||||
|
// needs to be changed. Using dynamic states we can change *some* of the pipeline. In this case, the viewport and
|
||||||
|
// scissor are dynamic and defined at frametime
|
||||||
|
std::vector dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||||
|
vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size()),
|
||||||
|
.pDynamicStates = dynamicStates.data()};
|
||||||
|
|
||||||
|
// Normally you'd pass vertices to the GPU requiring you to define how that vertex data is structured. Here we have
|
||||||
|
// hardcoded the vertices into the shader itself to keep stuff somewhat simple. As a result the vertex input state
|
||||||
|
// is just going to be empty.
|
||||||
|
vk::PipelineVertexInputStateCreateInfo vertexInputInfo;
|
||||||
|
|
||||||
|
// Set the vertex assembly
|
||||||
|
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo{
|
||||||
|
.topology = vk::PrimitiveTopology::eTriangleList,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define out the viewports and scissors
|
||||||
|
vk::PipelineViewportStateCreateInfo viewportState{
|
||||||
|
.viewportCount = 1, .pViewports = {}, .scissorCount = 1, .pScissors = {}};
|
||||||
|
|
||||||
|
// Define the rasterization state
|
||||||
|
vk::PipelineRasterizationStateCreateInfo rasterizationStateInfo{
|
||||||
|
// If we want to clamp the depth buffer data to a range of values
|
||||||
|
.depthClampEnable = vk::False,
|
||||||
|
// If we want to allow fragment discarding
|
||||||
|
.rasterizerDiscardEnable = vk::False,
|
||||||
|
// How we want to draw polygons, fill, lines, points, etc
|
||||||
|
.polygonMode = vk::PolygonMode::eFill,
|
||||||
|
// Face culling
|
||||||
|
.cullMode = vk::CullModeFlagBits::eBack,
|
||||||
|
.frontFace = vk::FrontFace::eClockwise,
|
||||||
|
// Depth buffer data doesn't have to be a linear 0.0f-1.0f, here we can bias it
|
||||||
|
.depthBiasEnable = vk::False,
|
||||||
|
.depthBiasSlopeFactor = 1.0f,
|
||||||
|
// Line width
|
||||||
|
.lineWidth = 1.0f};
|
||||||
|
|
||||||
|
// This is for MSAA, at the moment we aren't using it but should be fairly simple to enable, just need to change the
|
||||||
|
// swapchain images to support it, change the sample count here, and let GLFW know.
|
||||||
|
vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1,
|
||||||
|
.sampleShadingEnable = vk::False};
|
||||||
|
|
||||||
|
// Color blending attachment
|
||||||
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment{
|
||||||
|
// Enabled or not
|
||||||
|
.blendEnable = vk::False,
|
||||||
|
// Set the src and dst color factors
|
||||||
|
.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha,
|
||||||
|
.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha,
|
||||||
|
// Set the color operation
|
||||||
|
.colorBlendOp = vk::BlendOp::eAdd,
|
||||||
|
// Set the src and dst alpha factors
|
||||||
|
.srcAlphaBlendFactor = vk::BlendFactor::eOne,
|
||||||
|
.dstAlphaBlendFactor = vk::BlendFactor::eZero,
|
||||||
|
// Set the alpha operation
|
||||||
|
.alphaBlendOp = vk::BlendOp::eAdd,
|
||||||
|
// Define what components we want to blend
|
||||||
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA};
|
||||||
|
|
||||||
|
// Color blending as a whole
|
||||||
|
vk::PipelineColorBlendStateCreateInfo colorBlending{// Enabled or not
|
||||||
|
.logicOpEnable = vk::False,
|
||||||
|
// Operation to run between attachments
|
||||||
|
.logicOp = vk::LogicOp::eCopy,
|
||||||
|
// Our attachment/s
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &colorBlendAttachment};
|
||||||
|
|
||||||
|
// Pipeline layout
|
||||||
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0};
|
||||||
|
|
||||||
|
// Create the layout
|
||||||
|
m_pipelineLayout = vk::raii::PipelineLayout{m_device, pipelineLayoutInfo};
|
||||||
|
|
||||||
|
// Set the rending state
|
||||||
|
vk::PipelineRenderingCreateInfo renderingCreateInfo{.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachmentFormats = &m_swapchainSurfaceFormat.format};
|
||||||
|
|
||||||
|
// Create the pipeline
|
||||||
|
// (could use a structure chain here if i wanted...)
|
||||||
|
vk::GraphicsPipelineCreateInfo pipelineInfo{
|
||||||
|
// Make sure we include the rendering create info next
|
||||||
|
.pNext = &renderingCreateInfo,
|
||||||
|
// Specify the stages
|
||||||
|
.stageCount = 2,
|
||||||
|
.pStages = shaderStages,
|
||||||
|
// Specify the vertex input
|
||||||
|
.pVertexInputState = &vertexInputInfo,
|
||||||
|
// Specify the vertex assembly
|
||||||
|
.pInputAssemblyState = &inputAssemblyInfo,
|
||||||
|
// Specify the viewport state
|
||||||
|
.pViewportState = &viewportState,
|
||||||
|
// Specify the rasterization state
|
||||||
|
.pRasterizationState = &rasterizationStateInfo,
|
||||||
|
// Specify the MSAA state
|
||||||
|
.pMultisampleState = &multisampling,
|
||||||
|
// Set blending
|
||||||
|
.pColorBlendState = &colorBlending,
|
||||||
|
// Set the dynamic states
|
||||||
|
.pDynamicState = &dynamicState,
|
||||||
|
// Define the layout
|
||||||
|
.layout = m_pipelineLayout,
|
||||||
|
// Normally you'd need to define a render pass, however, since we are doing some dynamic state stuff we
|
||||||
|
// don't need to
|
||||||
|
.renderPass = nullptr,
|
||||||
|
// These are here for pipeline creation. Since you'd likely need many many pipelines as they are immutable,
|
||||||
|
// vulkan allows you to pass an already created pipeline and base your new pipeline on it.
|
||||||
|
.basePipelineHandle = VK_NULL_HANDLE, // Optional for now, but useful to remember in the future
|
||||||
|
.basePipelineIndex = -1 // Same here...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the pipeline
|
||||||
|
m_graphicsPipeline = vk::raii::Pipeline(m_device, nullptr, pipelineInfo, nullptr);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include <fstream>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
std::vector<char> Application::readFile(const std::string &filename) {
|
||||||
|
// Load the file in, checking to make sure it openned without errors
|
||||||
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer with the same size of the file
|
||||||
|
std::vector<char> buffer(file.tellg());
|
||||||
|
// Make sure we are at the beginning of the file
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
// Read the data in
|
||||||
|
file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||||
|
// Close the file and return the buffer
|
||||||
|
file.close();
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::raii::ShaderModule Application::createShaderModule(const std::vector<char> &code) const {
|
||||||
|
// Create the shader module (feel like this is self explainatory enough to not need a comment at all...)
|
||||||
|
vk::ShaderModuleCreateInfo createInfo{.codeSize = code.size() * sizeof(char),
|
||||||
|
.pCode = reinterpret_cast<const uint32_t *>(code.data())};
|
||||||
|
|
||||||
|
vk::raii::ShaderModule shaderModule{m_device, createInfo};
|
||||||
|
|
||||||
|
return shaderModule;
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
vk::SurfaceFormatKHR Application::chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR> &availableFormats) {
|
||||||
|
// Check for eB8G8R8Srgb & eSrgbNonlinear support
|
||||||
|
for (const auto &availableFormat: availableFormats) {
|
||||||
|
if (availableFormat.format == vk::Format::eB8G8R8Srgb &&
|
||||||
|
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
|
||||||
|
return availableFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If eB8G8R8Srgb & eSrgbNonlinear are not available, default to the first available format
|
||||||
|
return availableFormats[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PresentModeKHR Application::chooseSwapPresentMode(const std::vector<vk::PresentModeKHR> &availablePresentModes) {
|
||||||
|
// Check for eMailbox support. Return that if available.
|
||||||
|
for (const auto &availablePresentMode: availablePresentModes) {
|
||||||
|
if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
|
||||||
|
return availablePresentMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eFifo is guaranteed to be supported, so default to it.
|
||||||
|
return vk::PresentModeKHR::eFifo;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Extent2D Application::chooseSwapExtent(const vk::SurfaceCapabilitiesKHR &capabilities) {
|
||||||
|
// If the extent is already created, just use that.
|
||||||
|
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
|
||||||
|
return capabilities.currentExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the size of the window
|
||||||
|
int width, height;
|
||||||
|
glfwGetWindowSize(m_window, &width, &height);
|
||||||
|
// Return a new extent, clamping the window size between the min and max image extents
|
||||||
|
return {
|
||||||
|
std::clamp<uint32_t>(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
||||||
|
std::clamp<uint32_t>(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Application::chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) {
|
||||||
|
// Clamp to the min image count or 3 if min is less.
|
||||||
|
auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount);
|
||||||
|
// There are cases where a GPU doesn't support images (servers) or its max count is below 3, so clamp to that.
|
||||||
|
if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) {
|
||||||
|
minImageCount = surfaceCapabilities.maxImageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return minImageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createSwapChain() {
|
||||||
|
// Get the surfaceCapabilities
|
||||||
|
auto surfaceCapabilities = m_physicalDevice.getSurfaceCapabilitiesKHR(m_surface);
|
||||||
|
// Choose the surface format
|
||||||
|
m_swapchainSurfaceFormat = chooseSwapSurfaceFormat(m_physicalDevice.getSurfaceFormatsKHR(m_surface));
|
||||||
|
// Figure out the size of the swapchain images
|
||||||
|
m_swapchainExtent = chooseSwapExtent(surfaceCapabilities);
|
||||||
|
|
||||||
|
vk::SwapchainCreateInfoKHR swapchainCreateInfo{
|
||||||
|
// Pass the surface
|
||||||
|
.surface = *m_surface,
|
||||||
|
// Specify the number of images
|
||||||
|
.minImageCount = chooseSwapMinImageCount(surfaceCapabilities),
|
||||||
|
// Pass the image format, colorspace, and size
|
||||||
|
.imageFormat = m_swapchainSurfaceFormat.format,
|
||||||
|
.imageColorSpace = m_swapchainSurfaceFormat.colorSpace,
|
||||||
|
.imageExtent = m_swapchainExtent,
|
||||||
|
// The number of layers (images) in this image, think about how a 3d image is just multiple 2d image layers
|
||||||
|
.imageArrayLayers = 1,
|
||||||
|
// Define the image usage
|
||||||
|
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
|
||||||
|
// Define the sharing mode
|
||||||
|
.imageSharingMode = vk::SharingMode::eExclusive,
|
||||||
|
// Transform the images to the current surface
|
||||||
|
.preTransform = surfaceCapabilities.currentTransform,
|
||||||
|
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||||
|
// Set the present mode
|
||||||
|
.presentMode = chooseSwapPresentMode(m_physicalDevice.getSurfacePresentModesKHR(*m_surface)),
|
||||||
|
// Clip the images
|
||||||
|
.clipped = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the swapchain
|
||||||
|
m_swapchain = vk::raii::SwapchainKHR(m_device, swapchainCreateInfo);
|
||||||
|
// Get an array of the swapchain images
|
||||||
|
m_swapchainImages = m_swapchain.getImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createImageViews() {
|
||||||
|
// Clear the image views array
|
||||||
|
m_swapchainImageViews.clear();
|
||||||
|
// Create a creatInfo for all image views
|
||||||
|
vk::ImageViewCreateInfo imageViewCreateInfo{// Define the image type
|
||||||
|
.viewType = vk::ImageViewType::e2D,
|
||||||
|
// Define the format (same as the swapchain image format)
|
||||||
|
.format = m_swapchainSurfaceFormat.format,
|
||||||
|
// Define the subresourceRange
|
||||||
|
.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}};
|
||||||
|
|
||||||
|
// Loop through the images in the swapchain and create the m_imageViews
|
||||||
|
for (auto image: m_swapchainImages) {
|
||||||
|
imageViewCreateInfo.image = image;
|
||||||
|
m_swapchainImageViews.emplace_back(m_device, imageViewCreateInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::cleanupSwapchain() {
|
||||||
|
// Clear the image views
|
||||||
|
m_swapchainImageViews.clear();
|
||||||
|
// Set the swapchain as a nullptr (i believe raii will handle the dealloc)
|
||||||
|
m_swapchain = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::recreateSwapchain() {
|
||||||
|
// We really should only need to recreate the swapchain when the window is resized, in that case we need to get the
|
||||||
|
// new size of the window
|
||||||
|
int width = 0, height = 0;
|
||||||
|
glfwGetFramebufferSize(m_window, &width, &height);
|
||||||
|
while (width == 0 || height == 0) {
|
||||||
|
// In the case the window is minimized, we want to get the size and wait for new events until unminimized
|
||||||
|
glfwGetFramebufferSize(m_window, &width, &height);
|
||||||
|
glfwWaitEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all current tasks to complete so we don't destroy in use resources
|
||||||
|
m_device.waitIdle();
|
||||||
|
|
||||||
|
// Cleanup and create the swapchain
|
||||||
|
cleanupSwapchain();
|
||||||
|
createSwapChain();
|
||||||
|
createImageViews();
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
#include "vulkan/vulkan.hpp"
|
||||||
|
|
||||||
|
// A note on semaphores and fences:
|
||||||
|
// For a long while these honestly scared me a bit. In hind sight I have no idea why, maybe cause (for me atleast) they
|
||||||
|
// weren't all that intuitive or it was just explained wrong or something. Either way, they are a fairly simple and very
|
||||||
|
// very powerful. I'm going to *attempt* to explain them as well as I can with what and how I know them so far. Should
|
||||||
|
// be said that at this stage I am still very much in the process of learning and understanding vulkan and at its
|
||||||
|
// current state this is just still a rainbow triangle. If I do a shit job heres the documentation that finally made it
|
||||||
|
// click for me. Granted most of what is there is described here.
|
||||||
|
// https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.html#_synchronization
|
||||||
|
//
|
||||||
|
// Semaphores:
|
||||||
|
// At their core Semaphores are just a boolean that is set when a GPU task is finished and checked (if passed in the
|
||||||
|
// tasks create info). With it being a boolean, there are two states it can be set as, signaled or unsignaled. All
|
||||||
|
// semaphores start life as unsignaled.
|
||||||
|
//
|
||||||
|
// Say you have 2 tasks, A and B, and a single semaphore, S. You want B to execute on the GPU only when task A is
|
||||||
|
// finished. In this case we want task A to signal S and task B to wait on S.
|
||||||
|
// In pseudocode this would sort of look like below.
|
||||||
|
//
|
||||||
|
// vkQueueSubmit(work: A, signal: S, wait: None); // Submit work A, signaling S when done
|
||||||
|
// vkQueueSubmit(work: B, signal: None, wait: S); // Submit work B, waiting for S to signal
|
||||||
|
//
|
||||||
|
// Fences:
|
||||||
|
// Fences work much the same but on the CPU side. Just like semaphores at there core they are booleans. But they can be
|
||||||
|
// used to signal the CPU when a GPU task is finished. This allows you to have the CPU wait for GPU tasks to complete
|
||||||
|
// instead of just having all task return instantly.
|
||||||
|
//
|
||||||
|
// In this case say you have task A and fence F,
|
||||||
|
//
|
||||||
|
// vkQueueSubmit(work: A, fence: F); // Submit A to the GPU, returns immediately, but signals fence F when done.
|
||||||
|
// vkWaitForFence(F); // Wait for fence F to signal
|
||||||
|
// someOtherFunctionToRunAfterTheTask(); // Run some task afterwards
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// Most importantly a fence will block execution on the host (CPU) while a call with a semaphore will always return
|
||||||
|
// whether or not the task has finished.
|
||||||
|
//
|
||||||
|
// While I've compared semaphores and fences to booleans, there are other flags that change the behavior that I have yet
|
||||||
|
// to explore. While I feel like for the most part, you can get away with using semaphores as described, you might be
|
||||||
|
// able to use them in a different way and have much much better results. Always check the documentation.
|
||||||
|
//
|
||||||
|
// Semaphores:
|
||||||
|
// https://docs.vulkan.org/refpages/latest/refpages/source/VkSemaphore.html
|
||||||
|
// Fences:
|
||||||
|
// https://docs.vulkan.org/refpages/latest/refpages/source/VkFence.html
|
||||||
|
//
|
||||||
|
// Other documentation:
|
||||||
|
// https://www.khronos.org/blog/understanding-vulkan-synchronization
|
||||||
|
|
||||||
|
|
||||||
|
void Application::createSyncObjects() {
|
||||||
|
// The last thing we want to do is accidentally re-create the sync objects while they are in use. Ideally this
|
||||||
|
// function is called once on init, but programming is hard so we should error out if the vectors aren't empty.
|
||||||
|
assert(m_presentCompleteSemaphores.empty() && m_renderFinishedSemaphores.empty() && m_inFlightFences.empty() &&
|
||||||
|
"Attempt to create sync objects while vectors are non-empty");
|
||||||
|
|
||||||
|
// Create the m_renderFinishedSemaphores
|
||||||
|
for (size_t i = 0; i < m_swapchainImages.size(); i++) {
|
||||||
|
m_renderFinishedSemaphores.emplace_back(m_device, vk::SemaphoreCreateInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the m_presentCompleteSemaphores and m_inFlightFences
|
||||||
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||||
|
m_presentCompleteSemaphores.emplace_back(m_device, vk::SemaphoreCreateInfo());
|
||||||
|
m_inFlightFences.emplace_back(m_device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include <stdexcept>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include <vulkan/vulkan_raii.hpp>
|
||||||
|
#include "app.h"
|
||||||
|
|
||||||
|
void Application::initWindow() {
|
||||||
|
// GLFW is a state machine that needs to be initialized
|
||||||
|
glfwInit();
|
||||||
|
|
||||||
|
// By default, GLFW will spawn an OpenGL client. Since we are using Vulkan, we need to tell it to just not use an
|
||||||
|
// api at all as we will be getting the window surface directly.
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
|
||||||
|
// Create the window, checking if it is still a nullptr afterwards to make sure it was created correctly.
|
||||||
|
m_window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
|
||||||
|
if (m_window == nullptr) {
|
||||||
|
// Get the error code and message from GLFW
|
||||||
|
const char *desc;
|
||||||
|
uint32_t code = glfwGetError(&desc);
|
||||||
|
throw std::runtime_error(std::format("Failed to create GLFW window: ({}) {}", code, desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLFW callback functions are required to be static, meaning we can't interact with our Application data directly.
|
||||||
|
// To get around this we can specify a window user pointer (void*) to our application instance and reinterpret_cast
|
||||||
|
// it into an application in the callbacks.
|
||||||
|
glfwSetWindowUserPointer(m_window, this);
|
||||||
|
// Set our framebuffer resize callback
|
||||||
|
glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::framebufferResizeCallback(GLFWwindow *window, int width, int height) {
|
||||||
|
// Get our application user point from the window and reinterpret_cast it.
|
||||||
|
auto app = reinterpret_cast<Application *>(glfwGetWindowUserPointer(window));
|
||||||
|
// Set m_framebufferResized so the swapchain is recreated in the next frame.
|
||||||
|
app->m_framebufferResized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createSurface() {
|
||||||
|
// GLFW uses the C only vulkan types so we need to create a temp surface object.
|
||||||
|
VkSurfaceKHR _surface;
|
||||||
|
// Get the window surface
|
||||||
|
if (glfwCreateWindowSurface(*m_instance, m_window, nullptr, &_surface) != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("Failed to create window surface");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remake the surface as a CPP type using the C object GLFW gave us.
|
||||||
|
m_surface = vk::raii::SurfaceKHR(m_instance, _surface);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user