should prob put this on git sometime
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
cmake-**
|
||||||
|
|
||||||
|
# Runtime asset folders
|
||||||
|
destrum/assets_runtime
|
||||||
|
lightkeeper/assets_runtime
|
||||||
|
|
||||||
|
.vs/**
|
||||||
|
.idea/**
|
||||||
31
.gitmodules
vendored
Normal file
31
.gitmodules
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
[submodule "destrum/third_party/glm"]
|
||||||
|
path = destrum/third_party/glm
|
||||||
|
url = https://github.com/g-truc/glm.git
|
||||||
|
[submodule "destrum/third_party/fmt"]
|
||||||
|
path = destrum/third_party/fmt
|
||||||
|
url = https://github.com/fmtlib/fmt.git
|
||||||
|
[submodule "destrum/third_party/freetype"]
|
||||||
|
path = destrum/third_party/freetype
|
||||||
|
url = https://github.com/freetype/freetype.git
|
||||||
|
[submodule "destrum/third_party/json"]
|
||||||
|
path = destrum/third_party/json
|
||||||
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "destrum/third_party/volk"]
|
||||||
|
path = destrum/third_party/volk
|
||||||
|
url = https://github.com/zeux/volk.git
|
||||||
|
[submodule "destrum/third_party/sdl"]
|
||||||
|
path = destrum/third_party/sdl
|
||||||
|
url = https://github.com/libsdl-org/SDL.git
|
||||||
|
branch = SDL2
|
||||||
|
[submodule "destrum/third_party/vma"]
|
||||||
|
path = destrum/third_party/vma
|
||||||
|
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||||
|
[submodule "destrum/third_party/vk-bootstrap"]
|
||||||
|
path = destrum/third_party/vk-bootstrap
|
||||||
|
url = https://github.com/charles-lunarg/vk-bootstrap.git
|
||||||
|
[submodule "destrum/third_party/glfw"]
|
||||||
|
path = destrum/third_party/glfw
|
||||||
|
url = https://github.com/glfw/glfw.git
|
||||||
|
[submodule "destrum/third_party/spdlog"]
|
||||||
|
path = destrum/third_party/spdlog
|
||||||
|
url = https://github.com/gabime/spdlog.git
|
||||||
12
CMakeLists.txt
Normal file
12
CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.31)
|
||||||
|
project(Destrum)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# Disable Just My Code debugging
|
||||||
|
add_compile_options(/JMC-)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(destrum)
|
||||||
|
add_subdirectory(lightkeeper)
|
||||||
27
cmake/compile_shaders.cmake
Normal file
27
cmake/compile_shaders.cmake
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
function(compile_glsl_to_spv target shader_src_dir shader_out_dir out_var)
|
||||||
|
find_program(GLSLC glslc REQUIRED)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SHADERS
|
||||||
|
"${shader_src_dir}/*.vert"
|
||||||
|
"${shader_src_dir}/*.frag"
|
||||||
|
"${shader_src_dir}/*.comp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(SPV_OUTPUTS "")
|
||||||
|
foreach(shader ${SHADERS})
|
||||||
|
file(RELATIVE_PATH rel "${shader_src_dir}" "${shader}")
|
||||||
|
set(out "${shader_out_dir}/${rel}.spv")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${out}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<PATH:GET_PARENT_PATH,${out}>"
|
||||||
|
COMMAND ${GLSLC} -o "${out}" "${shader}"
|
||||||
|
DEPENDS "${shader}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND SPV_OUTPUTS "${out}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
add_custom_target(${target}_shaders ALL DEPENDS ${SPV_OUTPUTS})
|
||||||
|
set(${out_var} ${SPV_OUTPUTS} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
79
destrum/CMakeLists.txt
Normal file
79
destrum/CMakeLists.txt
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
add_subdirectory(third_party)
|
||||||
|
|
||||||
|
set(SRC_FILES
|
||||||
|
"src/App.cpp"
|
||||||
|
|
||||||
|
"src/Graphics/GfxDevice.cpp"
|
||||||
|
"src/Graphics/ImmediateExecuter.cpp"
|
||||||
|
"src/Graphics/Swapchain.cpp"
|
||||||
|
"src/Graphics/VImage.cpp"
|
||||||
|
"src/Graphics/Init.cpp"
|
||||||
|
"src/Graphics/Util.cpp"
|
||||||
|
"src/graphics/Pipeline.cpp"
|
||||||
|
|
||||||
|
"src/FS/AssetFS.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(destrum ${SRC_FILES})
|
||||||
|
|
||||||
|
add_library(destrum::destrum ALIAS destrum)
|
||||||
|
|
||||||
|
set_target_properties(destrum PROPERTIES
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_EXTENSIONS OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
#target_add_extra_warnings(destrum)
|
||||||
|
|
||||||
|
target_include_directories(destrum PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
|
|
||||||
|
target_link_libraries(destrum
|
||||||
|
PUBLIC
|
||||||
|
volk::volk_headers
|
||||||
|
vk-bootstrap::vk-bootstrap
|
||||||
|
GPUOpen::VulkanMemoryAllocator
|
||||||
|
|
||||||
|
glm::glm
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
spdlog::spdlog
|
||||||
|
|
||||||
|
PRIVATE
|
||||||
|
stb::image
|
||||||
|
freetype::freetype
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(destrum
|
||||||
|
PUBLIC
|
||||||
|
VK_NO_PROTOTYPES
|
||||||
|
VMA_VULKAN_VERSION=1003000
|
||||||
|
# VOLK_DEFAULT_VISIBILITY # FIXME: doesn't work for some reason
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
if(BUILD_SHARED_LIBS)
|
||||||
|
target_link_libraries(destrum
|
||||||
|
PUBLIC SDL2::SDL2main SDL2::SDL2
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
target_link_libraries(destrum
|
||||||
|
PUBLIC SDL2::SDL2main SDL2::SDL2-static
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(destrum
|
||||||
|
PUBLIC
|
||||||
|
GLM_FORCE_CTOR_INIT
|
||||||
|
GLM_FORCE_XYZW_ONLY
|
||||||
|
GLM_FORCE_EXPLICIT_CTOR
|
||||||
|
GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
GLM_ENABLE_EXPERIMENTAL
|
||||||
|
)
|
||||||
|
|
||||||
|
set(DESTRUM_SHADER_SRC "${CMAKE_CURRENT_LIST_DIR}/assets_src/shaders")
|
||||||
|
set(DESTRUM_SHADER_OUT "${CMAKE_CURRENT_LIST_DIR}/assets_runtime/shaders")
|
||||||
|
|
||||||
|
include(../cmake/compile_shaders.cmake)
|
||||||
|
|
||||||
|
compile_glsl_to_spv(destrum "${DESTRUM_SHADER_SRC}" "${DESTRUM_SHADER_OUT}" DESTRUM_SPV)
|
||||||
|
add_dependencies(destrum destrum_shaders)
|
||||||
1
destrum/assets_runtime/assetfstest.txt
Normal file
1
destrum/assets_runtime/assetfstest.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello world!
|
||||||
1
destrum/assets_src/assetfstest.txt
Normal file
1
destrum/assets_src/assetfstest.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello world!
|
||||||
0
destrum/assets_src/temp
Normal file
0
destrum/assets_src/temp
Normal file
43
destrum/include/destrum/App.h
Normal file
43
destrum/include/destrum/App.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef APP_H
|
||||||
|
#define APP_H
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "glm/vec2.hpp"
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
|
||||||
|
|
||||||
|
class App {
|
||||||
|
public:
|
||||||
|
struct AppParams {
|
||||||
|
glm::ivec2 windowSize{};
|
||||||
|
glm::ivec2 renderSize{};
|
||||||
|
std::string appName{"Destrum App"};
|
||||||
|
std::string windowTitle;
|
||||||
|
std::filesystem::path exeDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
void init(const AppParams& params);
|
||||||
|
void run();
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SDL_Window* window{nullptr};
|
||||||
|
AppParams m_params{};
|
||||||
|
|
||||||
|
GfxDevice gfxDevice;
|
||||||
|
|
||||||
|
bool isRunning{false};
|
||||||
|
bool gamePaused{false};
|
||||||
|
|
||||||
|
bool frameLimit{true};
|
||||||
|
float frameTime{0.f};
|
||||||
|
float avgFPS{0.f};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //APP_H
|
||||||
32
destrum/include/destrum/FS/AssetFS.h
Normal file
32
destrum/include/destrum/FS/AssetFS.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef ASSETFS_H
|
||||||
|
#define ASSETFS_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <destrum/Singleton.h>
|
||||||
|
|
||||||
|
struct FSMount {
|
||||||
|
std::string scheme; // "engine", "game"
|
||||||
|
std::filesystem::path root;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AssetFS final: public Singleton<AssetFS> {
|
||||||
|
public:
|
||||||
|
void Init(std::filesystem::path exeDir);
|
||||||
|
void Mount(std::string scheme, std::filesystem::path root);
|
||||||
|
|
||||||
|
std::vector<uint8_t> ReadBytes(std::string_view vpath);
|
||||||
|
|
||||||
|
[[nodiscard]] std::filesystem::path GetFullPath(std::string_view vpath) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<uint8_t> ReadFile(const std::filesystem::path& fullPath);
|
||||||
|
|
||||||
|
std::vector<FSMount> mounts;
|
||||||
|
|
||||||
|
bool initialized{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //ASSETFS_H
|
||||||
94
destrum/include/destrum/Graphics/GfxDevice.h
Normal file
94
destrum/include/destrum/Graphics/GfxDevice.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#ifndef GFXDEVICE_H
|
||||||
|
#define GFXDEVICE_H
|
||||||
|
|
||||||
|
#include <VkBootstrap.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/VImage.h>
|
||||||
|
#include <destrum/Graphics/Swapchain.h>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <volk.h>
|
||||||
|
#include <VkBootstrap.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
|
#include "ImmediateExecuter.h"
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Swapchain.h>
|
||||||
|
|
||||||
|
using ImageId = std::uint32_t;
|
||||||
|
static const auto NULL_IMAGE_ID = std::numeric_limits<std::uint32_t>::max();
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using ImmediateExecuteFunction = std::function<void(VkCommandBuffer)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GfxDevice {
|
||||||
|
public:
|
||||||
|
struct FrameData {
|
||||||
|
VkCommandPool commandPool;
|
||||||
|
VkCommandBuffer commandBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
GfxDevice();
|
||||||
|
GfxDevice(const GfxDevice&) = delete;
|
||||||
|
GfxDevice& operator=(const GfxDevice&) = delete;
|
||||||
|
|
||||||
|
void init(SDL_Window* window, const std::string& appName, bool vSync);
|
||||||
|
void recreateSwapchain(int width, int height);
|
||||||
|
|
||||||
|
VkCommandBuffer beginFrame();
|
||||||
|
bool needsSwapchainRecreate() const { return swapchain.isDirty(); }
|
||||||
|
|
||||||
|
|
||||||
|
struct EndFrameProps {
|
||||||
|
const VkClearColorValue clearColor{ {0.f, 0.f, 0.f, 1.f} };
|
||||||
|
glm::ivec4 drawImageBlitRect{}; // where to blit draw image to
|
||||||
|
};
|
||||||
|
void endFrame(VkCommandBuffer cmd, const VImage& drawImage, const EndFrameProps& props);
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
void waitIdle();
|
||||||
|
|
||||||
|
void immediateSubmit(ImmediateExecuteFunction&& f) const;
|
||||||
|
|
||||||
|
vkb::Device getDevice() const { return device; }
|
||||||
|
|
||||||
|
std::uint32_t getCurrentFrameIndex() const
|
||||||
|
{
|
||||||
|
return frameNumber % FRAMES_IN_FLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameData& getCurrentFrame() {
|
||||||
|
return frames[getCurrentFrameIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
VkExtent2D getSwapchainExtent() const { return swapchain.getExtent(); }
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
vkb::Instance instance;
|
||||||
|
vkb::PhysicalDevice physicalDevice;
|
||||||
|
vkb::Device device;
|
||||||
|
VmaAllocator allocator;
|
||||||
|
|
||||||
|
std::uint32_t graphicsQueueFamily;
|
||||||
|
VkQueue graphicsQueue;
|
||||||
|
|
||||||
|
VkSurfaceKHR surface;
|
||||||
|
VkFormat swapchainFormat;
|
||||||
|
Swapchain swapchain;
|
||||||
|
|
||||||
|
std::array<FrameData, FRAMES_IN_FLIGHT> frames{};
|
||||||
|
std::uint32_t frameNumber{0};
|
||||||
|
|
||||||
|
VulkanImmediateExecutor executor;
|
||||||
|
|
||||||
|
ImageId whiteImageId{NULL_IMAGE_ID};
|
||||||
|
ImageId errorImageId{NULL_IMAGE_ID};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //GFXDEVICE_H
|
||||||
27
destrum/include/destrum/Graphics/ImmediateExecuter.h
Normal file
27
destrum/include/destrum/Graphics/ImmediateExecuter.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef IMMEDIATEEXECUTER_H
|
||||||
|
#define IMMEDIATEEXECUTER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
class VulkanImmediateExecutor {
|
||||||
|
public:
|
||||||
|
void init(VkDevice device, std::uint32_t graphicsQueueFamily, VkQueue graphicsQueue);
|
||||||
|
void cleanup(VkDevice device);
|
||||||
|
|
||||||
|
void immediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialized{false};
|
||||||
|
|
||||||
|
VkDevice device;
|
||||||
|
VkQueue graphicsQueue;
|
||||||
|
|
||||||
|
VkCommandBuffer immCommandBuffer;
|
||||||
|
VkCommandPool immCommandPool;
|
||||||
|
VkFence immFence;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //IMMEDIATEEXECUTER_H
|
||||||
62
destrum/include/destrum/Graphics/Init.h
Normal file
62
destrum/include/destrum/Graphics/Init.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#ifndef INIT_H
|
||||||
|
#define INIT_H
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
struct VImage;
|
||||||
|
|
||||||
|
namespace vkinit
|
||||||
|
{
|
||||||
|
VkImageSubresourceRange imageSubresourceRange(VkImageAspectFlags aspectMask);
|
||||||
|
VkSemaphoreSubmitInfo semaphoreSubmitInfo(VkPipelineStageFlags2 stageMask, VkSemaphore semaphore);
|
||||||
|
|
||||||
|
VkCommandPoolCreateInfo commandPoolCreateInfo(
|
||||||
|
VkCommandPoolCreateFlags flags,
|
||||||
|
std::uint32_t queueFamilyIndex);
|
||||||
|
|
||||||
|
VkCommandBufferSubmitInfo commandBufferSubmitInfo(VkCommandBuffer cmd);
|
||||||
|
VkCommandBufferAllocateInfo commandBufferAllocateInfo(
|
||||||
|
VkCommandPool commandPool,
|
||||||
|
std::uint32_t commandBufferCount);
|
||||||
|
|
||||||
|
VkSubmitInfo2 submitInfo(
|
||||||
|
const VkCommandBufferSubmitInfo* cmd,
|
||||||
|
const VkSemaphoreSubmitInfo* waitSemaphoreInfo,
|
||||||
|
const VkSemaphoreSubmitInfo* signalSemaphoreInfo);
|
||||||
|
|
||||||
|
VkImageCreateInfo imageCreateInfo(
|
||||||
|
VkFormat format,
|
||||||
|
VkImageUsageFlags usageFlags,
|
||||||
|
VkExtent3D extent,
|
||||||
|
std::uint32_t mipLevels = 1);
|
||||||
|
VkImageViewCreateInfo imageViewCreateInfo(
|
||||||
|
VkFormat format,
|
||||||
|
VkImage image,
|
||||||
|
VkImageAspectFlags aspectFlags);
|
||||||
|
|
||||||
|
VkRenderingAttachmentInfo attachmentInfo(
|
||||||
|
VkImageView view,
|
||||||
|
VkImageLayout layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
std::optional<VkClearValue> clearValue = std::nullopt);
|
||||||
|
|
||||||
|
VkRenderingAttachmentInfo depthAttachmentInfo(
|
||||||
|
VkImageView view,
|
||||||
|
VkImageLayout layout,
|
||||||
|
std::optional<float> depthClearValue = 0.f);
|
||||||
|
|
||||||
|
VkRenderingInfo renderingInfo(
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const VkRenderingAttachmentInfo* colorAttachment,
|
||||||
|
const VkRenderingAttachmentInfo* depthAttachment);
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo pipelineShaderStageCreateInfo(
|
||||||
|
VkShaderStageFlagBits stage,
|
||||||
|
VkShaderModule shaderModule);
|
||||||
|
} // end of namespace vkinit
|
||||||
|
|
||||||
|
#endif //INIT_H
|
||||||
58
destrum/include/destrum/Graphics/Pipeline.h
Normal file
58
destrum/include/destrum/Graphics/Pipeline.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef VPIPELINE_H
|
||||||
|
#define VPIPELINE_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
|
||||||
|
struct PipelineConfigInfo {
|
||||||
|
std::string name;
|
||||||
|
VkPipelineViewportStateCreateInfo viewportInfo{};
|
||||||
|
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
|
||||||
|
VkPipelineRasterizationStateCreateInfo rasterizationInfo{};
|
||||||
|
VkPipelineMultisampleStateCreateInfo multisampleInfo{};
|
||||||
|
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
|
||||||
|
VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
|
||||||
|
VkPipelineDepthStencilStateCreateInfo depthStencilInfo{};
|
||||||
|
std::vector<VkDynamicState> dynamicStateEnables{};
|
||||||
|
VkPipelineDynamicStateCreateInfo dynamicStateInfo{};
|
||||||
|
|
||||||
|
std::vector<VkFormat> colorAttachments{};
|
||||||
|
VkFormat depthAttachment{VK_FORMAT_UNDEFINED};
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<VkVertexInputBindingDescription> vertexBindingDescriptions{};
|
||||||
|
std::vector<VkVertexInputAttributeDescription> vertexAttributeDescriptions{};
|
||||||
|
|
||||||
|
VkPipelineLayout pipelineLayout = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Pipeline {
|
||||||
|
public:
|
||||||
|
Pipeline(GfxDevice& device, const std::string& vertPath, const std::string& fragPath, const PipelineConfigInfo& configInfo);
|
||||||
|
~Pipeline();
|
||||||
|
|
||||||
|
Pipeline(const Pipeline& other) = delete;
|
||||||
|
Pipeline(Pipeline&& other) noexcept = delete;
|
||||||
|
Pipeline& operator=(const Pipeline& other) = delete;
|
||||||
|
Pipeline& operator=(Pipeline&& other) noexcept = delete;
|
||||||
|
|
||||||
|
void bind(VkCommandBuffer buffer) const;
|
||||||
|
static void DefaultPipelineConfigInfo(PipelineConfigInfo& configInfo);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<char> readFile(const std::string& filename);
|
||||||
|
|
||||||
|
void CreateGraphicsPipeline(const std::string& vertPath, const std::string& fragPath, const PipelineConfigInfo& configInfo);
|
||||||
|
|
||||||
|
void CreateShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) const;
|
||||||
|
|
||||||
|
GfxDevice& m_device;
|
||||||
|
|
||||||
|
VkPipeline m_graphicsPipeline{VK_NULL_HANDLE};
|
||||||
|
VkShaderModule m_vertShaderModule{VK_NULL_HANDLE};
|
||||||
|
VkShaderModule m_fragShaderModule{VK_NULL_HANDLE};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //VPIPELINE_H
|
||||||
24
destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h
Normal file
24
destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MESHPIPELINE_H
|
||||||
|
#define MESHPIPELINE_H
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <destrum/Graphics/Pipeline.h>
|
||||||
|
|
||||||
|
class MeshPipeline {
|
||||||
|
public:
|
||||||
|
MeshPipeline();
|
||||||
|
~MeshPipeline();
|
||||||
|
|
||||||
|
void init(GfxDevice& gfxDevice, VkFormat drawImageFormat, VkFormat depthImageFormat);
|
||||||
|
void draw();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkPipelineLayout m_pipelineLayout;
|
||||||
|
|
||||||
|
std::unique_ptr<Pipeline> m_pipeline;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //MESHPIPELINE_H
|
||||||
10
destrum/include/destrum/Graphics/Renderer.h
Normal file
10
destrum/include/destrum/Graphics/Renderer.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef RENDERER_H
|
||||||
|
#define RENDERER_H
|
||||||
|
|
||||||
|
class GameRenderer {
|
||||||
|
public:
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //RENDERER_H
|
||||||
15
destrum/include/destrum/Graphics/Resources/Buffer.h
Normal file
15
destrum/include/destrum/Graphics/Resources/Buffer.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef BUFFER_H
|
||||||
|
#define BUFFER_H
|
||||||
|
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
struct GPUBuffer {
|
||||||
|
VkBuffer buffer{VK_NULL_HANDLE};
|
||||||
|
VmaAllocation allocation;
|
||||||
|
VmaAllocationInfo info;
|
||||||
|
|
||||||
|
VkDeviceAddress address{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //BUFFER_H
|
||||||
53
destrum/include/destrum/Graphics/Resources/Mesh.h
Normal file
53
destrum/include/destrum/Graphics/Resources/Mesh.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#ifndef MESH_H
|
||||||
|
#define MESH_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
struct CPUMesh {
|
||||||
|
std::vector<std::uint32_t> indices;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec3 position;
|
||||||
|
float uv_x{};
|
||||||
|
glm::vec3 normal;
|
||||||
|
float uv_y{};
|
||||||
|
glm::vec4 tangent;
|
||||||
|
};
|
||||||
|
std::vector<Vertex> vertices;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
glm::vec3 minPos;
|
||||||
|
glm::vec3 maxPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct GPUMesh {
|
||||||
|
GPUBuffer vertexBuffer;
|
||||||
|
GPUBuffer indexBuffer;
|
||||||
|
|
||||||
|
std::uint32_t numVertices{0};
|
||||||
|
std::uint32_t numIndices{0};
|
||||||
|
|
||||||
|
// AABB
|
||||||
|
glm::vec3 minPos;
|
||||||
|
glm::vec3 maxPos;
|
||||||
|
math::Sphere boundingSphere;
|
||||||
|
|
||||||
|
bool hasSkeleton{false};
|
||||||
|
// skinned meshes only
|
||||||
|
GPUBuffer skinningDataBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SkinnedMesh {
|
||||||
|
GPUBuffer skinnedVertexBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //MESH_H
|
||||||
54
destrum/include/destrum/Graphics/Swapchain.h
Normal file
54
destrum/include/destrum/Graphics/Swapchain.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef SWAPCHAIN_H
|
||||||
|
#define SWAPCHAIN_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include "VkBootstrap.h"
|
||||||
|
|
||||||
|
class GfxDevice;
|
||||||
|
static constexpr int FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
|
class Swapchain {
|
||||||
|
public:
|
||||||
|
void initSync(VkDevice device);
|
||||||
|
//Ye ye, passing a pointer is bad bla bla @Kobe
|
||||||
|
void createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync);
|
||||||
|
void recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync);
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
[[nodiscard]] VkExtent2D getExtent() const { return extent; }
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<VkImage>& getImages() const { return images; }
|
||||||
|
[[nodiscard]] std::uint32_t getImageCount() const { return static_cast<std::uint32_t>(images.size()); }
|
||||||
|
|
||||||
|
void beginFrame(int index) const;
|
||||||
|
|
||||||
|
void resetFences(int index) const;
|
||||||
|
|
||||||
|
std::pair<VkImage, int> acquireNextImage(int index);
|
||||||
|
|
||||||
|
void submitAndPresent(VkCommandBuffer cmd, VkQueue graphicsQueue, std::uint32_t imageIndex, std::uint32_t frameIndex);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isDirty() const { return dirty; }
|
||||||
|
|
||||||
|
[[nodiscard]] VkImageView getImageView(int index) const { return imageViews[index]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FrameData {
|
||||||
|
VkSemaphore swapchainSemaphore;
|
||||||
|
VkFence renderFence;
|
||||||
|
};
|
||||||
|
std::vector<VkSemaphore> imageRenderSemaphores;
|
||||||
|
|
||||||
|
std::array<FrameData, FRAMES_IN_FLIGHT> frames;
|
||||||
|
vkb::Swapchain m_swapchain; //Euuuh, m_ cuz like cpluhpluh
|
||||||
|
std::vector<VkImage> images;
|
||||||
|
std::vector<VkImageView> imageViews;
|
||||||
|
bool dirty{false};
|
||||||
|
GfxDevice* m_gfxDevice{nullptr};
|
||||||
|
VkExtent2D extent{};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SWAPCHAIN_H
|
||||||
39
destrum/include/destrum/Graphics/Util.h
Normal file
39
destrum/include/destrum/Graphics/Util.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef UTIL_H
|
||||||
|
#define UTIL_H
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define VK_CHECK(call) \
|
||||||
|
do { \
|
||||||
|
VkResult result_ = call; \
|
||||||
|
assert(result_ == VK_SUCCESS); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
namespace vkutil {
|
||||||
|
void transitionImage(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkImage image,
|
||||||
|
VkImageLayout currentLayout,
|
||||||
|
VkImageLayout newLayout);
|
||||||
|
|
||||||
|
void copyImageToImage(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkImage source,
|
||||||
|
VkImage destination,
|
||||||
|
VkExtent2D srcSize,
|
||||||
|
VkExtent2D dstSize,
|
||||||
|
VkFilter filter);
|
||||||
|
|
||||||
|
void copyImageToImage(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkImage source,
|
||||||
|
VkImage destination,
|
||||||
|
VkExtent2D srcSize,
|
||||||
|
int destX,
|
||||||
|
int destY,
|
||||||
|
int destW,
|
||||||
|
int destH,
|
||||||
|
VkFilter filter);
|
||||||
|
}
|
||||||
|
#endif //UTIL_H
|
||||||
48
destrum/include/destrum/Graphics/VImage.h
Normal file
48
destrum/include/destrum/Graphics/VImage.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#ifndef VIMAGE_H
|
||||||
|
#define VIMAGE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
class VImage {
|
||||||
|
public:
|
||||||
|
glm::ivec2 getSize2D() const { return glm::ivec2{extent.width, extent.height}; }
|
||||||
|
VkExtent2D getExtent2D() const { return VkExtent2D{extent.width, extent.height}; }
|
||||||
|
|
||||||
|
std::uint32_t getBindlessId() const
|
||||||
|
{
|
||||||
|
assert(id != NULL_BINDLESS_ID && "Image wasn't added to bindless set");
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBindlessId(const std::uint32_t id)
|
||||||
|
{
|
||||||
|
assert(id != NULL_BINDLESS_ID);
|
||||||
|
this->id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInitialized() const { return id != NULL_BINDLESS_ID; }
|
||||||
|
|
||||||
|
VkImage getImage() const { return image; }
|
||||||
|
VkImageView getImageView() const { return imageView; }
|
||||||
|
VmaAllocation getAllocation() const { return allocation; }
|
||||||
|
private:
|
||||||
|
VkImage image;
|
||||||
|
VkImageView imageView;
|
||||||
|
VmaAllocation allocation;
|
||||||
|
VkFormat format;
|
||||||
|
VkImageUsageFlags usage;
|
||||||
|
VkExtent3D extent;
|
||||||
|
std::uint32_t mipLevels{1};
|
||||||
|
std::uint32_t numLayers{1};
|
||||||
|
std::string debugName{};
|
||||||
|
std::uint32_t id{NULL_BINDLESS_ID}; // bindless id - always equals to ImageId
|
||||||
|
|
||||||
|
static const auto NULL_BINDLESS_ID = std::numeric_limits<std::uint32_t>::max();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //VIMAGE_H
|
||||||
22
destrum/include/destrum/Singleton.h
Normal file
22
destrum/include/destrum/Singleton.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef SINGLETON_H
|
||||||
|
#define SINGLETON_H
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Singleton {
|
||||||
|
public:
|
||||||
|
static T& GetInstance() {
|
||||||
|
static T instance{};
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~Singleton() = default;
|
||||||
|
Singleton(const Singleton& other) = delete;
|
||||||
|
Singleton(Singleton&& other) = delete;
|
||||||
|
Singleton& operator=(const Singleton& other) = delete;
|
||||||
|
Singleton& operator=(Singleton&& other) = delete;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Singleton() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SINGLETON_H
|
||||||
115
destrum/src/App.cpp
Normal file
115
destrum/src/App.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <destrum/App.h>
|
||||||
|
|
||||||
|
#include <destrum/FS/AssetFS.h>
|
||||||
|
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
void App::init(const AppParams& params) {
|
||||||
|
m_params = params;
|
||||||
|
|
||||||
|
AssetFS::GetInstance().Init(params.exeDir);
|
||||||
|
// AssetFS::GetInstance().Mount("engine", params.exeDir / "assets" / "engine");
|
||||||
|
// AssetFS::GetInstance().Mount("game", params.exeDir / "assets" / "game");
|
||||||
|
|
||||||
|
window = SDL_CreateWindow(
|
||||||
|
params.windowTitle.c_str(),
|
||||||
|
// pos
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
// size
|
||||||
|
params.windowSize.x,
|
||||||
|
params.windowSize.y,
|
||||||
|
SDL_WINDOW_VULKAN);
|
||||||
|
|
||||||
|
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||||
|
if (!window) {
|
||||||
|
spdlog::error("Failed to create window. SDL Error: {}", SDL_GetError());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfxDevice.init(window, params.appName, false);
|
||||||
|
|
||||||
|
//Read whole file
|
||||||
|
auto file = AssetFS::GetInstance().ReadBytes("engine://assetfstest.txt");
|
||||||
|
std::string fileStr(file.begin(), file.end());
|
||||||
|
spdlog::info("Read from assetfstest.txt: {}", fileStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::run() {
|
||||||
|
const float FPS = 30.f;
|
||||||
|
const float dt = 1.f / FPS;
|
||||||
|
|
||||||
|
auto prevTime = std::chrono::high_resolution_clock::now();
|
||||||
|
float accumulator = dt; // so that we get at least 1 update before render
|
||||||
|
|
||||||
|
isRunning = true;
|
||||||
|
while (isRunning) {
|
||||||
|
const auto newTime = std::chrono::high_resolution_clock::now();
|
||||||
|
frameTime = std::chrono::duration<float>(newTime - prevTime).count();
|
||||||
|
|
||||||
|
if (frameTime > 0.07f && frameTime < 5.f) {
|
||||||
|
// if >=5.f - debugging?
|
||||||
|
spdlog::warn("Frame drop detected, time: {:.4f}s", frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator += frameTime;
|
||||||
|
prevTime = newTime;
|
||||||
|
|
||||||
|
float newFPS = 1.f / frameTime;
|
||||||
|
if (newFPS == std::numeric_limits<float>::infinity()) {
|
||||||
|
// can happen when frameTime == 0
|
||||||
|
newFPS = 0;
|
||||||
|
}
|
||||||
|
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
|
||||||
|
|
||||||
|
if (accumulator > 10 * dt) {
|
||||||
|
// game stopped for debug
|
||||||
|
accumulator = dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (accumulator >= dt) {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) {
|
||||||
|
isRunning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_WINDOWEVENT) {
|
||||||
|
switch (event.window.event) {
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
/* fallthrough */
|
||||||
|
case SDL_WINDOWEVENT_RESIZED:
|
||||||
|
m_params.windowSize = {event.window.data1, event.window.data2};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gfxDevice.needsSwapchainRecreate()) {
|
||||||
|
spdlog::info("Recreating swapchain to size: {}x{}", m_params.windowSize.x, m_params.windowSize.y);
|
||||||
|
gfxDevice.recreateSwapchain(m_params.windowSize.x, m_params.windowSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator -= dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gfxDevice.needsSwapchainRecreate()) {
|
||||||
|
auto cmd = gfxDevice.beginFrame();
|
||||||
|
gfxDevice.endFrame(cmd, VImage{}, {});
|
||||||
|
}
|
||||||
|
if (frameLimit) {
|
||||||
|
// Delay to not overload the CPU
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
const auto frameTime = std::chrono::duration<float>(now - prevTime).count();
|
||||||
|
if (dt > frameTime) {
|
||||||
|
SDL_Delay(static_cast<std::uint32_t>(dt - frameTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::cleanup() {
|
||||||
|
}
|
||||||
68
destrum/src/FS/AssetFS.cpp
Normal file
68
destrum/src/FS/AssetFS.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <fstream>
|
||||||
|
#include <destrum/FS/AssetFS.h>
|
||||||
|
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
void AssetFS::Init(std::filesystem::path exeDir) {
|
||||||
|
Mount("engine", exeDir / "assets/engine");
|
||||||
|
Mount("game", exeDir / "assets/game");
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetFS::Mount(std::string scheme, std::filesystem::path root) {
|
||||||
|
spdlog::debug("Mounting assetfs scheme '{}' to root '{}'", scheme, root.string());
|
||||||
|
mounts.push_back({std::move(scheme), std::move(root)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetFS::ReadBytes(std::string_view vpath) {
|
||||||
|
assert(initialized && "AssetFS not initialized");
|
||||||
|
// parse "engine://path/inside"
|
||||||
|
auto pos = vpath.find("://");
|
||||||
|
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||||
|
|
||||||
|
std::string scheme(vpath.substr(0, pos));
|
||||||
|
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||||
|
|
||||||
|
for (auto& m : mounts) {
|
||||||
|
if (m.scheme == scheme) {
|
||||||
|
auto full = m.root / rel;
|
||||||
|
return ReadFile(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw std::runtime_error("mount not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path AssetFS::GetFullPath(std::string_view vpath) const {
|
||||||
|
assert(initialized && "AssetFS not initialized");
|
||||||
|
// parse "engine://path/inside"
|
||||||
|
auto pos = vpath.find("://");
|
||||||
|
if (pos == std::string_view::npos) throw std::runtime_error("bad vpath");
|
||||||
|
|
||||||
|
std::string scheme(vpath.substr(0, pos));
|
||||||
|
std::filesystem::path rel(std::string(vpath.substr(pos + 3)));
|
||||||
|
|
||||||
|
for (auto& m : mounts) {
|
||||||
|
if (m.scheme == scheme) {
|
||||||
|
auto full = m.root / rel;
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw std::runtime_error("mount not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetFS::ReadFile(const std::filesystem::path& fullPath) {
|
||||||
|
std::ifstream file(fullPath, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
throw std::runtime_error("failed to open file: " + fullPath.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
std::vector<uint8_t> buffer(size);
|
||||||
|
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
|
||||||
|
throw std::runtime_error("failed to read file: " + fullPath.string());
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
236
destrum/src/graphics/GfxDevice.cpp
Normal file
236
destrum/src/graphics/GfxDevice.cpp
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
|
||||||
|
#include "destrum/Graphics/Util.h"
|
||||||
|
|
||||||
|
#define VOLK_IMPLEMENTATION
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
#define VMA_IMPLEMENTATION
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_vulkan.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Init.h>
|
||||||
|
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
GfxDevice::GfxDevice() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync) {
|
||||||
|
VK_CHECK(volkInitialize());
|
||||||
|
|
||||||
|
instance = vkb::InstanceBuilder{}
|
||||||
|
.set_app_name(appName.c_str())
|
||||||
|
.set_app_version(1, 0, 0)
|
||||||
|
.request_validation_layers()
|
||||||
|
.use_default_debug_messenger()
|
||||||
|
.require_api_version(1, 3, 0)
|
||||||
|
.build()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
volkLoadInstance(instance);
|
||||||
|
|
||||||
|
const auto res = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||||
|
if (res != SDL_TRUE) {
|
||||||
|
spdlog::error("Failed to create Vulkan surface: {}", SDL_GetError());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto deviceFeatures = VkPhysicalDeviceFeatures{
|
||||||
|
.imageCubeArray = VK_TRUE,
|
||||||
|
.geometryShader = VK_TRUE, // for im3d
|
||||||
|
.depthClamp = VK_TRUE,
|
||||||
|
.samplerAnisotropy = VK_TRUE,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto features12 = VkPhysicalDeviceVulkan12Features{
|
||||||
|
.descriptorIndexing = true,
|
||||||
|
.descriptorBindingSampledImageUpdateAfterBind = true,
|
||||||
|
.descriptorBindingStorageImageUpdateAfterBind = true,
|
||||||
|
.descriptorBindingPartiallyBound = true,
|
||||||
|
.descriptorBindingVariableDescriptorCount = true,
|
||||||
|
.runtimeDescriptorArray = true,
|
||||||
|
.scalarBlockLayout = true,
|
||||||
|
.bufferDeviceAddress = true,
|
||||||
|
};
|
||||||
|
constexpr auto features13 = VkPhysicalDeviceVulkan13Features{
|
||||||
|
.synchronization2 = true,
|
||||||
|
.dynamicRendering = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
physicalDevice = vkb::PhysicalDeviceSelector{instance}
|
||||||
|
.set_minimum_version(1, 3)
|
||||||
|
.set_required_features(deviceFeatures)
|
||||||
|
.set_required_features_12(features12)
|
||||||
|
.set_required_features_13(features13)
|
||||||
|
.set_surface(surface)
|
||||||
|
.select()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
device = vkb::DeviceBuilder{physicalDevice}.build().value();
|
||||||
|
|
||||||
|
graphicsQueueFamily = device.get_queue_index(vkb::QueueType::graphics).value();
|
||||||
|
graphicsQueue = device.get_queue(vkb::QueueType::graphics).value();
|
||||||
|
|
||||||
|
//Vma
|
||||||
|
const auto vulkanFunctions = VmaVulkanFunctions{
|
||||||
|
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||||
|
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto allocatorInfo = VmaAllocatorCreateInfo{
|
||||||
|
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
||||||
|
.physicalDevice = physicalDevice,
|
||||||
|
.device = device,
|
||||||
|
.pVulkanFunctions = &vulkanFunctions,
|
||||||
|
.instance = instance,
|
||||||
|
};
|
||||||
|
vmaCreateAllocator(&allocatorInfo, &allocator);
|
||||||
|
|
||||||
|
executor.init(device, graphicsQueueFamily, graphicsQueue);
|
||||||
|
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
SDL_GetWindowSize(window, &w, &h);
|
||||||
|
swapchainFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
||||||
|
swapchain.createSwapchain(this, swapchainFormat, w, h, vSync);
|
||||||
|
|
||||||
|
swapchain.initSync(device);
|
||||||
|
|
||||||
|
const auto poolCreateInfo = vkinit::commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||||
|
|
||||||
|
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||||
|
auto& commandPool = frames[i].commandPool;
|
||||||
|
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool));
|
||||||
|
|
||||||
|
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||||
|
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||||
|
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// { // create white texture
|
||||||
|
// std::uint32_t pixel = 0xFFFFFFFF;
|
||||||
|
// whiteImageId = createImage(
|
||||||
|
// {
|
||||||
|
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||||
|
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||||
|
// .extent = VkExtent3D{1, 1, 1},
|
||||||
|
// },
|
||||||
|
// "white texture",
|
||||||
|
// &pixel);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::recreateSwapchain(int width, int height) {
|
||||||
|
assert(width != 0 && height != 0);
|
||||||
|
waitIdle();
|
||||||
|
swapchain.recreateSwapchain(*this, swapchainFormat, width, height, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBuffer GfxDevice::beginFrame() {
|
||||||
|
swapchain.beginFrame(getCurrentFrameIndex());
|
||||||
|
|
||||||
|
const auto& frame = getCurrentFrame();
|
||||||
|
const auto& cmd = frame.commandBuffer;
|
||||||
|
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||||
|
};
|
||||||
|
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::endFrame(VkCommandBuffer cmd, const VImage& drawImage, const EndFrameProps& props) {
|
||||||
|
// get swapchain image
|
||||||
|
const auto [swapchainImage, swapchainImageIndex] = swapchain.acquireNextImage(getCurrentFrameIndex());
|
||||||
|
if (swapchainImage == VK_NULL_HANDLE) {
|
||||||
|
std::printf("Swapchain is dirty, skipping frame...\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fences are reset here to prevent the deadlock in case swapchain becomes dirty
|
||||||
|
swapchain.resetFences(getCurrentFrameIndex());
|
||||||
|
|
||||||
|
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
|
||||||
|
{
|
||||||
|
// clear swapchain image
|
||||||
|
VkImageSubresourceRange clearRange =
|
||||||
|
vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||||
|
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
|
swapchainLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||||
|
|
||||||
|
VkClearColorValue clearValue;
|
||||||
|
static int superFrameNumber = 0;
|
||||||
|
superFrameNumber += 1;
|
||||||
|
if (superFrameNumber > 255) {
|
||||||
|
superFrameNumber = 0;
|
||||||
|
}
|
||||||
|
float flash = std::abs(std::sin(superFrameNumber * 0.1f));
|
||||||
|
clearValue = { { 0.0f, 0.0f, flash, 1.0f } };
|
||||||
|
|
||||||
|
// const auto clearValue = props.clearColor;
|
||||||
|
vkCmdClearColorImage(cmd, swapchainImage, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (props.copyImageIntoSwapchain) {
|
||||||
|
// copy from draw image into swapchain
|
||||||
|
// vkutil::transitionImage(
|
||||||
|
// cmd,
|
||||||
|
// drawImage.getImage(),
|
||||||
|
// VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
|
||||||
|
// VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||||
|
vkutil::transitionImage(
|
||||||
|
cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||||
|
swapchainLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
|
||||||
|
// const auto filter = props.drawImageLinearBlit ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||||
|
const auto filter = VK_FILTER_LINEAR;
|
||||||
|
// // if (props.drawImageBlitRect != glm::ivec4{}) {
|
||||||
|
// vkutil::copyImageToImage(
|
||||||
|
// cmd,
|
||||||
|
// drawImage.getImage(),
|
||||||
|
// swapchainImage,
|
||||||
|
// drawImage.getExtent2D(),
|
||||||
|
// props.drawImageBlitRect.x,
|
||||||
|
// props.drawImageBlitRect.y,
|
||||||
|
// props.drawImageBlitRect.z,
|
||||||
|
// props.drawImageBlitRect.w,
|
||||||
|
// filter);
|
||||||
|
// } else {
|
||||||
|
// // will stretch image to swapchain
|
||||||
|
// vkutil::copyImageToImage(
|
||||||
|
// cmd,
|
||||||
|
// drawImage.getImage(),
|
||||||
|
// swapchainImage,
|
||||||
|
// drawImage.getExtent2D(),
|
||||||
|
// getSwapchainExtent(),
|
||||||
|
// filter);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// prepare for present
|
||||||
|
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
|
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
|
||||||
|
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||||
|
|
||||||
|
// swapchain.submitAndPresent(cmd, graphicsQueue, getCurrentFrameIndex(), swapchainImageIndex);
|
||||||
|
swapchain.submitAndPresent(cmd, graphicsQueue, swapchainImageIndex, getCurrentFrameIndex());
|
||||||
|
|
||||||
|
frameNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::cleanup() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::waitIdle() {
|
||||||
|
VK_CHECK(vkDeviceWaitIdle(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxDevice::immediateSubmit(ImmediateExecuteFunction&& f) const {
|
||||||
|
executor.immediateSubmit(std::move(f));
|
||||||
|
}
|
||||||
72
destrum/src/graphics/ImmediateExecuter.cpp
Normal file
72
destrum/src/graphics/ImmediateExecuter.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
#include <destrum/Graphics/ImmediateExecuter.h>
|
||||||
|
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Init.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr auto NO_TIMEOUT = std::numeric_limits<std::uint64_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanImmediateExecutor::init(
|
||||||
|
VkDevice device,
|
||||||
|
std::uint32_t graphicsQueueFamily,
|
||||||
|
VkQueue graphicsQueue)
|
||||||
|
{
|
||||||
|
assert(!initialized);
|
||||||
|
|
||||||
|
this->device = device;
|
||||||
|
this->graphicsQueue = graphicsQueue;
|
||||||
|
|
||||||
|
const auto poolCreateInfo = vkinit::
|
||||||
|
commandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, graphicsQueueFamily);
|
||||||
|
VK_CHECK(vkCreateCommandPool(device, &poolCreateInfo, nullptr, &immCommandPool));
|
||||||
|
|
||||||
|
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(immCommandPool, 1);
|
||||||
|
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &immCommandBuffer));
|
||||||
|
|
||||||
|
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||||
|
};
|
||||||
|
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &immFence));
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanImmediateExecutor::cleanup(VkDevice device)
|
||||||
|
{
|
||||||
|
assert(initialized);
|
||||||
|
vkDestroyCommandPool(device, immCommandPool, nullptr);
|
||||||
|
vkDestroyFence(device, immFence, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanImmediateExecutor::immediateSubmit(
|
||||||
|
std::function<void(VkCommandBuffer cmd)>&& function) const
|
||||||
|
{
|
||||||
|
assert(initialized);
|
||||||
|
VK_CHECK(vkResetFences(device, 1, &immFence));
|
||||||
|
VK_CHECK(vkResetCommandBuffer(immCommandBuffer, 0));
|
||||||
|
|
||||||
|
auto cmd = immCommandBuffer;
|
||||||
|
const auto cmdBeginInfo = VkCommandBufferBeginInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
||||||
|
function(cmd);
|
||||||
|
VK_CHECK(vkEndCommandBuffer(cmd));
|
||||||
|
|
||||||
|
const auto cmdinfo = vkinit::commandBufferSubmitInfo(cmd);
|
||||||
|
const auto submit = vkinit::submitInfo(&cmdinfo, nullptr, nullptr);
|
||||||
|
|
||||||
|
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, immFence));
|
||||||
|
|
||||||
|
VK_CHECK(vkWaitForFences(device, 1, &immFence, true, NO_TIMEOUT));
|
||||||
|
}
|
||||||
182
destrum/src/graphics/Init.cpp
Normal file
182
destrum/src/graphics/Init.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include <destrum/Graphics/Init.h>
|
||||||
|
|
||||||
|
namespace vkinit
|
||||||
|
{
|
||||||
|
|
||||||
|
VkImageSubresourceRange imageSubresourceRange(VkImageAspectFlags aspectMask)
|
||||||
|
{
|
||||||
|
return VkImageSubresourceRange{
|
||||||
|
.aspectMask = aspectMask,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSemaphoreSubmitInfo semaphoreSubmitInfo(VkPipelineStageFlags2 stageMask, VkSemaphore semaphore)
|
||||||
|
{
|
||||||
|
return VkSemaphoreSubmitInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
|
||||||
|
.semaphore = semaphore,
|
||||||
|
.value = 1,
|
||||||
|
.stageMask = stageMask,
|
||||||
|
.deviceIndex = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSubmitInfo2 submitInfo(
|
||||||
|
const VkCommandBufferSubmitInfo* cmd,
|
||||||
|
const VkSemaphoreSubmitInfo* waitSemaphoreInfo,
|
||||||
|
const VkSemaphoreSubmitInfo* signalSemaphoreInfo)
|
||||||
|
{
|
||||||
|
return VkSubmitInfo2{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
|
||||||
|
.waitSemaphoreInfoCount = waitSemaphoreInfo ? 1u : 0u,
|
||||||
|
.pWaitSemaphoreInfos = waitSemaphoreInfo,
|
||||||
|
.commandBufferInfoCount = 1,
|
||||||
|
.pCommandBufferInfos = cmd,
|
||||||
|
.signalSemaphoreInfoCount = signalSemaphoreInfo ? 1u : 0u,
|
||||||
|
.pSignalSemaphoreInfos = signalSemaphoreInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandPoolCreateInfo commandPoolCreateInfo(
|
||||||
|
VkCommandPoolCreateFlags flags,
|
||||||
|
std::uint32_t queueFamilyIndex)
|
||||||
|
{
|
||||||
|
return VkCommandPoolCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||||
|
.flags = flags,
|
||||||
|
.queueFamilyIndex = queueFamilyIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo commandBufferAllocateInfo(
|
||||||
|
VkCommandPool commandPool,
|
||||||
|
std::uint32_t commandBufferCount)
|
||||||
|
{
|
||||||
|
return VkCommandBufferAllocateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||||
|
.commandPool = commandPool,
|
||||||
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||||
|
.commandBufferCount = commandBufferCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBufferSubmitInfo commandBufferSubmitInfo(VkCommandBuffer cmd)
|
||||||
|
{
|
||||||
|
return VkCommandBufferSubmitInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
|
||||||
|
.commandBuffer = cmd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageCreateInfo imageCreateInfo(
|
||||||
|
VkFormat format,
|
||||||
|
VkImageUsageFlags usageFlags,
|
||||||
|
VkExtent3D extent,
|
||||||
|
std::uint32_t mipLevels)
|
||||||
|
{
|
||||||
|
return VkImageCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
|
.imageType = VK_IMAGE_TYPE_2D,
|
||||||
|
.format = format,
|
||||||
|
.extent = extent,
|
||||||
|
.mipLevels = mipLevels,
|
||||||
|
.arrayLayers = 1,
|
||||||
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||||
|
.usage = usageFlags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkImageViewCreateInfo imageViewCreateInfo(
|
||||||
|
VkFormat format,
|
||||||
|
VkImage image,
|
||||||
|
VkImageAspectFlags aspectFlags)
|
||||||
|
{
|
||||||
|
return VkImageViewCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
|
.image = image,
|
||||||
|
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||||
|
.format = format,
|
||||||
|
.subresourceRange =
|
||||||
|
VkImageSubresourceRange{
|
||||||
|
.aspectMask = aspectFlags,
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = 1,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkRenderingAttachmentInfo attachmentInfo(
|
||||||
|
VkImageView view,
|
||||||
|
VkImageLayout layout,
|
||||||
|
std::optional<VkClearValue> clearValue)
|
||||||
|
{
|
||||||
|
return VkRenderingAttachmentInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||||
|
.imageView = view,
|
||||||
|
.imageLayout = layout,
|
||||||
|
.loadOp = clearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||||
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||||
|
.clearValue = clearValue ? clearValue.value() : VkClearValue{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkRenderingAttachmentInfo depthAttachmentInfo(
|
||||||
|
VkImageView view,
|
||||||
|
VkImageLayout layout,
|
||||||
|
std::optional<float> depthClearValue)
|
||||||
|
{
|
||||||
|
return VkRenderingAttachmentInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||||
|
.imageView = view,
|
||||||
|
.imageLayout = layout,
|
||||||
|
.loadOp = depthClearValue ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||||
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||||
|
.clearValue =
|
||||||
|
{
|
||||||
|
.depthStencil =
|
||||||
|
{
|
||||||
|
.depth = depthClearValue ? depthClearValue.value() : 0.f,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkRenderingInfo renderingInfo(
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const VkRenderingAttachmentInfo* colorAttachment,
|
||||||
|
const VkRenderingAttachmentInfo* depthAttachment)
|
||||||
|
{
|
||||||
|
return VkRenderingInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
|
||||||
|
.renderArea =
|
||||||
|
VkRect2D{
|
||||||
|
.offset = {},
|
||||||
|
.extent = renderExtent,
|
||||||
|
},
|
||||||
|
.layerCount = 1,
|
||||||
|
.colorAttachmentCount = colorAttachment ? 1u : 0u,
|
||||||
|
.pColorAttachments = colorAttachment,
|
||||||
|
.pDepthAttachment = depthAttachment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo pipelineShaderStageCreateInfo(
|
||||||
|
VkShaderStageFlagBits stage,
|
||||||
|
VkShaderModule shaderModule)
|
||||||
|
{
|
||||||
|
return VkPipelineShaderStageCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.stage = stage,
|
||||||
|
.module = shaderModule,
|
||||||
|
.pName = "main",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of namespace vkinit
|
||||||
232
destrum/src/graphics/Pipeline.cpp
Normal file
232
destrum/src/graphics/Pipeline.cpp
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
#include <destrum/Graphics/Pipeline.h>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "destrum/Graphics/Util.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
|
||||||
|
Pipeline::Pipeline(GfxDevice& device, const std::string& vertPath, const std::string& fragPath,
|
||||||
|
const PipelineConfigInfo& configInfo): m_device(device) {
|
||||||
|
CreateGraphicsPipeline(vertPath, fragPath, configInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pipeline::~Pipeline() {
|
||||||
|
vkDestroyShaderModule(m_device.getDevice(), m_vertShaderModule, nullptr);
|
||||||
|
vkDestroyShaderModule(m_device.getDevice(), m_fragShaderModule, nullptr);
|
||||||
|
vkDestroyPipeline(m_device.getDevice(), m_graphicsPipeline, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::bind(VkCommandBuffer buffer) const {
|
||||||
|
vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::DefaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
|
||||||
|
configInfo.name = "DefaultPipelineConfigInfo";
|
||||||
|
|
||||||
|
configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||||
|
configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
|
||||||
|
|
||||||
|
configInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
configInfo.viewportInfo.viewportCount = 1;
|
||||||
|
configInfo.viewportInfo.pViewports = nullptr;
|
||||||
|
configInfo.viewportInfo.scissorCount = 1;
|
||||||
|
configInfo.viewportInfo.pScissors = nullptr;
|
||||||
|
|
||||||
|
configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||||
|
configInfo.rasterizationInfo.depthClampEnable = VK_FALSE;
|
||||||
|
configInfo.rasterizationInfo.rasterizerDiscardEnable = VK_FALSE;
|
||||||
|
configInfo.rasterizationInfo.polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
|
configInfo.rasterizationInfo.lineWidth = 1.0f;
|
||||||
|
configInfo.rasterizationInfo.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||||
|
configInfo.rasterizationInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||||
|
configInfo.rasterizationInfo.depthBiasEnable = VK_FALSE;
|
||||||
|
configInfo.rasterizationInfo.depthBiasConstantFactor = 0.0f; // Optional
|
||||||
|
configInfo.rasterizationInfo.depthBiasClamp = 0.0f; // Optional
|
||||||
|
configInfo.rasterizationInfo.depthBiasSlopeFactor = 0.0f; // Optional
|
||||||
|
|
||||||
|
configInfo.multisampleInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||||
|
configInfo.multisampleInfo.sampleShadingEnable = VK_FALSE;
|
||||||
|
configInfo.multisampleInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
configInfo.multisampleInfo.minSampleShading = 1.0f; // Optional
|
||||||
|
configInfo.multisampleInfo.pSampleMask = nullptr; // Optional
|
||||||
|
configInfo.multisampleInfo.alphaToCoverageEnable = VK_FALSE; // Optional
|
||||||
|
configInfo.multisampleInfo.alphaToOneEnable = VK_FALSE; // Optional
|
||||||
|
|
||||||
|
configInfo.colorBlendAttachment.colorWriteMask =
|
||||||
|
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
|
||||||
|
VK_COLOR_COMPONENT_A_BIT;
|
||||||
|
configInfo.colorBlendAttachment.blendEnable = VK_FALSE;
|
||||||
|
configInfo.colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
|
||||||
|
configInfo.colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
|
||||||
|
configInfo.colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
|
||||||
|
configInfo.colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
|
||||||
|
configInfo.colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
|
||||||
|
configInfo.colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional
|
||||||
|
|
||||||
|
configInfo.colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||||
|
configInfo.colorBlendInfo.logicOpEnable = VK_FALSE;
|
||||||
|
configInfo.colorBlendInfo.logicOp = VK_LOGIC_OP_COPY; // Optional
|
||||||
|
configInfo.colorBlendInfo.attachmentCount = 1;
|
||||||
|
configInfo.colorBlendInfo.pAttachments = &configInfo.colorBlendAttachment;
|
||||||
|
configInfo.colorBlendInfo.blendConstants[0] = 0.0f; // Optional
|
||||||
|
configInfo.colorBlendInfo.blendConstants[1] = 0.0f; // Optional
|
||||||
|
configInfo.colorBlendInfo.blendConstants[2] = 0.0f; // Optional
|
||||||
|
configInfo.colorBlendInfo.blendConstants[3] = 0.0f; // Optional
|
||||||
|
|
||||||
|
configInfo.depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||||
|
configInfo.depthStencilInfo.depthTestEnable = VK_TRUE;
|
||||||
|
configInfo.depthStencilInfo.depthWriteEnable = VK_TRUE;
|
||||||
|
configInfo.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
|
||||||
|
configInfo.depthStencilInfo.depthBoundsTestEnable = VK_FALSE;
|
||||||
|
configInfo.depthStencilInfo.minDepthBounds = 0.0f; // Optional
|
||||||
|
configInfo.depthStencilInfo.maxDepthBounds = 1.0f; // Optional
|
||||||
|
configInfo.depthStencilInfo.stencilTestEnable = VK_FALSE;
|
||||||
|
configInfo.depthStencilInfo.front = {}; // Optional
|
||||||
|
configInfo.depthStencilInfo.back = {}; // Optional
|
||||||
|
|
||||||
|
configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||||
|
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||||
|
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
|
||||||
|
configInfo.dynamicStateInfo.dynamicStateCount = static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
|
||||||
|
configInfo.dynamicStateInfo.flags = 0;
|
||||||
|
|
||||||
|
configInfo.colorAttachments = {VK_FORMAT_B8G8R8A8_SRGB};
|
||||||
|
configInfo.depthAttachment = VK_FORMAT_D32_SFLOAT;
|
||||||
|
|
||||||
|
// configInfo.vertexAttributeDescriptions = std::move(Mesh::Vertex::getAttributeDescriptions());
|
||||||
|
// configInfo.vertexBindingDescriptions = std::move(Mesh::Vertex::getBindingDescriptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> Pipeline::readFile(const std::string& filename) {
|
||||||
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t fileSize = (size_t)file.tellg();
|
||||||
|
std::vector<char> buffer(fileSize);
|
||||||
|
|
||||||
|
file.seekg(0);
|
||||||
|
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::string& fragPath,
|
||||||
|
const PipelineConfigInfo& configInfo) {
|
||||||
|
assert(configInfo.pipelineLayout != VK_NULL_HANDLE && "no pipelineLayout provided in configInfo");
|
||||||
|
|
||||||
|
std::vector < VkPipelineShaderStageCreateInfo > shaderStages;
|
||||||
|
|
||||||
|
if (!vertPath.empty()) {
|
||||||
|
std::string VertFileName = std::filesystem::path(vertPath).filename().string();
|
||||||
|
auto vertCode = readFile(vertPath);
|
||||||
|
spdlog::debug("Vertex shader code size: {}", vertCode.size());
|
||||||
|
spdlog::debug("Vertex shader file: {}", VertFileName);
|
||||||
|
|
||||||
|
CreateShaderModule(vertCode, &m_vertShaderModule);
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
|
||||||
|
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||||
|
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
vertShaderStageInfo.module = m_vertShaderModule;
|
||||||
|
vertShaderStageInfo.pName = "main";
|
||||||
|
vertShaderStageInfo.flags = 0;
|
||||||
|
vertShaderStageInfo.pSpecializationInfo = nullptr;
|
||||||
|
vertShaderStageInfo.pNext = nullptr;
|
||||||
|
|
||||||
|
shaderStages.push_back(vertShaderStageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fragPath.empty()) {
|
||||||
|
std::string FragFileName = std::filesystem::path(fragPath).filename().string();
|
||||||
|
auto fragCode = readFile(fragPath);
|
||||||
|
|
||||||
|
spdlog::debug("Fragment shader code size: {}", fragCode.size());
|
||||||
|
spdlog::debug("Fragment shader file: {}", FragFileName);
|
||||||
|
|
||||||
|
CreateShaderModule(fragCode, &m_fragShaderModule);
|
||||||
|
|
||||||
|
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
|
||||||
|
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||||
|
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
fragShaderStageInfo.module = m_fragShaderModule;
|
||||||
|
fragShaderStageInfo.pName = "main";
|
||||||
|
fragShaderStageInfo.flags = 0;
|
||||||
|
fragShaderStageInfo.pSpecializationInfo = nullptr;
|
||||||
|
fragShaderStageInfo.pNext = nullptr;
|
||||||
|
|
||||||
|
shaderStages.push_back(fragShaderStageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
|
||||||
|
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||||
|
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(configInfo.vertexAttributeDescriptions.size());
|
||||||
|
vertexInputInfo.pVertexAttributeDescriptions = configInfo.vertexAttributeDescriptions.data();
|
||||||
|
vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(configInfo.vertexBindingDescriptions.size());
|
||||||
|
vertexInputInfo.pVertexBindingDescriptions = configInfo.vertexBindingDescriptions.data();
|
||||||
|
|
||||||
|
|
||||||
|
VkGraphicsPipelineCreateInfo pipelineInfo{};
|
||||||
|
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||||
|
pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
|
||||||
|
pipelineInfo.pStages = shaderStages.data();
|
||||||
|
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
||||||
|
pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
|
||||||
|
pipelineInfo.pViewportState = &configInfo.viewportInfo;
|
||||||
|
pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
|
||||||
|
pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
|
||||||
|
pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo;
|
||||||
|
pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;
|
||||||
|
pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;
|
||||||
|
|
||||||
|
VkPipelineRenderingCreateInfoKHR renderingInfo{};
|
||||||
|
renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
|
||||||
|
renderingInfo.colorAttachmentCount = static_cast<uint32_t>(configInfo.colorAttachments.size());
|
||||||
|
renderingInfo.pColorAttachmentFormats = configInfo.colorAttachments.data();
|
||||||
|
renderingInfo.depthAttachmentFormat = configInfo.depthAttachment;
|
||||||
|
|
||||||
|
pipelineInfo.pNext = &renderingInfo;
|
||||||
|
|
||||||
|
pipelineInfo.layout = configInfo.pipelineLayout;
|
||||||
|
pipelineInfo.renderPass = nullptr;
|
||||||
|
// pipelineInfo.subpass = nullptr;
|
||||||
|
|
||||||
|
pipelineInfo.basePipelineIndex = -1;
|
||||||
|
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
if (!configInfo.name.empty()) {
|
||||||
|
// DebugLabel::SetObjectName(
|
||||||
|
// reinterpret_cast<uint64_t>(pipelineInfo.layout),
|
||||||
|
// VK_OBJECT_TYPE_PIPELINE_LAYOUT,
|
||||||
|
// configInfo.name.c_str()
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (vkCreateGraphicsPipelines(m_device.getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_graphicsPipeline) != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("Can't make pipeline!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pipeline::CreateShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) const {
|
||||||
|
VkShaderModuleCreateInfo createInfo{};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||||
|
createInfo.codeSize = code.size();
|
||||||
|
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||||
|
|
||||||
|
if (vkCreateShaderModule(m_device.getDevice(), &createInfo, nullptr, shaderModule) != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("Failed to create shader module!");
|
||||||
|
}
|
||||||
|
}
|
||||||
0
destrum/src/graphics/Renderer.cpp
Normal file
0
destrum/src/graphics/Renderer.cpp
Normal file
189
destrum/src/graphics/Swapchain.cpp
Normal file
189
destrum/src/graphics/Swapchain.cpp
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Swapchain.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
#include <destrum/Graphics/Init.h>
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vulkan/vk_enum_string_helper.h>
|
||||||
|
#include "volk.h"
|
||||||
|
|
||||||
|
|
||||||
|
void Swapchain::initSync(VkDevice device)
|
||||||
|
{
|
||||||
|
const auto fenceCreateInfo = VkFenceCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||||
|
};
|
||||||
|
const auto semaphoreCreateInfo = VkSemaphoreCreateInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||||
|
};
|
||||||
|
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||||
|
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &frames[i].renderFence));
|
||||||
|
VK_CHECK(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &frames[i].swapchainSemaphore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) {
|
||||||
|
m_gfxDevice = gfxDevice;
|
||||||
|
assert(format == VK_FORMAT_B8G8R8A8_SRGB && "TODO: test other formats");
|
||||||
|
vSync = true;
|
||||||
|
|
||||||
|
auto res = vkb::SwapchainBuilder{gfxDevice->getDevice()}
|
||||||
|
.set_desired_format(VkSurfaceFormatKHR{
|
||||||
|
.format = format,
|
||||||
|
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||||
|
})
|
||||||
|
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
|
||||||
|
.set_desired_present_mode(
|
||||||
|
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||||
|
.set_desired_extent(width, height)
|
||||||
|
.build();
|
||||||
|
if (!res.has_value()) {
|
||||||
|
throw std::runtime_error(std::format(
|
||||||
|
"failed to create swapchain: error = {}, vk result = {}",
|
||||||
|
res.full_error().type.message(),
|
||||||
|
string_VkResult(res.full_error().vk_result)));
|
||||||
|
}
|
||||||
|
m_swapchain = res.value();
|
||||||
|
|
||||||
|
images = m_swapchain.get_images().value();
|
||||||
|
imageViews = m_swapchain.get_image_views().value();
|
||||||
|
|
||||||
|
imageRenderSemaphores.resize(images.size());
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo sci{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& sem : imageRenderSemaphores) {
|
||||||
|
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: if re-creation of swapchain is supported, don't forget to call
|
||||||
|
// vkutil::initSwapchainViews here.
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::recreateSwapchain(const GfxDevice& gfxDevice, VkFormat format, std::uint32_t width, std::uint32_t height, bool vSync) {
|
||||||
|
assert(m_swapchain);
|
||||||
|
|
||||||
|
assert(format == VK_FORMAT_B8G8R8A8_SRGB && "TODO: test other formats");
|
||||||
|
auto res = vkb::SwapchainBuilder{gfxDevice.getDevice()}
|
||||||
|
.set_old_swapchain(m_swapchain)
|
||||||
|
.set_desired_format(VkSurfaceFormatKHR{
|
||||||
|
.format = format,
|
||||||
|
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||||
|
})
|
||||||
|
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
|
||||||
|
.set_desired_present_mode(
|
||||||
|
vSync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||||
|
.set_desired_extent(width, height)
|
||||||
|
.build();
|
||||||
|
if (!res.has_value()) {
|
||||||
|
throw std::runtime_error(std::format(
|
||||||
|
"failed to create swapchain: error = {}, vk result = {}",
|
||||||
|
res.full_error().type.message(),
|
||||||
|
string_VkResult(res.full_error().vk_result)));
|
||||||
|
}
|
||||||
|
vkb::destroy_swapchain(m_swapchain);
|
||||||
|
|
||||||
|
for (auto imageView: imageViews) {
|
||||||
|
vkDestroyImageView(m_gfxDevice->getDevice(), imageView, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_swapchain = res.value();
|
||||||
|
|
||||||
|
images = m_swapchain.get_images().value();
|
||||||
|
imageViews = m_swapchain.get_image_views().value();
|
||||||
|
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::cleanup() {
|
||||||
|
for (auto& frame: frames) {
|
||||||
|
vkDestroyFence(m_gfxDevice->getDevice(), frame.renderFence, nullptr);
|
||||||
|
vkDestroySemaphore(m_gfxDevice->getDevice(), frame.swapchainSemaphore, nullptr);
|
||||||
|
} {
|
||||||
|
// destroy swapchain and its views
|
||||||
|
for (auto imageView: imageViews) {
|
||||||
|
vkDestroyImageView(m_gfxDevice->getDevice(), imageView, nullptr);
|
||||||
|
}
|
||||||
|
imageViews.clear();
|
||||||
|
|
||||||
|
vkb::destroy_swapchain(m_swapchain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::beginFrame(int index) const {
|
||||||
|
auto& frame = frames[index];
|
||||||
|
VK_CHECK(vkWaitForFences(m_gfxDevice->getDevice(), 1, &frame.renderFence, true, std::numeric_limits<std::uint64_t>::max()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::resetFences(int index) const {
|
||||||
|
auto& frame = frames[index];
|
||||||
|
VK_CHECK(vkResetFences(m_gfxDevice->getDevice(), 1, &frame.renderFence));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
|
||||||
|
std::uint32_t swapchainImageIndex{};
|
||||||
|
const auto result = vkAcquireNextImageKHR(
|
||||||
|
m_gfxDevice->getDevice(),
|
||||||
|
m_swapchain,
|
||||||
|
std::numeric_limits<std::uint64_t>::max(),
|
||||||
|
frames[index].swapchainSemaphore,
|
||||||
|
VK_NULL_HANDLE,
|
||||||
|
&swapchainImageIndex);
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||||
|
dirty = true;
|
||||||
|
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||||
|
} else if (result != VK_SUCCESS) {
|
||||||
|
throw std::runtime_error("failed to acquire swap chain image!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {images[swapchainImageIndex], swapchainImageIndex};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::submitAndPresent(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkQueue graphicsQueue,
|
||||||
|
uint32_t imageIndex, // from vkAcquireNextImageKHR
|
||||||
|
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
|
||||||
|
{
|
||||||
|
auto& frame = frames[frameIndex]; // ✅ per-frame
|
||||||
|
|
||||||
|
VkSemaphore renderFinished = imageRenderSemaphores[imageIndex]; // ✅ per-image
|
||||||
|
|
||||||
|
// submit
|
||||||
|
VkCommandBufferSubmitInfo cmdInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
|
||||||
|
.commandBuffer = cmd,
|
||||||
|
};
|
||||||
|
|
||||||
|
VkSemaphoreSubmitInfo waitInfo =
|
||||||
|
vkinit::semaphoreSubmitInfo(
|
||||||
|
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
|
||||||
|
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
|
||||||
|
|
||||||
|
VkSemaphoreSubmitInfo signalInfo =
|
||||||
|
vkinit::semaphoreSubmitInfo(
|
||||||
|
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
|
||||||
|
renderFinished); // ✅ signal semaphore (per-image)
|
||||||
|
|
||||||
|
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
|
||||||
|
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
|
||||||
|
|
||||||
|
// present
|
||||||
|
VkPresentInfoKHR presentInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitSemaphores = &renderFinished,
|
||||||
|
.swapchainCount = 1,
|
||||||
|
.pSwapchains = &m_swapchain.swapchain,
|
||||||
|
.pImageIndices = &imageIndex, // ✅ imageIndex, NOT frameIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
VkResult res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
|
||||||
|
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
|
||||||
|
else if (res != VK_SUCCESS) dirty = true;
|
||||||
|
}
|
||||||
79
destrum/src/graphics/Util.cpp
Normal file
79
destrum/src/graphics/Util.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include <destrum/Graphics/Init.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
void vkutil::transitionImage(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout) {
|
||||||
|
VkImageAspectFlags aspectMask =
|
||||||
|
(currentLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||||
|
newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ||
|
||||||
|
newLayout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL) ?
|
||||||
|
VK_IMAGE_ASPECT_DEPTH_BIT :
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
VkImageMemoryBarrier2 imageBarrier{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||||
|
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
|
||||||
|
.oldLayout = currentLayout,
|
||||||
|
.newLayout = newLayout,
|
||||||
|
.image = image,
|
||||||
|
.subresourceRange = vkinit::imageSubresourceRange(aspectMask),
|
||||||
|
};
|
||||||
|
|
||||||
|
VkDependencyInfo depInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||||
|
.imageMemoryBarrierCount = 1,
|
||||||
|
.pImageMemoryBarriers = &imageBarrier,
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier2(cmd, &depInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::copyImageToImage(VkCommandBuffer cmd, VkImage source, VkImage destination, VkExtent2D srcSize, VkExtent2D dstSize, VkFilter filter) {
|
||||||
|
copyImageToImage(cmd, source, destination, srcSize, 0, 0, dstSize.width, dstSize.height, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::copyImageToImage(VkCommandBuffer cmd, VkImage source, VkImage destination, VkExtent2D srcSize, int destX, int destY, int destW, int destH, VkFilter filter) {
|
||||||
|
const auto blitRegion = VkImageBlit2{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2,
|
||||||
|
.srcSubresource =
|
||||||
|
{
|
||||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
.mipLevel = 0,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = 1,
|
||||||
|
},
|
||||||
|
.srcOffsets =
|
||||||
|
{
|
||||||
|
{},
|
||||||
|
{(std::int32_t)srcSize.width, (std::int32_t)srcSize.height, 1},
|
||||||
|
},
|
||||||
|
.dstSubresource =
|
||||||
|
{
|
||||||
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
|
.mipLevel = 0,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = 1,
|
||||||
|
},
|
||||||
|
.dstOffsets =
|
||||||
|
{
|
||||||
|
{(std::int32_t)destX, (std::int32_t)destY},
|
||||||
|
{(std::int32_t)(destX + destW), (std::int32_t)(destY + destH), 1},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto blitInfo = VkBlitImageInfo2{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2,
|
||||||
|
.srcImage = source,
|
||||||
|
.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
|
.dstImage = destination,
|
||||||
|
.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
.regionCount = 1,
|
||||||
|
.pRegions = &blitRegion,
|
||||||
|
.filter = filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdBlitImage2(cmd, &blitInfo);
|
||||||
|
}
|
||||||
1
destrum/src/graphics/VImage.cpp
Normal file
1
destrum/src/graphics/VImage.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include <destrum/Graphics/VImage.h>
|
||||||
52
destrum/third_party/CMakeLists.txt
vendored
Normal file
52
destrum/third_party/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# glm
|
||||||
|
add_subdirectory(glm)
|
||||||
|
|
||||||
|
# stb
|
||||||
|
add_subdirectory(stb)
|
||||||
|
|
||||||
|
# SDL
|
||||||
|
if (NOT BUILD_SHARED_LIBS)
|
||||||
|
set(SDL_SHARED_ENABLED_BY_DEFAULT OFF CACHE BOOL "Don't build SDL as shared lib")
|
||||||
|
endif()
|
||||||
|
option(SDL_TEST "Build the SDL2_test library" OFF)
|
||||||
|
option(SDL_AUDIO_ENABLED_BY_DEFAULT "Enable the Audio subsystem" OFF)
|
||||||
|
add_subdirectory(sdl)
|
||||||
|
|
||||||
|
add_subdirectory(vk-bootstrap)
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_definitions(vk-bootstrap PRIVATE $<$<CONFIG:Debug>:_ITERATOR_DEBUG_LEVEL=1>)
|
||||||
|
endif()
|
||||||
|
if (BUILD_SHARED_LIBS)
|
||||||
|
set_target_properties(vk-bootstrap PROPERTIES
|
||||||
|
POSITION_INDEPENDENT_CODE ON
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# vma
|
||||||
|
add_subdirectory(vma)
|
||||||
|
# volk
|
||||||
|
add_subdirectory(volk)
|
||||||
|
|
||||||
|
# FreeType
|
||||||
|
option(FT_DISABLE_ZLIB
|
||||||
|
"Disable use of system zlib and use internal zlib library instead." ON)
|
||||||
|
option(FT_DISABLE_BZIP2
|
||||||
|
"Disable support of bzip2 compressed fonts." ON)
|
||||||
|
option(FT_DISABLE_BROTLI
|
||||||
|
"Disable support of compressed WOFF2 fonts." ON)
|
||||||
|
option(FT_DISABLE_HARFBUZZ
|
||||||
|
"Disable HarfBuzz (used for improving auto-hinting of OpenType fonts)." ON)
|
||||||
|
option(FT_DISABLE_PNG
|
||||||
|
"Disable support of PNG compressed OpenType embedded bitmaps." ON)
|
||||||
|
option(SKIP_INSTALL_ALL "Skip install all" ON)
|
||||||
|
option(FT_ENABLE_ERROR_STRINGS
|
||||||
|
"Enable support for meaningful error descriptions." ON)
|
||||||
|
add_subdirectory(freetype)
|
||||||
|
add_library(freetype::freetype ALIAS freetype)
|
||||||
|
|
||||||
|
# nlohmann_json
|
||||||
|
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||||
|
option(JSON_Install "Install CMake targets during install step." OFF)
|
||||||
|
add_subdirectory(json)
|
||||||
|
|
||||||
|
add_subdirectory(spdlog)
|
||||||
1
destrum/third_party/fmt
vendored
Submodule
1
destrum/third_party/fmt
vendored
Submodule
Submodule destrum/third_party/fmt added at 7ad8004d57
1
destrum/third_party/freetype
vendored
Submodule
1
destrum/third_party/freetype
vendored
Submodule
Submodule destrum/third_party/freetype added at 23b6cd27ff
1
destrum/third_party/glfw
vendored
Submodule
1
destrum/third_party/glfw
vendored
Submodule
Submodule destrum/third_party/glfw added at dbadda2683
1
destrum/third_party/glm
vendored
Submodule
1
destrum/third_party/glm
vendored
Submodule
Submodule destrum/third_party/glm added at 8f6213d379
1
destrum/third_party/json
vendored
Submodule
1
destrum/third_party/json
vendored
Submodule
Submodule destrum/third_party/json added at 30b28175e4
1
destrum/third_party/sdl
vendored
Submodule
1
destrum/third_party/sdl
vendored
Submodule
Submodule destrum/third_party/sdl added at 3eba0b6f8a
1
destrum/third_party/spdlog
vendored
Submodule
1
destrum/third_party/spdlog
vendored
Submodule
Submodule destrum/third_party/spdlog added at 32dd298dc2
3
destrum/third_party/stb/CMakeLists.txt
vendored
Normal file
3
destrum/third_party/stb/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
add_library(stb_image INTERFACE)
|
||||||
|
target_include_directories(stb_image INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
|
add_library(stb::image ALIAS stb_image)
|
||||||
7987
destrum/third_party/stb/include/stb_image.h
vendored
Normal file
7987
destrum/third_party/stb/include/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1724
destrum/third_party/stb/include/stb_image_write.h
vendored
Normal file
1724
destrum/third_party/stb/include/stb_image_write.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
destrum/third_party/vk-bootstrap
vendored
Submodule
1
destrum/third_party/vk-bootstrap
vendored
Submodule
Submodule destrum/third_party/vk-bootstrap added at 7028d6f652
1
destrum/third_party/vma
vendored
Submodule
1
destrum/third_party/vma
vendored
Submodule
Submodule destrum/third_party/vma added at e722e57c89
1
destrum/third_party/volk
vendored
Submodule
1
destrum/third_party/volk
vendored
Submodule
Submodule destrum/third_party/volk added at bf84ce9573
52
lightkeeper/CMakeLists.txt
Normal file
52
lightkeeper/CMakeLists.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
|
project(lightkeeper LANGUAGES CXX C)
|
||||||
|
|
||||||
|
set(GAME_SRC
|
||||||
|
src/main.cpp
|
||||||
|
|
||||||
|
src/Lightkeeper.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(lightkeeper ${GAME_SRC})
|
||||||
|
|
||||||
|
set_target_properties(lightkeeper PROPERTIES
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_EXTENSIONS OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
#target_add_extra_warnings(lightkeeper)
|
||||||
|
|
||||||
|
target_include_directories(lightkeeper PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
|
|
||||||
|
|
||||||
|
target_link_libraries(lightkeeper PRIVATE destrum::destrum)
|
||||||
|
|
||||||
|
#symlink_assets(lightkeeper)
|
||||||
|
|
||||||
|
set(LK_SHADER_SRC "${CMAKE_CURRENT_LIST_DIR}/assets_src/shaders")
|
||||||
|
set(LK_SHADER_OUT "${CMAKE_CURRENT_LIST_DIR}/assets_runtime/shaders")
|
||||||
|
|
||||||
|
include(../cmake/compile_shaders.cmake)
|
||||||
|
compile_glsl_to_spv(lightkeeper "${LK_SHADER_SRC}" "${LK_SHADER_OUT}" LK_SPV)
|
||||||
|
add_dependencies(lightkeeper lightkeeper_shaders)
|
||||||
|
|
||||||
|
set(ENGINE_ASSETS_SRC "${CMAKE_SOURCE_DIR}/destrum/assets_runtime")
|
||||||
|
set(GAME_ASSETS_SRC "${CMAKE_SOURCE_DIR}/lightkeeper/assets_runtime")
|
||||||
|
|
||||||
|
set(ENGINE_ASSETS_DST "$<TARGET_FILE_DIR:lightkeeper>/assets/engine")
|
||||||
|
set(GAME_ASSETS_DST "$<TARGET_FILE_DIR:lightkeeper>/assets/game")
|
||||||
|
|
||||||
|
add_custom_command(TARGET lightkeeper POST_BUILD
|
||||||
|
# ensure parent dir exists
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:lightkeeper>/assets"
|
||||||
|
|
||||||
|
# remove destinations if they already exist (dir OR symlink)
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${ENGINE_ASSETS_DST}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${GAME_ASSETS_DST}"
|
||||||
|
|
||||||
|
# create symlinks
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E create_symlink "${ENGINE_ASSETS_SRC}" "${ENGINE_ASSETS_DST}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E create_symlink "${GAME_ASSETS_SRC}" "${GAME_ASSETS_DST}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
0
lightkeeper/assets_src/temp
Normal file
0
lightkeeper/assets_src/temp
Normal file
13
lightkeeper/include/Lightkeeper.h
Normal file
13
lightkeeper/include/Lightkeeper.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef LIGHTKEEPER_H
|
||||||
|
#define LIGHTKEEPER_H
|
||||||
|
|
||||||
|
#include <destrum/App.h>
|
||||||
|
|
||||||
|
class LightKeeper : public App {
|
||||||
|
public:
|
||||||
|
LightKeeper();
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //LIGHTKEEPER_H
|
||||||
5
lightkeeper/src/Lightkeeper.cpp
Normal file
5
lightkeeper/src/Lightkeeper.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "Lightkeeper.h"
|
||||||
|
|
||||||
|
LightKeeper::LightKeeper() {
|
||||||
|
|
||||||
|
}
|
||||||
29
lightkeeper/src/main.cpp
Normal file
29
lightkeeper/src/main.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "Lightkeeper.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||||
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", SDL_GetError(), nullptr);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path exeDir = SDL_GetBasePath();
|
||||||
|
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
LightKeeper app;
|
||||||
|
app.init({
|
||||||
|
.windowSize = {800, 600},
|
||||||
|
.renderSize = {800, 600},
|
||||||
|
.appName = "Astro Engine",
|
||||||
|
.windowTitle = "Lightkeeper",
|
||||||
|
.exeDir = exeDir,
|
||||||
|
});
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
SDL_Quit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
213
lightkeeper/src/main12.cpp
Normal file
213
lightkeeper/src/main12.cpp
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_vulkan.h>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <VkBootstrap.h>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
const int MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
|
struct FrameData {
|
||||||
|
VkSemaphore imageAvailable;
|
||||||
|
VkSemaphore renderFinished;
|
||||||
|
VkFence inFlight;
|
||||||
|
VkCommandBuffer commandBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_Window* window = nullptr;
|
||||||
|
VkInstance instance;
|
||||||
|
vkb::PhysicalDevice physicalDevice;
|
||||||
|
VkDevice device;
|
||||||
|
vkb::Device vkbDevice;
|
||||||
|
VkQueue graphicsQueue;
|
||||||
|
VkQueue presentQueue;
|
||||||
|
VkSurfaceKHR surface;
|
||||||
|
VkSwapchainKHR swapchain;
|
||||||
|
std::vector<VkImage> swapchainImages;
|
||||||
|
std::vector<VkImageView> swapchainImageViews;
|
||||||
|
VkCommandPool commandPool;
|
||||||
|
FrameData frames[MAX_FRAMES_IN_FLIGHT];
|
||||||
|
std::vector<VkFence> imagesInFlight;
|
||||||
|
size_t currentFrame = 0;
|
||||||
|
VkFormat swapchainImageFormat;
|
||||||
|
VkExtent2D swapchainExtent;
|
||||||
|
|
||||||
|
// --- Helper: allocate command buffer ---
|
||||||
|
VkCommandBuffer allocateCommandBuffer(VkDevice device, VkCommandPool pool) {
|
||||||
|
VkCommandBufferAllocateInfo allocInfo{};
|
||||||
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
|
allocInfo.commandPool = pool;
|
||||||
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
allocInfo.commandBufferCount = 1;
|
||||||
|
|
||||||
|
VkCommandBuffer cmd;
|
||||||
|
vkAllocateCommandBuffers(device, &allocInfo, &cmd);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Helper: record command buffer with dynamic rendering ---
|
||||||
|
void recordCommandBuffer(VkCommandBuffer cmd, VkImage image) {
|
||||||
|
VkCommandBufferBeginInfo beginInfo{};
|
||||||
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
|
vkBeginCommandBuffer(cmd, &beginInfo);
|
||||||
|
|
||||||
|
VkRenderingAttachmentInfo colorAttachment{};
|
||||||
|
colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||||
|
colorAttachment.imageView = VK_NULL_HANDLE; // placeholder, normally swapchainImageViews[i]
|
||||||
|
colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
VkClearValue clearColor{ {{0.1f, 0.2f, 0.3f, 1.0f}} };
|
||||||
|
colorAttachment.clearValue = clearColor;
|
||||||
|
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
|
||||||
|
VkRenderingInfo renderingInfo{};
|
||||||
|
renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||||
|
renderingInfo.renderArea.offset = {0, 0};
|
||||||
|
renderingInfo.renderArea.extent = {800, 600};
|
||||||
|
renderingInfo.layerCount = 1;
|
||||||
|
renderingInfo.colorAttachmentCount = 1;
|
||||||
|
renderingInfo.pColorAttachments = &colorAttachment;
|
||||||
|
|
||||||
|
vkCmdBeginRendering(cmd, &renderingInfo);
|
||||||
|
// No draw calls, just clear
|
||||||
|
vkCmdEndRendering(cmd);
|
||||||
|
|
||||||
|
vkEndCommandBuffer(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDL_main(int argc, char* argv[]) {
|
||||||
|
// --- SDL Window ---
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
||||||
|
throw std::runtime_error("SDL_Init failed");
|
||||||
|
|
||||||
|
window = SDL_CreateWindow("Vulkan SDL Dynamic Rendering",
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
SDL_WINDOWPOS_CENTERED,
|
||||||
|
800, 600,
|
||||||
|
SDL_WINDOW_VULKAN);
|
||||||
|
|
||||||
|
// --- Vulkan instance ---
|
||||||
|
volkInitialize();
|
||||||
|
vkb::InstanceBuilder builder;
|
||||||
|
auto instRet = builder.set_app_name("SDL Dynamic Rendering")
|
||||||
|
.request_validation_layers(true)
|
||||||
|
.build();
|
||||||
|
if (!instRet) throw std::runtime_error("Failed to create instance");
|
||||||
|
instance = instRet.value().instance;
|
||||||
|
|
||||||
|
if (!SDL_Vulkan_CreateSurface(window, instance, &surface))
|
||||||
|
throw std::runtime_error("Failed to create Vulkan surface");
|
||||||
|
|
||||||
|
// --- Physical device & logical device ---
|
||||||
|
vkb::PhysicalDeviceSelector selector{instRet.value()};
|
||||||
|
auto physRet = selector.set_minimum_version(1,2).set_surface(surface).require_dedicated_transfer_queue().select();
|
||||||
|
if (!physRet) throw std::runtime_error("Failed to select physical device");
|
||||||
|
physicalDevice = physRet.value();
|
||||||
|
|
||||||
|
vkb::DeviceBuilder deviceBuilder{physicalDevice};
|
||||||
|
auto devRet = deviceBuilder.build();
|
||||||
|
if (!devRet) throw std::runtime_error("Failed to create device");
|
||||||
|
device = devRet.value().device;
|
||||||
|
vkbDevice = devRet.value();
|
||||||
|
graphicsQueue = devRet.value().get_queue(vkb::QueueType::graphics).value();
|
||||||
|
presentQueue = devRet.value().get_queue(vkb::QueueType::present).value();
|
||||||
|
|
||||||
|
// --- Swapchain ---
|
||||||
|
vkb::SwapchainBuilder swapchainBuilder{vkbDevice};
|
||||||
|
auto swapRet = swapchainBuilder.set_desired_format(VkSurfaceFormatKHR{
|
||||||
|
.format = VK_FORMAT_B8G8R8A8_SRGB,
|
||||||
|
.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
|
||||||
|
})
|
||||||
|
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
|
||||||
|
.set_desired_extent(800,600)
|
||||||
|
.build();
|
||||||
|
if (!swapRet) throw std::runtime_error("Failed to create swapchain");
|
||||||
|
|
||||||
|
swapchain = swapRet.value().swapchain;
|
||||||
|
swapchainImages = swapRet.value().get_images().value();
|
||||||
|
swapchainImageFormat = swapRet.value().image_format;
|
||||||
|
swapchainExtent = swapRet.value().extent;
|
||||||
|
|
||||||
|
// --- Command pool ---
|
||||||
|
VkCommandPoolCreateInfo poolInfo{};
|
||||||
|
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
|
poolInfo.queueFamilyIndex = vkbDevice.get_queue_index(vkb::QueueType::graphics).value();
|
||||||
|
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool);
|
||||||
|
|
||||||
|
// --- Frames ---
|
||||||
|
imagesInFlight.resize(swapchainImages.size(), VK_NULL_HANDLE);
|
||||||
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||||
|
VkSemaphoreCreateInfo semInfo{};
|
||||||
|
semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
vkCreateSemaphore(device, &semInfo, nullptr, &frames[i].imageAvailable);
|
||||||
|
vkCreateSemaphore(device, &semInfo, nullptr, &frames[i].renderFinished);
|
||||||
|
|
||||||
|
VkFenceCreateInfo fenceInfo{};
|
||||||
|
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
|
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
vkCreateFence(device, &fenceInfo, nullptr, &frames[i].inFlight);
|
||||||
|
|
||||||
|
frames[i].commandBuffer = allocateCommandBuffer(device, commandPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main loop ---
|
||||||
|
bool running = true;
|
||||||
|
while (running) {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameData& frame = frames[currentFrame];
|
||||||
|
|
||||||
|
vkWaitForFences(device, 1, &frame.inFlight, VK_TRUE, UINT64_MAX);
|
||||||
|
|
||||||
|
uint32_t imageIndex;
|
||||||
|
vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, frame.imageAvailable, VK_NULL_HANDLE, &imageIndex);
|
||||||
|
|
||||||
|
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
|
||||||
|
vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX);
|
||||||
|
imagesInFlight[imageIndex] = frame.inFlight;
|
||||||
|
|
||||||
|
vkResetFences(device, 1, &frame.inFlight);
|
||||||
|
vkResetCommandBuffer(frame.commandBuffer, 0);
|
||||||
|
recordCommandBuffer(frame.commandBuffer, swapchainImages[imageIndex]);
|
||||||
|
|
||||||
|
VkSubmitInfo submitInfo{};
|
||||||
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
VkSemaphore waitSemaphores[] = { frame.imageAvailable };
|
||||||
|
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
||||||
|
submitInfo.waitSemaphoreCount = 1;
|
||||||
|
submitInfo.pWaitSemaphores = waitSemaphores;
|
||||||
|
submitInfo.pWaitDstStageMask = waitStages;
|
||||||
|
submitInfo.commandBufferCount = 1;
|
||||||
|
submitInfo.pCommandBuffers = &frame.commandBuffer;
|
||||||
|
VkSemaphore signalSemaphores[] = { frame.renderFinished };
|
||||||
|
submitInfo.signalSemaphoreCount = 1;
|
||||||
|
submitInfo.pSignalSemaphores = signalSemaphores;
|
||||||
|
|
||||||
|
vkQueueSubmit(graphicsQueue, 1, &submitInfo, frame.inFlight);
|
||||||
|
|
||||||
|
VkPresentInfoKHR presentInfo{};
|
||||||
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
|
presentInfo.waitSemaphoreCount = 1;
|
||||||
|
presentInfo.pWaitSemaphores = signalSemaphores;
|
||||||
|
presentInfo.swapchainCount = 1;
|
||||||
|
presentInfo.pSwapchains = &swapchain;
|
||||||
|
presentInfo.pImageIndices = &imageIndex;
|
||||||
|
|
||||||
|
vkQueuePresentKHR(presentQueue, &presentInfo);
|
||||||
|
|
||||||
|
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDeviceWaitIdle(device);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user