should prob put this on git sometime

This commit is contained in:
2026-01-03 01:48:40 +01:00
commit 1b8cc96164
52 changed files with 12002 additions and 0 deletions

79
destrum/CMakeLists.txt Normal file
View 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)

View File

@@ -0,0 +1 @@
Hello world!

View File

@@ -0,0 +1 @@
Hello world!

0
destrum/assets_src/temp Normal file
View File

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,10 @@
#ifndef RENDERER_H
#define RENDERER_H
class GameRenderer {
public:
private:
};
#endif //RENDERER_H

View 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

View 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

View 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

View 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

View 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

View 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
View 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() {
}

View 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;
}

View 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));
}

View 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));
}

View 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

View 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!");
}
}

View File

View 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;
}

View 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);
}

View File

@@ -0,0 +1 @@
#include <destrum/Graphics/VImage.h>

52
destrum/third_party/CMakeLists.txt vendored Normal file
View 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

Submodule destrum/third_party/fmt added at 7ad8004d57

1
destrum/third_party/freetype vendored Submodule

1
destrum/third_party/glfw 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/sdl vendored Submodule

Submodule destrum/third_party/sdl added at 3eba0b6f8a

1
destrum/third_party/spdlog vendored Submodule

View 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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1
destrum/third_party/vma vendored Submodule

Submodule destrum/third_party/vma added at e722e57c89

1
destrum/third_party/volk vendored Submodule