lotsa things
This commit is contained in:
parent
f99ff172db
commit
31f772eb2d
238
src/main.cpp
238
src/main.cpp
|
@ -23,9 +23,10 @@
|
||||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||||
|
|
||||||
// Include custom utility headers
|
// Include custom utility headers
|
||||||
#include "util/types.h" // Custom type definitions
|
#include "util/shaders.hpp" // Compiled shader code
|
||||||
#include "util/unique_image.h" // Custom image handling utilities
|
#include "util/types.hpp" // Custom type definitions
|
||||||
#include "util/vertex.h" // Custom vertex structure definition
|
#include "util/unique_image.hpp" // Custom image handling utilities
|
||||||
|
#include "util/vertex.hpp" // Custom vertex structure definition
|
||||||
|
|
||||||
// ImGui headers for GUI
|
// ImGui headers for GUI
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
@ -96,14 +97,20 @@ constexpr std::array<const char*, 2> deviceExtensions = { vk::KHRSwapchainExtens
|
||||||
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
|
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Main application class
|
/**
|
||||||
|
* @brief The Vulkan application class.
|
||||||
|
*
|
||||||
|
* This class encapsulates the entire Vulkan application, managing the Vulkan
|
||||||
|
* instance, window, rendering loop, and resource cleanup. It handles the
|
||||||
|
* initialization of Vulkan, manages resources, and orchestrates rendering.
|
||||||
|
*/
|
||||||
class VulkanApp {
|
class VulkanApp {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Runs the Vulkan application.
|
* @brief Runs the Vulkan application.
|
||||||
*
|
*
|
||||||
* This function initializes the application window, sets up Vulkan, and enters the main rendering loop.
|
* This function initializes the application window, sets up Vulkan, and enters
|
||||||
* It also cleans up resources when the application is closed.
|
* the main rendering loop. It also cleans up resources when the application is closed.
|
||||||
*/
|
*/
|
||||||
fn run() -> void {
|
fn run() -> void {
|
||||||
initWindow(); // Initialize the application window
|
initWindow(); // Initialize the application window
|
||||||
|
@ -125,154 +132,113 @@ class VulkanApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// GLFW and Vulkan instance handles
|
vkfw::UniqueInstance mVKFWInstance; ///< GLFW instance
|
||||||
vkfw::UniqueInstance mVKFWInstance; // GLFW instance
|
vkfw::UniqueWindow mWindow; ///< Application window
|
||||||
vkfw::UniqueWindow mWindow; // Application window
|
vk::UniqueInstance mInstance; ///< Vulkan instance
|
||||||
vk::UniqueInstance mInstance; // Vulkan instance
|
|
||||||
|
|
||||||
// Debug messenger for validation layers
|
vk::UniqueDebugUtilsMessengerEXT mDebugMessenger; ///< Debug messenger
|
||||||
vk::UniqueDebugUtilsMessengerEXT mDebugMessenger;
|
|
||||||
|
|
||||||
// Vulkan surface for rendering
|
vk::UniqueSurfaceKHR mSurface; ///< Vulkan surface for rendering
|
||||||
vk::UniqueSurfaceKHR mSurface;
|
|
||||||
|
|
||||||
// Vulkan devices and queues
|
vk::PhysicalDevice mPhysicalDevice; ///< Physical GPU
|
||||||
vk::PhysicalDevice mPhysicalDevice; // Physical GPU
|
vk::SampleCountFlagBits mMsaaSamples; ///< Multisampling count
|
||||||
vk::SampleCountFlagBits mMsaaSamples; // Multisampling count
|
vk::UniqueDevice mDevice; ///< Logical Vulkan device
|
||||||
vk::UniqueDevice mDevice; // Logical Vulkan device
|
|
||||||
|
|
||||||
vk::Queue mGraphicsQueue; // Queue for graphics commands
|
vk::Queue mGraphicsQueue; ///< Queue for graphics commands
|
||||||
vk::Queue mPresentQueue; // Queue for presentation commands
|
vk::Queue mPresentQueue; ///< Queue for presentation commands
|
||||||
|
|
||||||
// Swap chain and related resources
|
vk::UniqueSwapchainKHR mSwapChain; ///< Swap chain for frame buffering
|
||||||
vk::UniqueSwapchainKHR mSwapChain; // Swap chain for frame buffering
|
std::vector<vk::Image> mSwapChainImages; ///< Images in the swap chain
|
||||||
std::vector<vk::Image> mSwapChainImages; // Images in the swap chain
|
vk::Format mSwapChainImageFormat; ///< Format of swap chain images
|
||||||
vk::Format mSwapChainImageFormat; // Format of swap chain images
|
vk::Extent2D mSwapChainExtent; ///< Dimensions of swap chain images
|
||||||
vk::Extent2D mSwapChainExtent; // Dimensions of swap chain images
|
std::vector<vk::UniqueImageView> mSwapChainImageViews; ///< Image views for swap chain images
|
||||||
std::vector<vk::UniqueImageView> mSwapChainImageViews; // Image views for swap chain images
|
std::vector<vk::UniqueFramebuffer> mSwapChainFramebuffers; ///< Framebuffers for rendering
|
||||||
std::vector<vk::UniqueFramebuffer> mSwapChainFramebuffers; // Framebuffers for rendering
|
|
||||||
|
|
||||||
// Render pass and pipeline objects
|
vk::UniqueRenderPass mRenderPass; ///< Render pass definition
|
||||||
vk::UniqueRenderPass mRenderPass; // Render pass definition
|
vk::UniqueDescriptorSetLayout mDescriptorSetLayout; ///< Descriptor set layout
|
||||||
vk::UniqueDescriptorSetLayout mDescriptorSetLayout; // Descriptor set layout
|
vk::UniquePipelineLayout mPipelineLayout; ///< Pipeline layout
|
||||||
vk::UniquePipelineLayout mPipelineLayout; // Pipeline layout
|
vk::UniquePipeline mGraphicsPipeline; ///< Graphics pipeline
|
||||||
vk::UniquePipeline mGraphicsPipeline; // Graphics pipeline
|
|
||||||
|
|
||||||
// Command pool for allocating command buffers
|
vk::UniqueCommandPool mCommandPool; ///< Command pool for allocating command buffers
|
||||||
vk::UniqueCommandPool mCommandPool;
|
|
||||||
|
|
||||||
// Multisampling color resources
|
vk::UniqueImage mColorImage; ///< Color image
|
||||||
vk::UniqueImage mColorImage;
|
vk::UniqueDeviceMemory mColorImageMemory; ///< Memory for color image
|
||||||
vk::UniqueDeviceMemory mColorImageMemory;
|
vk::UniqueImageView mColorImageView; ///< Image view for color image
|
||||||
vk::UniqueImageView mColorImageView;
|
|
||||||
|
|
||||||
// Depth buffer resources
|
vk::UniqueImage mDepthImage; ///< Depth image
|
||||||
vk::UniqueImage mDepthImage;
|
vk::UniqueDeviceMemory mDepthImageMemory; ///< Memory for depth image
|
||||||
vk::UniqueDeviceMemory mDepthImageMemory;
|
vk::UniqueImageView mDepthImageView; ///< Image view for depth image
|
||||||
vk::UniqueImageView mDepthImageView;
|
|
||||||
|
|
||||||
// Texture resources
|
u32 mMipLevels; ///< Number of mipmap levels
|
||||||
u32 mMipLevels; // Number of mipmap levels
|
vk::UniqueImage mTextureImage; ///< Texture image
|
||||||
vk::UniqueImage mTextureImage; // Texture image
|
vk::UniqueDeviceMemory mTextureImageMemory; ///< Memory for texture image
|
||||||
vk::UniqueDeviceMemory mTextureImageMemory; // Memory for texture image
|
vk::UniqueImageView mTextureImageView; ///< Image view for texture
|
||||||
vk::UniqueImageView mTextureImageView; // Image view for texture
|
vk::UniqueSampler mTextureSampler; ///< Sampler for texture
|
||||||
vk::UniqueSampler mTextureSampler; // Sampler for texture
|
|
||||||
|
|
||||||
// Vertex and index data
|
std::vector<Vertex> mVertices; ///< Vertex data for the model
|
||||||
std::vector<Vertex> mVertices; // Vertex data for the model
|
std::vector<u32> mIndices; ///< Index data for the model
|
||||||
std::vector<u32> mIndices; // Index data for the model
|
vk::UniqueBuffer mVertexBuffer; ///< Buffer for vertex data
|
||||||
vk::UniqueBuffer mVertexBuffer; // Buffer for vertex data
|
vk::UniqueDeviceMemory mVertexBufferMemory; ///< Memory for vertex buffer
|
||||||
vk::UniqueDeviceMemory mVertexBufferMemory; // Memory for vertex buffer
|
vk::UniqueBuffer mIndexBuffer; ///< Buffer for index data
|
||||||
vk::UniqueBuffer mIndexBuffer; // Buffer for index data
|
vk::UniqueDeviceMemory mIndexBufferMemory; ///< Memory for index buffer
|
||||||
vk::UniqueDeviceMemory mIndexBufferMemory; // Memory for index buffer
|
|
||||||
|
|
||||||
// Uniform buffers for shader parameters
|
std::vector<vk::UniqueBuffer> mUniformBuffers; ///< Uniform buffers for shader parameters
|
||||||
std::vector<vk::UniqueBuffer> mUniformBuffers;
|
std::vector<vk::UniqueDeviceMemory> mUniformBuffersMemory; ///< Memory for uniform buffers
|
||||||
std::vector<vk::UniqueDeviceMemory> mUniformBuffersMemory;
|
std::vector<void*> mUniformBuffersMapped; ///< Mapped uniform buffers
|
||||||
std::vector<void*> mUniformBuffersMapped;
|
|
||||||
|
|
||||||
// Descriptor pools and sets
|
vk::UniqueDescriptorPool mDescriptorPool; ///< Descriptor pool for the application
|
||||||
vk::UniqueDescriptorPool mDescriptorPool; // Descriptor pool for the application
|
vk::UniqueDescriptorPool mImGuiDescriptorPool; ///< Separate descriptor pool for ImGui
|
||||||
vk::UniqueDescriptorPool mImGuiDescriptorPool; // Separate descriptor pool for ImGui
|
std::vector<vk::DescriptorSet> mDescriptorSets; ///< Descriptor sets for binding resources
|
||||||
std::vector<vk::DescriptorSet> mDescriptorSets; // Descriptor sets for binding resources
|
|
||||||
|
|
||||||
// Command buffers for recording drawing commands
|
std::vector<vk::UniqueCommandBuffer> mCommandBuffers; ///< Command buffers for drawing
|
||||||
std::vector<vk::UniqueCommandBuffer> mCommandBuffers;
|
|
||||||
|
|
||||||
// Synchronization primitives
|
|
||||||
std::vector<vk::UniqueSemaphore>
|
std::vector<vk::UniqueSemaphore>
|
||||||
mImageAvailableSemaphores; // Signals that an image is available for rendering
|
mImageAvailableSemaphores; ///< Signals that an image is available for rendering
|
||||||
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores; // Signals that rendering has finished
|
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores; ///< Signals that rendering has finished
|
||||||
std::vector<vk::UniqueFence> mInFlightFences; // Ensures CPU-GPU synchronization
|
std::vector<vk::UniqueFence> mInFlightFences; ///< Ensures CPU-GPU synchronization
|
||||||
|
|
||||||
// State variables
|
bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized
|
||||||
bool mFramebufferResized = false; // Flag indicating if the framebuffer was resized
|
u32 mCurrentFrame = 0; ///< Index of the current frame being rendered
|
||||||
u32 mCurrentFrame = 0; // Index of the current frame being rendered
|
|
||||||
|
|
||||||
// Helper struct to store queue family indices
|
/**
|
||||||
|
* @brief Struct to store queue family indices.
|
||||||
|
*
|
||||||
|
* This struct contains the indices of the graphics and presentation queue families.
|
||||||
|
*/
|
||||||
struct QueueFamilyIndices {
|
struct QueueFamilyIndices {
|
||||||
std::optional<u32> graphics_family; // Index of graphics queue family
|
std::optional<u32> graphics_family; ///< Index of graphics queue family
|
||||||
std::optional<u32> present_family; // Index of presentation queue family
|
std::optional<u32> present_family; ///< Index of presentation queue family
|
||||||
|
|
||||||
// Check if all required queue families are found
|
/**
|
||||||
|
* @brief Check if all required queue families are found.
|
||||||
|
*
|
||||||
|
* @return True if both graphics and presentation families are found, false otherwise.
|
||||||
|
*/
|
||||||
fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); }
|
fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper struct to store swap chain support details
|
/**
|
||||||
|
* @brief Struct to hold swap chain support details.
|
||||||
|
*
|
||||||
|
* This struct contains information about the surface capabilities,
|
||||||
|
* supported formats, and presentation modes.
|
||||||
|
*/
|
||||||
struct SwapChainSupportDetails {
|
struct SwapChainSupportDetails {
|
||||||
vk::SurfaceCapabilitiesKHR capabilities; // Surface capabilities
|
vk::SurfaceCapabilitiesKHR capabilities; ///< Surface capabilities
|
||||||
std::vector<vk::SurfaceFormatKHR> formats; // Supported surface formats
|
std::vector<vk::SurfaceFormatKHR> formats; ///< Supported surface formats
|
||||||
std::vector<vk::PresentModeKHR> present_modes; // Supported presentation modes
|
std::vector<vk::PresentModeKHR> present_modes; ///< Supported presentation modes
|
||||||
};
|
};
|
||||||
|
|
||||||
// Struct to hold uniform buffer object data
|
|
||||||
struct UniformBufferObject {
|
|
||||||
alignas(16) glm::mat4 model; // Model transformation matrix
|
|
||||||
alignas(16) glm::mat4 view; // View transformation matrix
|
|
||||||
alignas(16) glm::mat4 proj; // Projection matrix
|
|
||||||
};
|
|
||||||
|
|
||||||
static fn compileShader(const std::string& source, const shaderc_shader_kind& kind)
|
|
||||||
-> std::vector<uint32_t> {
|
|
||||||
shaderc::Compiler compiler;
|
|
||||||
shaderc::CompileOptions options;
|
|
||||||
|
|
||||||
shaderc::CompilationResult<u32> result =
|
|
||||||
compiler.CompileGlslToSpv(source, kind, "shader.glsl", "main", options);
|
|
||||||
|
|
||||||
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
|
||||||
fmt::println(stderr, "Shader compilation failed: {}", result.GetErrorMessage());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { result.cbegin(), result.cend() };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reads the contents of a file into a vector of chars.
|
* @brief Struct representing a uniform buffer object.
|
||||||
*
|
*
|
||||||
* @param filename The path to the file to be read.
|
* This struct holds the model, view, and projection matrices for use in shaders.
|
||||||
* @return std::vector<char> A vector containing the file's contents.
|
|
||||||
* @throws std::runtime_error if the file cannot be opened.
|
|
||||||
*
|
|
||||||
* This function opens the specified file in binary mode, reads its entire contents
|
|
||||||
* into a vector of chars, and returns that vector. It's commonly used for reading
|
|
||||||
* shader files or other binary data needed by the application.
|
|
||||||
*/
|
*/
|
||||||
static fn readFile(const std::string& filename) -> std::vector<char> {
|
struct UniformBufferObject {
|
||||||
// Open the file in binary mode
|
alignas(16) glm::mat4 model; ///< Model transformation matrix
|
||||||
std::ifstream file(filename, std::ios::binary);
|
alignas(16) glm::mat4 view; ///< View transformation matrix
|
||||||
|
alignas(16) glm::mat4 proj; ///< Projection matrix
|
||||||
// Check if the file was successfully opened
|
};
|
||||||
if (!file)
|
|
||||||
throw std::runtime_error("Failed to open file: " + filename);
|
|
||||||
|
|
||||||
// Read the entire file content into a vector
|
|
||||||
// This constructor reads from the start (first iterator) to the end (default constructor) of the stream
|
|
||||||
std::vector<char> buffer((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update the FPS counter in the window title.
|
* @brief Update the FPS counter in the window title.
|
||||||
|
@ -282,13 +248,13 @@ class VulkanApp {
|
||||||
*
|
*
|
||||||
* This function updates the window title with the current frames per second.
|
* This function updates the window title with the current frames per second.
|
||||||
*/
|
*/
|
||||||
static fn updateFPS(const vkfw::Window& window, const std::string& baseTitle) -> void {
|
static fn updateFPS(const vkfw::Window& window, const string& baseTitle) -> void {
|
||||||
static int FrameCount = 0;
|
static u32 FrameCount = 0;
|
||||||
static double LastTime = glfwGetTime();
|
static f64 LastTime = glfwGetTime();
|
||||||
static double Fps = 0.0;
|
static f64 Fps = 0.0;
|
||||||
|
|
||||||
// Get the current time
|
// Get the current time
|
||||||
double currentTime = glfwGetTime();
|
f64 currentTime = glfwGetTime();
|
||||||
FrameCount++;
|
FrameCount++;
|
||||||
|
|
||||||
// If one second has passed, calculate the FPS
|
// If one second has passed, calculate the FPS
|
||||||
|
@ -298,7 +264,7 @@ class VulkanApp {
|
||||||
FrameCount = 0;
|
FrameCount = 0;
|
||||||
|
|
||||||
// Update window title
|
// Update window title
|
||||||
std::string newTitle = format("{} - {:.0F} FPS", baseTitle, Fps);
|
string newTitle = format("{} - {:.0F} FPS", baseTitle, Fps);
|
||||||
glfwSetWindowTitle(window, newTitle.c_str());
|
glfwSetWindowTitle(window, newTitle.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,7 +394,7 @@ class VulkanApp {
|
||||||
.DescriptorPool = mImGuiDescriptorPool.get(),
|
.DescriptorPool = mImGuiDescriptorPool.get(),
|
||||||
.RenderPass = mRenderPass.get(),
|
.RenderPass = mRenderPass.get(),
|
||||||
.MinImageCount = MAX_FRAMES_IN_FLIGHT,
|
.MinImageCount = MAX_FRAMES_IN_FLIGHT,
|
||||||
.ImageCount = static_cast<uint32_t>(mSwapChainImages.size()),
|
.ImageCount = static_cast<u32>(mSwapChainImages.size()),
|
||||||
.MSAASamples = static_cast<VkSampleCountFlagBits>(mMsaaSamples),
|
.MSAASamples = static_cast<VkSampleCountFlagBits>(mMsaaSamples),
|
||||||
.PipelineCache = VK_NULL_HANDLE,
|
.PipelineCache = VK_NULL_HANDLE,
|
||||||
.Subpass = 0,
|
.Subpass = 0,
|
||||||
|
@ -525,7 +491,7 @@ class VulkanApp {
|
||||||
.applicationVersion = 1,
|
.applicationVersion = 1,
|
||||||
.pEngineName = "No Engine",
|
.pEngineName = "No Engine",
|
||||||
.engineVersion = 1,
|
.engineVersion = 1,
|
||||||
.apiVersion = vk::ApiVersion12,
|
.apiVersion = vk::ApiVersion13,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the required extensions
|
// Get the required extensions
|
||||||
|
@ -898,9 +864,9 @@ class VulkanApp {
|
||||||
*/
|
*/
|
||||||
fn createGraphicsPipeline() -> void {
|
fn createGraphicsPipeline() -> void {
|
||||||
std::vector<u32> vertShaderCode =
|
std::vector<u32> vertShaderCode =
|
||||||
compileShader(vertShaderSrc, shaderc_shader_kind::shaderc_vertex_shader);
|
ShaderCompiler::getCompiledShader(vertShaderSrc, shaderc_shader_kind::shaderc_vertex_shader, "vert");
|
||||||
std::vector<u32> fragShaderCode =
|
std::vector<u32> fragShaderCode =
|
||||||
compileShader(fragShaderSrc, shaderc_shader_kind::shaderc_fragment_shader);
|
ShaderCompiler::getCompiledShader(fragShaderSrc, shaderc_shader_kind::shaderc_fragment_shader, "frag");
|
||||||
|
|
||||||
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
||||||
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
||||||
|
@ -1582,7 +1548,7 @@ class VulkanApp {
|
||||||
tinyobj::attrib_t attrib;
|
tinyobj::attrib_t attrib;
|
||||||
std::vector<tinyobj::shape_t> shapes;
|
std::vector<tinyobj::shape_t> shapes;
|
||||||
std::vector<tinyobj::material_t> materials;
|
std::vector<tinyobj::material_t> materials;
|
||||||
std::string warn, err;
|
string warn, err;
|
||||||
|
|
||||||
std::filesystem::path modelPath = std::filesystem::current_path() / "models" / "viking_room.obj";
|
std::filesystem::path modelPath = std::filesystem::current_path() / "models" / "viking_room.obj";
|
||||||
|
|
||||||
|
|
166
src/util/shaders.hpp
Normal file
166
src/util/shaders.hpp
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fmt/base.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <shaderc/shaderc.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
|
class ShaderCompiler {
|
||||||
|
public:
|
||||||
|
ShaderCompiler() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compiles or loads a cached SPIR-V shader from a file.
|
||||||
|
*
|
||||||
|
* @param shaderSource The source code of the shader.
|
||||||
|
* @param kind The type of shader being compiled (vertex, fragment, etc.).
|
||||||
|
* @param shaderName The name used for caching the compiled shader.
|
||||||
|
* @return std::vector<u32> A vector containing the compiled SPIR-V code.
|
||||||
|
* @throws std::runtime_error if shader compilation fails.
|
||||||
|
*
|
||||||
|
* This function attempts to load a shader from the cache. If the shader
|
||||||
|
* is not found in the cache, it compiles the shader from the source code
|
||||||
|
* and saves the result to the cache for future use.
|
||||||
|
*/
|
||||||
|
static fn getCompiledShader(
|
||||||
|
const char* shaderSource,
|
||||||
|
const shaderc_shader_kind& kind,
|
||||||
|
const string& shaderName
|
||||||
|
) -> std::vector<u32> {
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const string cacheFile = getCacheFilePath(shaderName);
|
||||||
|
|
||||||
|
// Try loading from cache first
|
||||||
|
vector<u32> spirvCode = loadCachedShader(cacheFile);
|
||||||
|
|
||||||
|
if (!spirvCode.empty()) {
|
||||||
|
fmt::println("Loaded shader from cache: {}", cacheFile);
|
||||||
|
return spirvCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss, compile the shader
|
||||||
|
spirvCode = compileShader(shaderSource, kind);
|
||||||
|
|
||||||
|
if (spirvCode.empty())
|
||||||
|
throw runtime_error("Shader compilation failed for: " + shaderName);
|
||||||
|
|
||||||
|
// Cache the compiled SPIR-V binary
|
||||||
|
saveCompiledShader(spirvCode, cacheFile);
|
||||||
|
return spirvCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Generates the cache file path based on the operating system.
|
||||||
|
*
|
||||||
|
* @param shaderName The name used for the shader file.
|
||||||
|
* @return string The full path to the cache file.
|
||||||
|
*
|
||||||
|
* This function determines the appropriate directory for caching shaders
|
||||||
|
* based on the operating system and returns the full path for the specified shader name.
|
||||||
|
*/
|
||||||
|
static fn getCacheFilePath(const string& shaderName) -> string {
|
||||||
|
using namespace std::filesystem;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
path cacheDir = path(getenv("LOCALAPPDATA")) / "VulkanApp" / "Shaders";
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
path cacheDir = path(getenv("HOME")) / "Library" / "Application Support" / "VulkanApp" / "Shaders";
|
||||||
|
#else // Assume Linux or other UNIX-like systems
|
||||||
|
path cacheDir = path(getenv("HOME")) / ".local" / "share" / "VulkanApp" / "Shaders";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!exists(cacheDir))
|
||||||
|
create_directories(cacheDir); // Create the directory if it doesn't exist
|
||||||
|
|
||||||
|
return (cacheDir / (shaderName + ".spv")).string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compiles GLSL code to SPIR-V.
|
||||||
|
*
|
||||||
|
* @param source The GLSL source code to compile.
|
||||||
|
* @param kind The type of shader being compiled.
|
||||||
|
* @return std::vector<u32> A vector containing the compiled SPIR-V code.
|
||||||
|
*
|
||||||
|
* This function uses the shaderc library to compile GLSL source code into
|
||||||
|
* SPIR-V binary format. If the compilation fails, an empty vector is returned.
|
||||||
|
*/
|
||||||
|
static fn compileShader(const char* source, const shaderc_shader_kind& kind) -> std::vector<u32> {
|
||||||
|
using namespace shaderc;
|
||||||
|
|
||||||
|
Compiler compiler;
|
||||||
|
CompileOptions options;
|
||||||
|
|
||||||
|
SpvCompilationResult result = compiler.CompileGlslToSpv(source, kind, "shader.glsl", "main", options);
|
||||||
|
|
||||||
|
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
||||||
|
fmt::println(stderr, "Shader compilation failed: {}", result.GetErrorMessage());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { result.cbegin(), result.cend() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads compiled SPIR-V shader code from a cache file.
|
||||||
|
*
|
||||||
|
* @param cacheFile The path to the cached SPIR-V file.
|
||||||
|
* @return std::vector<u32> A vector containing the loaded SPIR-V code.
|
||||||
|
* @throws std::runtime_error if the file cannot be read.
|
||||||
|
*
|
||||||
|
* This function checks if the specified cache file exists and reads its
|
||||||
|
* contents into a vector. If the file cannot be opened, an exception is thrown.
|
||||||
|
*/
|
||||||
|
static fn loadCachedShader(const string& cacheFile) -> std::vector<u32> {
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
if (!filesystem::exists(cacheFile))
|
||||||
|
return {}; // No cached file
|
||||||
|
|
||||||
|
ifstream file(cacheFile, ios::binary);
|
||||||
|
|
||||||
|
// Check if the file was successfully opened
|
||||||
|
if (!file)
|
||||||
|
throw runtime_error("Failed to open cached shader file: " + cacheFile);
|
||||||
|
|
||||||
|
usize fileSize = filesystem::file_size(cacheFile);
|
||||||
|
vector<u32> spirvCode(fileSize / sizeof(u32));
|
||||||
|
|
||||||
|
// Read entire file content into the vector
|
||||||
|
if (!file.read(bit_cast<char*>(spirvCode.data()), static_cast<streamsize>(fileSize)))
|
||||||
|
throw runtime_error("Failed to read cached shader file: " + cacheFile);
|
||||||
|
|
||||||
|
return spirvCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Saves compiled SPIR-V binary to a cache file.
|
||||||
|
*
|
||||||
|
* @param spirvCode The SPIR-V code to save.
|
||||||
|
* @param cacheFile The path to the file where the SPIR-V code will be saved.
|
||||||
|
* @throws std::runtime_error if the file cannot be written.
|
||||||
|
*
|
||||||
|
* This function writes the compiled SPIR-V binary to the specified file.
|
||||||
|
* If the file cannot be opened or written, an exception is thrown.
|
||||||
|
*/
|
||||||
|
static fn saveCompiledShader(const std::vector<u32>& spirvCode, const string& cacheFile) -> void {
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
ofstream file(cacheFile, ios::binary);
|
||||||
|
|
||||||
|
// Check if the file was successfully opened
|
||||||
|
if (!file)
|
||||||
|
throw runtime_error("Failed to open file for saving shader: " + cacheFile);
|
||||||
|
|
||||||
|
if (!file.write(
|
||||||
|
bit_cast<const char*>(spirvCode.data()), static_cast<streamsize>(spirvCode.size() * sizeof(u32))
|
||||||
|
))
|
||||||
|
throw runtime_error("Failed to save shader to cache: " + cacheFile);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,69 +0,0 @@
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include <stb_image.h>
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
namespace stb {
|
|
||||||
class UniqueImage {
|
|
||||||
public:
|
|
||||||
// Constructor
|
|
||||||
UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment operator
|
|
||||||
UniqueImage(const UniqueImage&) = delete;
|
|
||||||
fn operator=(const UniqueImage&)->UniqueImage& = delete;
|
|
||||||
|
|
||||||
// Move constructor and assignment operator
|
|
||||||
UniqueImage(UniqueImage&& other) noexcept
|
|
||||||
: mData(other.mData), mWidth(other.mWidth), mHeight(other.mHeight), mChannels(other.mChannels) {
|
|
||||||
other.mData = nullptr; // Set the other's data to nullptr
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
|
|
||||||
if (this != &other) {
|
|
||||||
// Free existing resource
|
|
||||||
if (mData)
|
|
||||||
stbi_image_free(mData);
|
|
||||||
|
|
||||||
// Move the resource
|
|
||||||
mData = other.mData;
|
|
||||||
mWidth = other.mWidth;
|
|
||||||
mHeight = other.mHeight;
|
|
||||||
mChannels = other.mChannels;
|
|
||||||
other.mData = nullptr; // Set the other's data to nullptr
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~UniqueImage() {
|
|
||||||
if (mData)
|
|
||||||
stbi_image_free(mData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accessors for image data and dimensions
|
|
||||||
[[nodiscard]] fn getData() const -> unsigned char* { return mData; }
|
|
||||||
|
|
||||||
[[nodiscard]] fn getWidth() const -> int { return mWidth; }
|
|
||||||
|
|
||||||
[[nodiscard]] fn getHeight() const -> int { return mHeight; }
|
|
||||||
|
|
||||||
[[nodiscard]] fn getChannels() const -> int { return mChannels; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned char* mData = nullptr;
|
|
||||||
int mWidth = 0;
|
|
||||||
int mHeight = 0;
|
|
||||||
int mChannels = 0;
|
|
||||||
|
|
||||||
// Load function
|
|
||||||
void load(const char* filename) {
|
|
||||||
mData = stbi_load(filename, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
|
|
||||||
|
|
||||||
if (!mData)
|
|
||||||
throw std::runtime_error("Failed to load image: " + std::string(stbi_failure_reason()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
121
src/util/unique_image.hpp
Normal file
121
src/util/unique_image.hpp
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
|
namespace stb {
|
||||||
|
/**
|
||||||
|
* @brief A class that handles loading and managing image data.
|
||||||
|
*
|
||||||
|
* This class uses the stb_image library to load images from the filesystem
|
||||||
|
* and provides access to the image data, dimensions, and channel count.
|
||||||
|
*/
|
||||||
|
class UniqueImage {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a UniqueImage object and loads an image from the specified path.
|
||||||
|
*
|
||||||
|
* @param path The filesystem path to the image file to load.
|
||||||
|
*/
|
||||||
|
UniqueImage(const std::filesystem::path& path) { load(path.c_str()); }
|
||||||
|
|
||||||
|
// Deleted copy constructor to prevent copying.
|
||||||
|
UniqueImage(const UniqueImage&) = delete;
|
||||||
|
|
||||||
|
// Deleted copy assignment operator to prevent copying.
|
||||||
|
fn operator=(const UniqueImage&)->UniqueImage& = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor for UniqueImage.
|
||||||
|
*
|
||||||
|
* Transfers ownership of resources from another UniqueImage object.
|
||||||
|
*
|
||||||
|
* @param other The UniqueImage object from which to move resources.
|
||||||
|
*/
|
||||||
|
UniqueImage(UniqueImage&& other) noexcept
|
||||||
|
: mData(other.mData), mWidth(other.mWidth), mHeight(other.mHeight), mChannels(other.mChannels) {
|
||||||
|
other.mData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator for UniqueImage.
|
||||||
|
*
|
||||||
|
* Transfers ownership of resources from another UniqueImage object.
|
||||||
|
*
|
||||||
|
* @param other The UniqueImage object from which to move resources.
|
||||||
|
* @return Reference to this object.
|
||||||
|
*/
|
||||||
|
fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
|
||||||
|
if (this != &other) {
|
||||||
|
if (mData)
|
||||||
|
stbi_image_free(mData);
|
||||||
|
|
||||||
|
mData = other.mData;
|
||||||
|
mWidth = other.mWidth;
|
||||||
|
mHeight = other.mHeight;
|
||||||
|
mChannels = other.mChannels;
|
||||||
|
other.mData = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor for UniqueImage.
|
||||||
|
*
|
||||||
|
* Frees the image data if it is allocated.
|
||||||
|
*/
|
||||||
|
~UniqueImage() {
|
||||||
|
if (mData)
|
||||||
|
stbi_image_free(mData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the image data.
|
||||||
|
*
|
||||||
|
* @return Pointer to the image data in memory.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] fn getData() const -> u8* { return mData; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the width of the image.
|
||||||
|
*
|
||||||
|
* @return The width of the image in pixels.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] fn getWidth() const -> i32 { return mWidth; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the height of the image.
|
||||||
|
*
|
||||||
|
* @return The height of the image in pixels.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] fn getHeight() const -> i32 { return mHeight; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the number of channels in the image.
|
||||||
|
*
|
||||||
|
* @return The number of channels in the image (e.g., 3 for RGB, 4 for RGBA).
|
||||||
|
*/
|
||||||
|
[[nodiscard]] fn getChannels() const -> i32 { return mChannels; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
u8* mData = nullptr; ///< Pointer to the image data.
|
||||||
|
i32 mWidth = 0; ///< Width of the image in pixels.
|
||||||
|
i32 mHeight = 0; ///< Height of the image in pixels.
|
||||||
|
i32 mChannels = 0; ///< Number of channels in the image.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads an image from a file.
|
||||||
|
*
|
||||||
|
* @param filename The name of the file from which to load the image.
|
||||||
|
* @throws std::runtime_error If the image fails to load.
|
||||||
|
*/
|
||||||
|
fn load(const char* filename) -> void {
|
||||||
|
mData = stbi_load(filename, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
|
||||||
|
|
||||||
|
if (!mData)
|
||||||
|
throw std::runtime_error("Failed to load image: " + string(stbi_failure_reason()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.hpp"
|
||||||
|
|
||||||
// Vertex data for the model
|
// Vertex data for the model
|
||||||
struct Vertex {
|
struct Vertex {
|
Loading…
Reference in a new issue