2024-10-18 00:29:10 -04:00
|
|
|
// Include necessary headers
|
2024-10-18 20:00:24 -04:00
|
|
|
#include <chrono> // For time-related functions
|
|
|
|
#include <fmt/format.h> // For string formatting
|
|
|
|
#include <shaderc/shaderc.hpp> // For shader compilation
|
|
|
|
#include <unordered_set> // For unordered_set container
|
2024-10-18 00:29:10 -04:00
|
|
|
|
|
|
|
// GLM (OpenGL Mathematics) configuration
|
|
|
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE // Use Vulkan's depth range (0 to 1) instead of OpenGL's (-1 to 1)
|
|
|
|
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES // Force GLM to use aligned types
|
|
|
|
#include <glm/glm.hpp> // Include GLM for mathematics operations
|
|
|
|
|
|
|
|
// TinyObjLoader for loading 3D models
|
2024-10-11 20:04:28 -04:00
|
|
|
#define TINYOBJLOADER_IMPLEMENTATION
|
|
|
|
#include <tiny_obj_loader.h>
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Vulkan configuration and inclusion
|
|
|
|
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 // Use dynamic dispatch for Vulkan functions
|
|
|
|
#define VK_ENABLE_BETA_EXTENSIONS // Enable beta Vulkan extensions
|
|
|
|
#define VULKAN_HPP_NO_CONSTRUCTORS // Use aggregate initialization for Vulkan structs
|
|
|
|
#include <vulkan/vulkan.hpp> // Include Vulkan C++ bindings
|
2024-09-29 23:02:04 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Necessary for dynamic dispatch to work
|
2024-10-06 12:26:44 -04:00
|
|
|
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Include custom utility headers
|
2024-11-15 21:25:10 -05:00
|
|
|
#include "util/crosshair.hpp" // Crosshair definitions
|
2024-10-19 00:41:14 -04:00
|
|
|
#include "util/shaders.hpp" // Compiled shader code
|
|
|
|
#include "util/types.hpp" // Custom type definitions
|
|
|
|
#include "util/unique_image.hpp" // Custom image handling utilities
|
|
|
|
#include "util/vertex.hpp" // Custom vertex structure definition
|
2024-10-12 15:39:20 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// ImGui headers for GUI
|
2024-10-15 19:49:39 -04:00
|
|
|
#include <imgui.h>
|
|
|
|
#include <imgui_impl_glfw.h>
|
|
|
|
#include <imgui_impl_vulkan.h>
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// GLFW configuration and inclusion
|
|
|
|
#define VKFW_NO_STRUCT_CONSTRUCTORS // Use aggregate initialization for GLFW structs
|
|
|
|
#include "vkfw.hpp" // Include GLFW C++ bindings
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Constants for window dimensions
|
2024-11-15 12:40:13 -05:00
|
|
|
constexpr i32 WIDTH = 1920;
|
|
|
|
constexpr i32 HEIGHT = 1080;
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-11-07 00:50:58 -05:00
|
|
|
// CAMERA_SPEED of camera movement
|
|
|
|
constexpr f64 CAMERA_SPEED = 1.0;
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Maximum number of frames that can be processed concurrently
|
2024-10-12 15:39:20 -04:00
|
|
|
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
|
2024-10-10 20:39:04 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Shader file paths
|
2024-11-15 21:25:10 -05:00
|
|
|
constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl";
|
|
|
|
constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl";
|
|
|
|
constexpr const char* CROSSHAIR_VERTEX_SHADER_PATH = "shaders/crosshair_vertex.glsl";
|
|
|
|
constexpr const char* CROSSHAIR_FRAGMENT_SHADER_PATH = "shaders/crosshair_fragment.glsl";
|
2024-10-18 20:00:24 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Validation layers for debug builds
|
2024-10-14 23:32:52 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };
|
|
|
|
#endif
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Required device extensions (platform-specific)
|
2024-10-10 20:39:04 -04:00
|
|
|
#ifdef __APPLE__
|
|
|
|
constexpr std::array<const char*, 2> deviceExtensions = { vk::KHRSwapchainExtensionName,
|
|
|
|
vk::KHRPortabilitySubsetExtensionName };
|
|
|
|
#else
|
|
|
|
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
|
|
|
|
#endif
|
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2024-09-29 23:11:12 -04:00
|
|
|
class VulkanApp {
|
2024-09-25 23:03:56 -04:00
|
|
|
public:
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Runs the Vulkan application.
|
|
|
|
*
|
2024-10-19 00:41:14 -04:00
|
|
|
* This function initializes the application window, sets up Vulkan, and enters
|
|
|
|
* the main rendering loop. It also cleans up resources when the application is closed.
|
2024-10-18 00:29:10 -04:00
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn run() -> void {
|
2024-10-18 00:29:10 -04:00
|
|
|
initWindow(); // Initialize the application window
|
|
|
|
initVulkan(); // Initialize Vulkan
|
|
|
|
mainLoop(); // Enter the main rendering loop
|
2024-10-15 19:49:39 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
cleanupSwapChain(); // Clean up swap chain resources
|
|
|
|
|
|
|
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
|
|
|
if (mUniformBuffersMapped[i]) {
|
|
|
|
mDevice->unmapMemory(mUniformBuffersMemory[i].get());
|
|
|
|
mUniformBuffersMapped[i] = nullptr;
|
|
|
|
}
|
2024-10-15 19:49:39 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Shut down ImGui
|
2024-11-15 15:25:36 -05:00
|
|
|
ImGui_ImplVulkan_Shutdown();
|
|
|
|
ImGui_ImplGlfw_Shutdown();
|
|
|
|
ImGui::DestroyContext();
|
2024-09-25 23:03:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2024-10-19 00:41:14 -04:00
|
|
|
vkfw::UniqueInstance mVKFWInstance; ///< GLFW instance
|
|
|
|
vkfw::UniqueWindow mWindow; ///< Application window
|
|
|
|
vk::UniqueInstance mInstance; ///< Vulkan instance
|
2024-10-15 20:38:05 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueDebugUtilsMessengerEXT mDebugMessenger; ///< Debug messenger
|
2024-10-15 20:38:05 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueSurfaceKHR mSurface; ///< Vulkan surface for rendering
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::PhysicalDevice mPhysicalDevice; ///< Physical GPU
|
|
|
|
vk::SampleCountFlagBits mMsaaSamples; ///< Multisampling count
|
|
|
|
vk::UniqueDevice mDevice; ///< Logical Vulkan device
|
2024-09-28 21:55:26 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::Queue mGraphicsQueue; ///< Queue for graphics commands
|
|
|
|
vk::Queue mPresentQueue; ///< Queue for presentation commands
|
2024-10-01 16:10:20 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueSwapchainKHR mSwapChain; ///< Swap chain for frame buffering
|
|
|
|
std::vector<vk::Image> mSwapChainImages; ///< Images in the swap chain
|
|
|
|
vk::Format mSwapChainImageFormat; ///< Format 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::UniqueFramebuffer> mSwapChainFramebuffers; ///< Framebuffers for rendering
|
2024-10-10 20:39:04 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueRenderPass mRenderPass; ///< Render pass definition
|
|
|
|
vk::UniqueDescriptorSetLayout mDescriptorSetLayout; ///< Descriptor set layout
|
|
|
|
vk::UniquePipelineLayout mPipelineLayout; ///< Pipeline layout
|
|
|
|
vk::UniquePipeline mGraphicsPipeline; ///< Graphics pipeline
|
2024-11-15 15:25:36 -05:00
|
|
|
vk::UniquePipeline mOldPipeline; ///< Previous graphics pipeline for safe deletion
|
2024-10-18 20:00:24 -04:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
vk::UniquePipelineLayout mCrosshairPipelineLayout;
|
|
|
|
vk::UniquePipeline mCrosshairPipeline;
|
|
|
|
vk::UniqueBuffer mCrosshairVertexBuffer;
|
|
|
|
vk::UniqueDeviceMemory mCrosshairVertexBufferMemory;
|
|
|
|
vk::UniqueBuffer mCrosshairIndexBuffer;
|
|
|
|
vk::UniqueDeviceMemory mCrosshairIndexBufferMemory;
|
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueCommandPool mCommandPool; ///< Command pool for allocating command buffers
|
2024-10-18 20:00:24 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueImage mColorImage; ///< Color image
|
|
|
|
vk::UniqueDeviceMemory mColorImageMemory; ///< Memory for color image
|
|
|
|
vk::UniqueImageView mColorImageView; ///< Image view for color image
|
2024-10-18 20:00:24 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueImage mDepthImage; ///< Depth image
|
|
|
|
vk::UniqueDeviceMemory mDepthImageMemory; ///< Memory for depth image
|
|
|
|
vk::UniqueImageView mDepthImageView; ///< Image view for depth image
|
|
|
|
|
|
|
|
u32 mMipLevels; ///< Number of mipmap levels
|
|
|
|
vk::UniqueImage mTextureImage; ///< Texture image
|
|
|
|
vk::UniqueDeviceMemory mTextureImageMemory; ///< Memory for texture image
|
|
|
|
vk::UniqueImageView mTextureImageView; ///< Image view for texture
|
|
|
|
vk::UniqueSampler mTextureSampler; ///< Sampler for texture
|
|
|
|
|
|
|
|
std::vector<Vertex> mVertices; ///< Vertex data for the model
|
|
|
|
std::vector<u32> mIndices; ///< Index data for the model
|
|
|
|
vk::UniqueBuffer mVertexBuffer; ///< Buffer for vertex data
|
|
|
|
vk::UniqueDeviceMemory mVertexBufferMemory; ///< Memory for vertex buffer
|
|
|
|
vk::UniqueBuffer mIndexBuffer; ///< Buffer for index data
|
|
|
|
vk::UniqueDeviceMemory mIndexBufferMemory; ///< Memory for index buffer
|
|
|
|
|
|
|
|
std::vector<vk::UniqueBuffer> mUniformBuffers; ///< Uniform buffers for shader parameters
|
|
|
|
std::vector<vk::UniqueDeviceMemory> mUniformBuffersMemory; ///< Memory for uniform buffers
|
|
|
|
std::vector<void*> mUniformBuffersMapped; ///< Mapped uniform buffers
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
std::vector<vk::UniqueBuffer> mLightUniformBuffers; ///< Uniform buffers for light parameters
|
|
|
|
std::vector<vk::UniqueDeviceMemory> mLightUniformBuffersMemory; ///< Memory for light uniform buffers
|
|
|
|
std::vector<void*> mLightUniformBuffersMapped; ///< Mapped light uniform buffers
|
|
|
|
|
|
|
|
std::vector<vk::UniqueBuffer> mCameraUniformBuffers; ///< Uniform buffers for camera parameters
|
|
|
|
std::vector<vk::UniqueDeviceMemory> mCameraUniformBuffersMemory; ///< Memory for camera uniform buffers
|
|
|
|
std::vector<void*> mCameraUniformBuffersMapped; ///< Mapped camera uniform buffers
|
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
vk::UniqueDescriptorPool mDescriptorPool; ///< Descriptor pool for the application
|
|
|
|
vk::UniqueDescriptorPool mImGuiDescriptorPool; ///< Separate descriptor pool for ImGui
|
|
|
|
std::vector<vk::DescriptorSet> mDescriptorSets; ///< Descriptor sets for binding resources
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
std::vector<vk::UniqueCommandBuffer> mCommandBuffers; ///< Command buffers for drawing commands
|
|
|
|
|
|
|
|
// Light settings
|
|
|
|
struct {
|
|
|
|
glm::vec3 position = glm::vec3(2.0F, 2.0F, 2.0F);
|
|
|
|
glm::vec3 color = glm::vec3(1.0F, 1.0F, 1.0F);
|
|
|
|
float ambient_strength = 0.1F;
|
|
|
|
float specular_strength = 0.5F;
|
|
|
|
} mLightSettings;
|
2024-10-19 00:41:14 -04:00
|
|
|
|
|
|
|
std::vector<vk::UniqueSemaphore>
|
|
|
|
mImageAvailableSemaphores; ///< Signals that an image is available for rendering
|
|
|
|
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores; ///< Signals that rendering has finished
|
|
|
|
std::vector<vk::UniqueFence> mInFlightFences; ///< Ensures CPU-GPU synchronization
|
2024-11-15 15:25:36 -05:00
|
|
|
std::vector<vk::Fence> mImagesInFlight; ///< Tracks which fences are in use by which swap chain images
|
2024-10-19 00:41:14 -04:00
|
|
|
|
|
|
|
bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized
|
|
|
|
u32 mCurrentFrame = 0; ///< Index of the current frame being rendered
|
2024-10-18 20:00:24 -04:00
|
|
|
|
2024-10-23 13:10:18 -04:00
|
|
|
glm::mat4 mView; ///< View matrix
|
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
// Mouse input tracking
|
|
|
|
bool mFirstMouse = true; ///< Flag for first mouse movement
|
|
|
|
f64 mLastX = WIDTH / 2.0; ///< Last mouse X position
|
|
|
|
f64 mLastY = HEIGHT / 2.0; ///< Last mouse Y position
|
|
|
|
bool mCursorCaptured = true; ///< Flag indicating if cursor is captured
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// ImGui-related state
|
|
|
|
f32 mCameraSpeed = CAMERA_SPEED; ///< Current camera speed
|
|
|
|
f32 mFieldOfView = 90.0F; ///< Current field of view
|
|
|
|
bool mWireframeMode = false; ///< Wireframe rendering mode
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
2024-10-19 00:41:14 -04:00
|
|
|
* @brief Struct to store queue family indices.
|
2024-10-18 00:29:10 -04:00
|
|
|
*
|
2024-10-19 00:41:14 -04:00
|
|
|
* This struct contains the indices of the graphics and presentation queue families.
|
2024-10-18 00:29:10 -04:00
|
|
|
*/
|
2024-10-19 00:41:14 -04:00
|
|
|
struct QueueFamilyIndices {
|
|
|
|
std::optional<u32> graphics_family; ///< Index of graphics queue family
|
|
|
|
std::optional<u32> present_family; ///< Index of presentation queue family
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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(); }
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
/**
|
|
|
|
* @brief Struct to hold swap chain support details.
|
|
|
|
*
|
|
|
|
* This struct contains information about the surface capabilities,
|
|
|
|
* supported formats, and presentation modes.
|
|
|
|
*/
|
|
|
|
struct SwapChainSupportDetails {
|
|
|
|
vk::SurfaceCapabilitiesKHR capabilities; ///< Surface capabilities
|
|
|
|
std::vector<vk::SurfaceFormatKHR> formats; ///< Supported surface formats
|
|
|
|
std::vector<vk::PresentModeKHR> present_modes; ///< Supported presentation modes
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-19 00:41:14 -04:00
|
|
|
/**
|
|
|
|
* @brief Struct representing a uniform buffer object.
|
|
|
|
*
|
|
|
|
* This struct holds the model, view, and projection matrices for use in shaders.
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
struct LightInfo {
|
|
|
|
alignas(16) glm::vec3 position; ///< Light position
|
|
|
|
alignas(16) glm::vec3 color; ///< Light color
|
|
|
|
alignas(4) float ambient_strength; ///< Ambient strength
|
|
|
|
alignas(4) float specular_strength; ///< Specular strength
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CameraInfo {
|
|
|
|
alignas(16) glm::vec3 position; ///< Camera position
|
|
|
|
};
|
|
|
|
|
2024-10-23 13:10:18 -04:00
|
|
|
struct Camera {
|
|
|
|
glm::dvec3 position;
|
2024-11-15 11:52:37 -05:00
|
|
|
glm::dvec3 front;
|
2024-10-23 13:10:18 -04:00
|
|
|
glm::dvec3 up;
|
2024-11-15 11:52:37 -05:00
|
|
|
glm::dvec3 right;
|
2024-11-15 15:25:36 -05:00
|
|
|
f64 yaw;
|
|
|
|
f64 pitch;
|
2024-11-15 11:52:37 -05:00
|
|
|
|
|
|
|
Camera()
|
2024-11-15 21:25:10 -05:00
|
|
|
: position(2.0, 2.0, 0.5),
|
|
|
|
front(glm::normalize(glm::dvec3(0.0, 1.0, 0.0))),
|
2024-11-15 11:52:37 -05:00
|
|
|
up(0.0, 0.0, 1.0),
|
2024-11-15 21:25:10 -05:00
|
|
|
right(glm::normalize(glm::cross(front, glm::dvec3(0.0, 0.0, 1.0)))),
|
|
|
|
yaw(180.0),
|
|
|
|
pitch(0.0) {
|
2024-11-15 11:52:37 -05:00
|
|
|
updateCameraVectors();
|
|
|
|
}
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-11-07 00:50:58 -05:00
|
|
|
[[nodiscard]] fn getPosition() const -> glm::dvec3 { return position; }
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
[[nodiscard]] fn getFront() const -> glm::dvec3 { return front; }
|
|
|
|
|
|
|
|
[[nodiscard]] fn getYaw() const -> f64 { return yaw; }
|
|
|
|
|
|
|
|
[[nodiscard]] fn getPitch() const -> f64 { return pitch; }
|
|
|
|
|
2024-11-15 11:52:37 -05:00
|
|
|
[[nodiscard]] fn getViewMatrix() const -> glm::mat4 {
|
|
|
|
return glm::lookAt(position, position + front, up);
|
|
|
|
}
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
fn moveForward(f64 deltaTime) -> void {
|
|
|
|
// Project front vector onto horizontal plane by zeroing Z component
|
|
|
|
glm::dvec3 horizontalFront = front;
|
|
|
|
horizontalFront.z = 0.0;
|
|
|
|
horizontalFront = glm::normalize(horizontalFront);
|
|
|
|
position += horizontalFront * CAMERA_SPEED * deltaTime;
|
|
|
|
}
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
fn moveBackward(f64 deltaTime) -> void {
|
|
|
|
// Project front vector onto horizontal plane by zeroing Z component
|
|
|
|
glm::dvec3 horizontalFront = front;
|
|
|
|
horizontalFront.z = 0.0;
|
|
|
|
horizontalFront = glm::normalize(horizontalFront);
|
|
|
|
position -= horizontalFront * CAMERA_SPEED * deltaTime;
|
|
|
|
}
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
fn moveLeft(f64 deltaTime) -> void {
|
|
|
|
// Project right vector onto horizontal plane by zeroing Z component
|
|
|
|
glm::dvec3 horizontalRight = right;
|
|
|
|
horizontalRight.z = 0.0;
|
|
|
|
horizontalRight = glm::normalize(horizontalRight);
|
|
|
|
position -= horizontalRight * CAMERA_SPEED * deltaTime;
|
|
|
|
}
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
fn moveRight(f64 deltaTime) -> void {
|
|
|
|
// Project right vector onto horizontal plane by zeroing Z component
|
|
|
|
glm::dvec3 horizontalRight = right;
|
|
|
|
horizontalRight.z = 0.0;
|
|
|
|
horizontalRight = glm::normalize(horizontalRight);
|
|
|
|
position += horizontalRight * CAMERA_SPEED * deltaTime;
|
|
|
|
}
|
2024-11-15 11:52:37 -05:00
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
fn moveUp(f64 deltaTime) -> void { position += glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
|
|
|
|
|
|
|
|
fn moveDown(f64 deltaTime) -> void { position -= glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
fn rotate(f64 xoffset, f64 yoffset) -> void {
|
|
|
|
const f64 sensitivity = 0.1;
|
2024-11-15 11:52:37 -05:00
|
|
|
yaw += xoffset * sensitivity;
|
|
|
|
pitch += yoffset * sensitivity;
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Clamp yaw to [-180, 180] range
|
|
|
|
if (yaw > 180.0)
|
|
|
|
yaw -= 360.0;
|
|
|
|
if (yaw < -180.0)
|
|
|
|
yaw += 360.0;
|
|
|
|
|
2024-11-15 11:52:37 -05:00
|
|
|
// Constrain pitch to avoid camera flipping
|
|
|
|
if (pitch > 89.0)
|
|
|
|
pitch = 89.0;
|
|
|
|
if (pitch < -89.0)
|
|
|
|
pitch = -89.0;
|
|
|
|
|
|
|
|
updateCameraVectors();
|
2024-10-23 13:10:18 -04:00
|
|
|
}
|
|
|
|
|
2024-11-15 11:52:37 -05:00
|
|
|
private:
|
|
|
|
fn updateCameraVectors() -> void {
|
|
|
|
// Calculate new front vector
|
|
|
|
glm::dvec3 newFront;
|
|
|
|
newFront.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
|
|
|
|
newFront.y = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
|
|
|
|
newFront.z = sin(glm::radians(pitch));
|
|
|
|
|
|
|
|
front = glm::normalize(newFront);
|
|
|
|
// Recalculate right and up vectors
|
|
|
|
right = glm::normalize(glm::cross(front, glm::dvec3(0.0, 0.0, 1.0)));
|
|
|
|
up = glm::normalize(glm::cross(right, front));
|
2024-10-23 13:10:18 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-11-15 11:52:37 -05:00
|
|
|
Camera mCamera; ///< Camera object
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
static fn processInput(vkfw::Window& window, Camera& camera, const f32& deltaTime, const f32& cameraSpeed)
|
|
|
|
-> void {
|
2024-10-23 13:10:18 -04:00
|
|
|
if (window.getKey(vkfw::Key::eW) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveForward(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-10-23 13:10:18 -04:00
|
|
|
if (window.getKey(vkfw::Key::eA) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveLeft(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-10-23 13:10:18 -04:00
|
|
|
if (window.getKey(vkfw::Key::eS) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveBackward(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-10-23 13:10:18 -04:00
|
|
|
if (window.getKey(vkfw::Key::eD) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveRight(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-11-15 12:40:13 -05:00
|
|
|
if (window.getKey(vkfw::Key::eSpace) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveUp(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-11-15 12:40:13 -05:00
|
|
|
if (window.getKey(vkfw::Key::eLeftShift) == vkfw::eTrue)
|
2024-11-15 15:25:36 -05:00
|
|
|
camera.moveDown(static_cast<f64>(deltaTime * cameraSpeed));
|
2024-10-23 13:10:18 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Initializes the application window using GLFW.
|
|
|
|
*
|
|
|
|
* This function performs the following tasks:
|
|
|
|
* 1. Initializes the GLFW library.
|
|
|
|
* 2. Creates a window with the specified dimensions and title.
|
|
|
|
* 3. Sets up a callback for window resize events.
|
|
|
|
*
|
|
|
|
* The window is created without a default OpenGL context, as we'll be using Vulkan.
|
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn initWindow() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Initialize GLFW
|
2024-10-12 15:39:20 -04:00
|
|
|
mVKFWInstance = vkfw::initUnique();
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Set window creation hints
|
|
|
|
vkfw::WindowHints hints { .clientAPI = vkfw::ClientAPI::eNone }; // No OpenGL context
|
2024-09-30 16:46:17 -04:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Get the primary monitor and its resolution
|
|
|
|
vkfw::Monitor primaryMonitor = vkfw::getPrimaryMonitor();
|
|
|
|
const GLFWvidmode* videoMode = primaryMonitor.getVideoMode();
|
|
|
|
|
|
|
|
// Calculate window position to center it
|
|
|
|
i32 xpos = (videoMode->width - WIDTH) / 2;
|
|
|
|
i32 ypos = (videoMode->height - HEIGHT) / 2;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the window
|
2024-09-30 16:50:58 -04:00
|
|
|
mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Set window position
|
|
|
|
mWindow->setPos(xpos, ypos);
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Set the user pointer to this instance, allowing us to access it in callbacks
|
2024-11-15 12:40:13 -05:00
|
|
|
mWindow->setUserPointer(this);
|
2024-11-15 11:52:37 -05:00
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
// Configure cursor for FPS-style camera control
|
|
|
|
mWindow->set<vkfw::InputMode::eCursor>(vkfw::CursorMode::eDisabled);
|
2024-11-15 11:52:37 -05:00
|
|
|
|
|
|
|
// Set up mouse callback
|
2024-11-15 12:40:13 -05:00
|
|
|
mWindow->callbacks()->on_cursor_move =
|
2024-11-15 21:25:10 -05:00
|
|
|
[this](const vkfw::Window& /*window*/, f64 mouseX, f64 mouseY) -> void {
|
2024-11-15 12:40:13 -05:00
|
|
|
if (!mCursorCaptured)
|
|
|
|
return; // Skip camera movement when cursor is not captured
|
|
|
|
|
|
|
|
if (mFirstMouse) {
|
2024-11-15 21:25:10 -05:00
|
|
|
mLastX = mouseX;
|
|
|
|
mLastY = mouseY;
|
2024-11-15 12:40:13 -05:00
|
|
|
mFirstMouse = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
f64 xoffset = mouseX - mLastX;
|
|
|
|
f64 yoffset = mLastY - mouseY; // Reversed since y-coordinates range from bottom to top
|
2024-11-15 12:40:13 -05:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
mLastX = mouseX;
|
|
|
|
mLastY = mouseY;
|
2024-11-15 12:40:13 -05:00
|
|
|
|
|
|
|
mCamera.rotate(-xoffset, yoffset); // Invert xoffset for correct horizontal movement
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set up key callback for escape
|
|
|
|
mWindow->callbacks()->on_key = [this](
|
|
|
|
const vkfw::Window& window,
|
|
|
|
const vkfw::Key& key,
|
|
|
|
const i32& /*scancode*/,
|
|
|
|
const vkfw::KeyAction& action,
|
|
|
|
const vkfw::ModifierKeyFlags& /*mods*/
|
|
|
|
) -> void {
|
|
|
|
if (key == vkfw::Key::eEscape && action == vkfw::KeyAction::ePress) {
|
|
|
|
mCursorCaptured = false;
|
|
|
|
window.set<vkfw::InputMode::eCursor>(vkfw::CursorMode::eNormal);
|
|
|
|
}
|
2024-11-15 21:25:10 -05:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
if (key == vkfw::Key::eR && action == vkfw::KeyAction::ePress) {
|
|
|
|
try {
|
|
|
|
mDevice->waitIdle();
|
|
|
|
createGraphicsPipeline();
|
|
|
|
fmt::println("Shaders reloaded successfully!");
|
|
|
|
} catch (const std::exception& e) { fmt::println(stderr, "Failed to reload shaders: {}", e.what()); }
|
|
|
|
}
|
2024-11-15 12:40:13 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// Set up mouse button callback for re-capture
|
|
|
|
mWindow->callbacks()->on_mouse_button = [this](
|
|
|
|
const vkfw::Window& window,
|
|
|
|
const vkfw::MouseButton& button,
|
|
|
|
const vkfw::MouseButtonAction& action,
|
|
|
|
const vkfw::ModifierKeyFlags& /*mods*/
|
|
|
|
) -> void {
|
|
|
|
if (button == vkfw::MouseButton::eLeft && action == vkfw::MouseButtonAction::ePress &&
|
|
|
|
!mCursorCaptured) {
|
2024-11-15 15:25:36 -05:00
|
|
|
// Only capture cursor if click is not on ImGui window
|
|
|
|
if (!ImGui::GetIO().WantCaptureMouse) {
|
|
|
|
mCursorCaptured = true;
|
|
|
|
mFirstMouse = true; // Reset first mouse flag to avoid jumps
|
|
|
|
window.set<vkfw::InputMode::eCursor>(vkfw::CursorMode::eDisabled);
|
|
|
|
}
|
2024-11-15 12:40:13 -05:00
|
|
|
}
|
|
|
|
};
|
2024-10-14 23:32:52 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
// Set up the window resize callback
|
2024-10-12 15:39:20 -04:00
|
|
|
mWindow->callbacks()->on_window_resize =
|
2024-11-15 12:40:13 -05:00
|
|
|
[this](const vkfw::Window& /*window*/, usize /*width*/, usize /*height*/) -> void {
|
2024-10-18 00:29:10 -04:00
|
|
|
// Set the framebuffer resized flag when the window is resized
|
2024-11-15 12:40:13 -05:00
|
|
|
mFramebufferResized = true;
|
2024-10-12 15:39:20 -04:00
|
|
|
};
|
2024-09-25 23:03:56 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Initializes Vulkan by setting up all necessary components.
|
|
|
|
*
|
|
|
|
* This function calls a series of helper functions to set up the Vulkan environment.
|
|
|
|
* It creates and configures all the Vulkan objects needed for rendering, including:
|
|
|
|
* - Vulkan instance
|
|
|
|
* - Debug messenger (for validation layers)
|
|
|
|
* - Surface (for presenting rendered images)
|
|
|
|
* - Physical and logical devices
|
|
|
|
* - Swap chain
|
|
|
|
* - Render pass and graphics pipeline
|
|
|
|
* - Buffers and images
|
|
|
|
* - Synchronization objects
|
|
|
|
*
|
|
|
|
* The order of these function calls is important, as many Vulkan objects depend on
|
|
|
|
* others that must be created first.
|
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn initVulkan() -> void {
|
2024-11-15 21:25:10 -05:00
|
|
|
createInstance(); // Create the Vulkan instance
|
|
|
|
setupDebugMessenger(); // Set up debug messaging (validation layers)
|
|
|
|
createSurface(); // Create the window surface
|
|
|
|
pickPhysicalDevice(); // Select a suitable GPU
|
|
|
|
createLogicalDevice(); // Create a logical device from the chosen GPU
|
|
|
|
createSwapChain(); // Create the swap chain for presenting images
|
|
|
|
createImageViews(); // Create image views for the swap chain images
|
|
|
|
createRenderPass(); // Set up the render pass
|
|
|
|
createDescriptorSetLayout(); // Create the descriptor set layout
|
|
|
|
createGraphicsPipeline(); // Create the graphics pipeline
|
|
|
|
createCrosshairPipeline(); // Create the crosshair pipeline
|
|
|
|
createCommandPool(); // Create a command pool for allocating command buffers
|
|
|
|
createColorResources(); // Create resources for multisampling
|
|
|
|
createDepthResources(); // Create resources for depth testing
|
|
|
|
createFramebuffers(); // Create framebuffers for rendering
|
|
|
|
createTextureImage(); // Load and create the texture image
|
|
|
|
createTextureImageView(); // Create an image view for the texture
|
|
|
|
createTextureSampler(); // Create a sampler for the texture
|
|
|
|
loadModel(); // Load the 3D model
|
|
|
|
createVertexBuffer(); // Create a buffer for vertex data
|
|
|
|
createIndexBuffer(); // Create a buffer for index data
|
|
|
|
createUniformBuffers(); // Create uniform buffers for shader parameters
|
|
|
|
createLightUniformBuffers(); // Create uniform buffers for light parameters
|
|
|
|
createCameraUniformBuffers(); // Create uniform buffers for camera parameters
|
|
|
|
createDescriptorPool(); // Create a descriptor pool
|
|
|
|
createDescriptorSets(); // Allocate and update descriptor sets
|
|
|
|
createCommandBuffers(); // Create command buffers for rendering commands
|
|
|
|
createCrosshairBuffers(); // Create crosshair buffers
|
|
|
|
createSyncObjects(); // Create synchronization objects (semaphores and fences)
|
|
|
|
initImGui(); // Initialize Dear ImGui for GUI rendering
|
2024-10-15 19:49:39 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Initializes the ImGui library for use with GLFW and Vulkan.
|
|
|
|
*
|
|
|
|
* This function sets up ImGui for rendering with Vulkan and GLFW. It creates the ImGui context,
|
|
|
|
* sets up the style, initializes ImGui for GLFW and Vulkan, and creates a descriptor pool for ImGui.
|
|
|
|
*/
|
2024-10-17 23:14:22 -04:00
|
|
|
fn initImGui() -> void {
|
2024-10-15 19:49:39 -04:00
|
|
|
// Create ImGui context
|
|
|
|
IMGUI_CHECKVERSION();
|
|
|
|
ImGui::CreateContext();
|
|
|
|
|
2024-10-18 13:35:59 -04:00
|
|
|
// Remember to use a reference here, otherwise
|
|
|
|
// the ImGui::GetIO() function will return a
|
|
|
|
// copy and the changes won't be saved.
|
|
|
|
ImGuiIO& imGuiIO = ImGui::GetIO();
|
|
|
|
|
|
|
|
// Disable writing imgui.ini
|
|
|
|
imGuiIO.IniFilename = nullptr;
|
|
|
|
|
2024-10-15 19:49:39 -04:00
|
|
|
// Setup Dear ImGui style
|
|
|
|
ImGui::StyleColorsDark();
|
|
|
|
|
|
|
|
// Initialize ImGui for GLFW and Vulkan
|
|
|
|
ImGui_ImplGlfw_InitForVulkan(mWindow.get(), true);
|
|
|
|
|
2024-10-15 21:53:36 -04:00
|
|
|
vk::DescriptorPoolSize descriptorPoolSize = {
|
|
|
|
.type = vk::DescriptorType::eCombinedImageSampler,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
};
|
2024-10-15 19:49:39 -04:00
|
|
|
|
2024-10-15 21:53:36 -04:00
|
|
|
vk::DescriptorPoolCreateInfo poolInfo {
|
|
|
|
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
|
|
|
|
.maxSets = 1,
|
|
|
|
.poolSizeCount = 1,
|
|
|
|
.pPoolSizes = &descriptorPoolSize,
|
|
|
|
};
|
2024-10-15 19:49:39 -04:00
|
|
|
|
2024-10-15 21:53:36 -04:00
|
|
|
mImGuiDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo);
|
|
|
|
|
|
|
|
ImGui_ImplVulkan_InitInfo initInfo = {
|
|
|
|
.Instance = mInstance.get(),
|
|
|
|
.PhysicalDevice = mPhysicalDevice,
|
|
|
|
.Device = mDevice.get(),
|
|
|
|
.QueueFamily = findQueueFamilies(mPhysicalDevice).graphics_family.value(),
|
|
|
|
.Queue = mGraphicsQueue,
|
|
|
|
.DescriptorPool = mImGuiDescriptorPool.get(),
|
|
|
|
.RenderPass = mRenderPass.get(),
|
2024-10-18 13:35:50 -04:00
|
|
|
.MinImageCount = MAX_FRAMES_IN_FLIGHT,
|
2024-10-19 00:41:14 -04:00
|
|
|
.ImageCount = static_cast<u32>(mSwapChainImages.size()),
|
2024-10-15 21:53:36 -04:00
|
|
|
.MSAASamples = static_cast<VkSampleCountFlagBits>(mMsaaSamples),
|
|
|
|
.PipelineCache = VK_NULL_HANDLE,
|
|
|
|
.Subpass = 0,
|
|
|
|
.UseDynamicRendering = false,
|
|
|
|
.PipelineRenderingCreateInfo = {},
|
|
|
|
.Allocator = nullptr,
|
|
|
|
.CheckVkResultFn = nullptr,
|
|
|
|
.MinAllocationSize = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
ImGui_ImplVulkan_Init(&initInfo);
|
2024-09-29 23:02:04 -04:00
|
|
|
}
|
2024-09-28 14:54:39 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief The main rendering loop of the application.
|
|
|
|
*
|
|
|
|
* This function contains the main loop that runs while the window is open. It continuously
|
|
|
|
* polls for events and draws frames until the window is closed.
|
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn mainLoop() -> void {
|
2024-11-15 12:40:13 -05:00
|
|
|
f64 lastFrame = 0.0;
|
|
|
|
f64 deltaTime = 0.0;
|
|
|
|
f64 lastFpsUpdate = 0.0;
|
|
|
|
i32 frameCounter = 0;
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-10-01 18:54:41 -04:00
|
|
|
while (!mWindow->shouldClose()) {
|
2024-10-23 13:10:18 -04:00
|
|
|
f64 currentFrame = vkfw::getTime();
|
|
|
|
deltaTime = currentFrame - lastFrame;
|
|
|
|
lastFrame = currentFrame;
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
processInput(mWindow.get(), mCamera, static_cast<f32>(deltaTime), mCameraSpeed);
|
2024-11-15 11:52:37 -05:00
|
|
|
mView = mCamera.getViewMatrix();
|
2024-10-23 13:10:18 -04:00
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
if (currentFrame - lastFpsUpdate > 1.0) {
|
|
|
|
mWindow->setTitle(
|
|
|
|
fmt::format("Vulkan - {:.0f}FPS", static_cast<f32>(frameCounter / (currentFrame - lastFpsUpdate)))
|
|
|
|
);
|
|
|
|
lastFpsUpdate = currentFrame;
|
|
|
|
frameCounter = 0;
|
|
|
|
}
|
|
|
|
++frameCounter;
|
2024-10-18 00:29:10 -04:00
|
|
|
|
2024-10-10 21:36:42 -04:00
|
|
|
vkfw::pollEvents();
|
2024-11-15 21:25:10 -05:00
|
|
|
|
|
|
|
// Start the ImGui frame
|
|
|
|
ImGui_ImplVulkan_NewFrame();
|
|
|
|
ImGui_ImplGlfw_NewFrame();
|
|
|
|
ImGui::NewFrame();
|
|
|
|
|
|
|
|
// Create ImGui window with controls
|
|
|
|
ImGui::Begin("Controls");
|
|
|
|
|
|
|
|
// Camera Position
|
|
|
|
auto pos = mCamera.getPosition();
|
|
|
|
ImGui::Text("Camera Position: (%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z);
|
|
|
|
|
|
|
|
// Camera Settings
|
|
|
|
if (ImGui::CollapsingHeader("Camera Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
|
|
ImGui::SliderFloat("Camera Speed", &mCameraSpeed, 0.1F, 10.0F, "%.1f");
|
|
|
|
ImGui::SliderFloat("Field of View", &mFieldOfView, 45.0F, 120.0F, "%.1f");
|
|
|
|
if (ImGui::Button("Reset Camera")) {
|
|
|
|
mCamera = Camera(); // Reset to default position
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rendering Settings
|
|
|
|
if (ImGui::CollapsingHeader("Rendering Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
|
|
// Wireframe Mode
|
|
|
|
if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) {
|
|
|
|
recreateSwapChain();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Line Width (only in wireframe mode)
|
|
|
|
static float LineWidth = 1.0F;
|
|
|
|
if (mWireframeMode) {
|
|
|
|
if (ImGui::SliderFloat("Line Width", &LineWidth, 1.0F, 5.0F, "%.1f")) {
|
|
|
|
// TODO: Update line width in pipeline
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Controls Help
|
|
|
|
if (ImGui::CollapsingHeader("Controls", ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
|
|
ImGui::BulletText("Use mouse to look around");
|
|
|
|
ImGui::BulletText("WASD to move horizontally");
|
|
|
|
ImGui::BulletText("Space/Shift to move up/down");
|
|
|
|
ImGui::BulletText("ESC to toggle mouse capture");
|
|
|
|
ImGui::BulletText("Tab to toggle this menu");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performance Metrics
|
|
|
|
if (ImGui::CollapsingHeader("Performance", ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
|
|
ImGui::Text(
|
|
|
|
"Application average %.3f ms/frame (%.1f FPS)",
|
|
|
|
static_cast<f64>(1000.0F / ImGui::GetIO().Framerate),
|
|
|
|
static_cast<f64>(ImGui::GetIO().Framerate)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Memory Usage
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGui::Text("Memory Usage:");
|
|
|
|
ImGui::Text(
|
|
|
|
"Vertex Buffer: %.2f MB",
|
|
|
|
(static_cast<double>(mVertices.size()) * static_cast<double>(sizeof(Vertex))) / (1024.0 * 1024.0)
|
|
|
|
);
|
|
|
|
ImGui::Text(
|
|
|
|
"Index Buffer: %.2f MB",
|
|
|
|
(static_cast<double>(mIndices.size()) * static_cast<double>(sizeof(uint32_t))) / (1024.0 * 1024.0)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Camera Information
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGui::Text("Camera Information:");
|
|
|
|
ImGui::Text(
|
|
|
|
"Position: (%.2f, %.2f, %.2f)",
|
|
|
|
mCamera.getPosition().x,
|
|
|
|
mCamera.getPosition().y,
|
|
|
|
mCamera.getPosition().z
|
|
|
|
);
|
|
|
|
ImGui::Text(
|
|
|
|
"Front Vector: (%.2f, %.2f, %.2f)", mCamera.getFront().x, mCamera.getFront().y, mCamera.getFront().z
|
|
|
|
);
|
|
|
|
ImGui::Text("Yaw: %.2f°, Pitch: %.2f°", mCamera.getYaw(), mCamera.getPitch());
|
|
|
|
|
|
|
|
// Light Controls
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGui::Text("Light Controls:");
|
|
|
|
|
|
|
|
// Light Position
|
|
|
|
ImGui::DragFloat3("Light Position", &mLightSettings.position.x, 0.1F, -10.0F, 10.0F);
|
|
|
|
|
|
|
|
// Light Color
|
|
|
|
ImGui::ColorEdit3("Light Color", &mLightSettings.color.x);
|
|
|
|
|
|
|
|
// Light Strengths
|
|
|
|
ImGui::SliderFloat("Ambient Strength", &mLightSettings.ambient_strength, 0.0F, 1.0F);
|
|
|
|
ImGui::SliderFloat("Specular Strength", &mLightSettings.specular_strength, 0.0F, 1.0F);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::End();
|
|
|
|
|
|
|
|
// Render ImGui
|
|
|
|
ImGui::Render();
|
|
|
|
|
2024-10-01 18:54:41 -04:00
|
|
|
drawFrame();
|
|
|
|
}
|
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mDevice->waitIdle();
|
2024-09-28 14:54:39 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Cleans up the swap chain resources.
|
|
|
|
*
|
|
|
|
* This function destroys the framebuffers and image views associated with the swap chain.
|
|
|
|
*/
|
2024-10-05 23:08:12 -04:00
|
|
|
fn cleanupSwapChain() -> void {
|
2024-10-11 19:09:37 -04:00
|
|
|
for (vk::UniqueFramebuffer& mSwapChainFramebuffer : mSwapChainFramebuffers) mSwapChainFramebuffer.reset();
|
|
|
|
for (vk::UniqueImageView& mSwapChainImageView : mSwapChainImageViews) mSwapChainImageView.reset();
|
2024-10-05 23:08:12 -04:00
|
|
|
|
|
|
|
mSwapChain.reset();
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Recreates the swap chain.
|
|
|
|
*
|
|
|
|
* This function is called when the swap chain needs to be recreated, such as when the window is resized.
|
|
|
|
* It cleans up the old swap chain and creates a new one with updated properties.
|
|
|
|
*/
|
2024-10-05 23:08:12 -04:00
|
|
|
fn recreateSwapChain() -> void {
|
2024-11-15 15:25:36 -05:00
|
|
|
i32 width = 0, height = 0;
|
2024-10-05 23:08:12 -04:00
|
|
|
while (width == 0 || height == 0) {
|
|
|
|
std::tie(width, height) = mWindow->getFramebufferSize();
|
|
|
|
vkfw::waitEvents();
|
|
|
|
}
|
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mDevice->waitIdle();
|
2024-10-05 23:08:12 -04:00
|
|
|
|
|
|
|
cleanupSwapChain();
|
|
|
|
|
|
|
|
createSwapChain();
|
|
|
|
createImageViews();
|
2024-11-15 15:25:36 -05:00
|
|
|
createRenderPass();
|
|
|
|
createGraphicsPipeline();
|
2024-11-15 21:25:10 -05:00
|
|
|
createCrosshairPipeline();
|
2024-10-11 21:14:36 -04:00
|
|
|
createColorResources();
|
2024-10-11 19:09:37 -04:00
|
|
|
createDepthResources();
|
2024-10-05 23:08:12 -04:00
|
|
|
createFramebuffers();
|
2024-11-15 15:25:36 -05:00
|
|
|
createUniformBuffers();
|
2024-11-15 21:25:10 -05:00
|
|
|
createLightUniformBuffers();
|
|
|
|
createCameraUniformBuffers();
|
2024-11-15 15:25:36 -05:00
|
|
|
createDescriptorPool();
|
|
|
|
createDescriptorSets();
|
|
|
|
createCommandBuffers();
|
2024-11-15 21:25:10 -05:00
|
|
|
createCrosshairBuffers();
|
2024-11-15 15:25:36 -05:00
|
|
|
|
|
|
|
mImagesInFlight.resize(mSwapChainImages.size(), nullptr);
|
2024-10-05 23:08:12 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the Vulkan instance.
|
|
|
|
*
|
|
|
|
* This function sets up the Vulkan instance, including application info, extensions, and validation layers.
|
|
|
|
*/
|
2024-09-25 23:03:56 -04:00
|
|
|
fn createInstance() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
// Make sure validation layers are supported
|
|
|
|
if (!checkValidationLayerSupport())
|
2024-09-30 21:21:38 -04:00
|
|
|
throw std::runtime_error("Validation layers requested, but not available!");
|
2024-10-14 23:32:52 -04:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Application metadata
|
|
|
|
vk::ApplicationInfo appInfo {
|
2024-10-18 14:34:26 -04:00
|
|
|
.pApplicationName = "Vulkan App",
|
2024-10-14 23:32:52 -04:00
|
|
|
.applicationVersion = 1,
|
|
|
|
.pEngineName = "No Engine",
|
|
|
|
.engineVersion = 1,
|
2024-10-19 00:41:14 -04:00
|
|
|
.apiVersion = vk::ApiVersion13,
|
2024-10-14 23:32:52 -04:00
|
|
|
};
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the required extensions
|
|
|
|
std::span<const char*> extensionsSpan = vkfw::getRequiredInstanceExtensions();
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Convert the span to a vector
|
|
|
|
std::vector extensions(extensionsSpan.begin(), extensionsSpan.end());
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// Add the debug utils extension
|
|
|
|
extensions.emplace_back(vk::EXTDebugUtilsExtensionName);
|
|
|
|
#endif
|
2024-09-26 19:56:19 -04:00
|
|
|
|
2024-10-01 16:10:20 -04:00
|
|
|
#ifdef __APPLE__
|
2024-10-14 23:32:52 -04:00
|
|
|
// Add the portability extension
|
2024-10-01 14:15:39 -04:00
|
|
|
extensions.emplace_back(vk::KHRPortabilityEnumerationExtensionName);
|
2024-10-14 23:32:52 -04:00
|
|
|
// Technically deprecated but Vulkan complains if I don't include it for macOS,
|
|
|
|
// so instead of using the vk::KHRPortabilitySubsetExtensionName, I just use
|
2024-10-01 14:15:39 -04:00
|
|
|
// the direct string.
|
|
|
|
extensions.emplace_back("VK_KHR_get_physical_device_properties2");
|
2024-09-30 21:21:38 -04:00
|
|
|
#endif
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the instance
|
2024-09-29 23:05:15 -04:00
|
|
|
vk::InstanceCreateInfo createInfo {
|
2024-10-01 16:57:40 -04:00
|
|
|
#ifdef __APPLE__
|
2024-10-14 23:32:52 -04:00
|
|
|
// Enable the portability extension
|
2024-10-01 16:57:40 -04:00
|
|
|
.flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR,
|
|
|
|
#endif
|
2024-10-14 23:32:52 -04:00
|
|
|
.pApplicationInfo = &appInfo,
|
|
|
|
#ifdef NDEBUG
|
|
|
|
.enabledLayerCount = 0,
|
|
|
|
.ppEnabledLayerNames = nullptr,
|
|
|
|
#else
|
|
|
|
.enabledLayerCount = static_cast<u32>(validationLayers.size()),
|
|
|
|
.ppEnabledLayerNames = validationLayers.data(),
|
|
|
|
#endif
|
2024-09-30 17:52:49 -04:00
|
|
|
.enabledExtensionCount = static_cast<u32>(extensions.size()),
|
2024-09-30 00:31:08 -04:00
|
|
|
.ppEnabledExtensionNames = extensions.data()
|
2024-09-29 23:05:15 -04:00
|
|
|
};
|
2024-09-26 17:18:45 -04:00
|
|
|
|
2024-09-30 00:31:08 -04:00
|
|
|
#ifndef NDEBUG
|
2024-09-29 23:02:04 -04:00
|
|
|
fmt::println("Available extensions:");
|
|
|
|
for (const char* extension : extensions) fmt::println("\t{}", extension);
|
2024-09-30 00:31:08 -04:00
|
|
|
#endif
|
2024-09-25 23:03:56 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the instance
|
2024-10-06 18:14:15 -04:00
|
|
|
mInstance = vk::createInstanceUnique(createInfo);
|
2024-10-06 17:34:25 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Load the instance functions
|
2024-10-06 12:26:44 -04:00
|
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init(mInstance.get());
|
2024-09-28 14:54:39 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Sets up the debug messenger for Vulkan validation layers.
|
|
|
|
*
|
|
|
|
* This function creates a debug messenger that handles validation layer messages in debug builds.
|
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn setupDebugMessenger() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
#ifdef NDEBUG
|
|
|
|
return;
|
|
|
|
#endif
|
2024-09-28 18:13:24 -04:00
|
|
|
|
2024-09-30 00:31:08 -04:00
|
|
|
vk::DebugUtilsMessengerCreateInfoEXT messengerCreateInfo {
|
|
|
|
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
|
|
|
|
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
|
|
|
|
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
|
|
|
|
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
|
|
|
|
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
|
|
|
|
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
|
2024-10-12 15:39:20 -04:00
|
|
|
.pfnUserCallback = debugCallback
|
2024-09-30 00:31:08 -04:00
|
|
|
};
|
2024-09-28 18:13:24 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mDebugMessenger = mInstance->createDebugUtilsMessengerEXTUnique(messengerCreateInfo, nullptr);
|
2024-09-30 00:31:08 -04:00
|
|
|
}
|
2024-09-28 18:13:24 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the window surface for rendering.
|
|
|
|
*
|
|
|
|
* This function creates a Vulkan surface for the GLFW window, which is used for presenting rendered images.
|
|
|
|
*/
|
2024-09-30 16:46:17 -04:00
|
|
|
fn createSurface() -> void { mSurface = vkfw::createWindowSurfaceUnique(mInstance.get(), mWindow.get()); }
|
2024-09-30 00:57:13 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Selects a suitable physical device (GPU) for Vulkan.
|
|
|
|
*
|
|
|
|
* This function enumerates available physical devices and selects the first one that meets
|
|
|
|
* the application's requirements.
|
|
|
|
*/
|
2024-09-30 00:31:08 -04:00
|
|
|
fn pickPhysicalDevice() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get all physical devices
|
2024-10-06 18:14:15 -04:00
|
|
|
std::vector<vk::PhysicalDevice> devices = mInstance->enumeratePhysicalDevices();
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Make sure there are supported devices
|
2024-10-06 18:14:15 -04:00
|
|
|
if (devices.empty())
|
2024-09-30 00:31:08 -04:00
|
|
|
throw std::runtime_error("Failed to find GPUs with Vulkan support!");
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
fmt::println("Available devices:");
|
|
|
|
#endif
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// For each device,
|
2024-10-06 18:14:15 -04:00
|
|
|
for (const vk::PhysicalDevice& device : devices) {
|
2024-09-30 00:31:08 -04:00
|
|
|
#ifndef NDEBUG
|
|
|
|
vk::PhysicalDeviceProperties properties = device.getProperties();
|
2024-09-30 19:49:44 -04:00
|
|
|
fmt::println("\t{}", properties.deviceName.data());
|
2024-09-30 00:31:08 -04:00
|
|
|
#endif
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Set the first suitable device as the physical device
|
2024-09-30 00:31:08 -04:00
|
|
|
if (isDeviceSuitable(device)) {
|
|
|
|
mPhysicalDevice = device;
|
2024-10-11 21:14:36 -04:00
|
|
|
mMsaaSamples = getMaxUsableSampleCount();
|
2024-09-30 00:31:08 -04:00
|
|
|
break;
|
|
|
|
}
|
2024-09-28 18:13:24 -04:00
|
|
|
}
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If no suitable device was found, throw an error
|
2024-09-30 00:31:08 -04:00
|
|
|
if (!mPhysicalDevice)
|
|
|
|
throw std::runtime_error("Failed to find a suitable GPU!");
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the logical device and retrieves queue handles.
|
|
|
|
*
|
|
|
|
* This function creates a logical device from the selected physical device, enabling required
|
|
|
|
* features and retrieving handles to the graphics and presentation queues.
|
|
|
|
*/
|
2024-09-30 00:57:13 -04:00
|
|
|
fn createLogicalDevice() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the queue families
|
2024-10-10 18:51:20 -04:00
|
|
|
QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice);
|
2024-09-30 00:57:13 -04:00
|
|
|
|
|
|
|
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::set<u32> uniqueQueueFamilies = {
|
|
|
|
qfIndices.graphics_family.value(),
|
|
|
|
qfIndices.present_family.value(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set the queue priority
|
2024-09-30 00:57:13 -04:00
|
|
|
f32 queuePriority = 1.0F;
|
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// For each unique queue family, create a queue create info
|
|
|
|
queueCreateInfos.reserve(uniqueQueueFamilies.size());
|
|
|
|
|
|
|
|
for (u32 queueFamily : uniqueQueueFamilies)
|
|
|
|
queueCreateInfos.push_back({
|
2024-10-14 23:32:52 -04:00
|
|
|
.queueFamilyIndex = queueFamily,
|
|
|
|
.queueCount = 1,
|
|
|
|
.pQueuePriorities = &queuePriority,
|
2024-11-15 15:25:36 -05:00
|
|
|
});
|
2024-09-30 00:57:13 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Enable required features
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::PhysicalDeviceFeatures deviceFeatures {
|
2024-11-15 15:25:36 -05:00
|
|
|
.fillModeNonSolid = vk::True, // Required for wireframe rendering
|
|
|
|
.wideLines = vk::True, // Required for line width > 1.0
|
2024-10-11 00:42:58 -04:00
|
|
|
.samplerAnisotropy = vk::True,
|
|
|
|
};
|
2024-09-30 00:57:13 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Create the logical device
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::DeviceCreateInfo createInfo {
|
|
|
|
.queueCreateInfoCount = static_cast<u32>(queueCreateInfos.size()),
|
|
|
|
.pQueueCreateInfos = queueCreateInfos.data(),
|
|
|
|
.enabledExtensionCount = static_cast<u32>(deviceExtensions.size()),
|
|
|
|
.ppEnabledExtensionNames = deviceExtensions.data(),
|
|
|
|
.pEnabledFeatures = &deviceFeatures,
|
|
|
|
};
|
2024-09-30 00:57:13 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
mDevice = mPhysicalDevice.createDeviceUnique(createInfo);
|
|
|
|
|
|
|
|
// Get the graphics and present queues
|
2024-10-10 18:51:20 -04:00
|
|
|
mGraphicsQueue = mDevice->getQueue(qfIndices.graphics_family.value(), 0);
|
|
|
|
mPresentQueue = mDevice->getQueue(qfIndices.present_family.value(), 0);
|
2024-09-30 00:57:13 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the swap chain for image presentation.
|
|
|
|
*
|
|
|
|
* This function sets up the swap chain, which is a queue of images that can be presented to the screen.
|
|
|
|
* It determines the format, presentation mode, and extent of the swap chain images.
|
|
|
|
*/
|
2024-10-01 16:57:40 -04:00
|
|
|
fn createSwapChain() -> void {
|
|
|
|
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(mPhysicalDevice);
|
|
|
|
|
|
|
|
vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
|
|
|
|
vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.present_modes);
|
|
|
|
vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
|
|
|
|
|
|
|
|
u32 imageCount = swapChainSupport.capabilities.minImageCount + 1;
|
|
|
|
|
|
|
|
if (swapChainSupport.capabilities.maxImageCount > 0 &&
|
|
|
|
imageCount > swapChainSupport.capabilities.maxImageCount)
|
|
|
|
imageCount = swapChainSupport.capabilities.maxImageCount;
|
|
|
|
|
2024-10-10 18:51:20 -04:00
|
|
|
QueueFamilyIndices qfIndices = findQueueFamilies(mPhysicalDevice);
|
2024-10-14 23:32:52 -04:00
|
|
|
std::array<u32, 2> queueFamilyIndices = {
|
|
|
|
qfIndices.graphics_family.value(),
|
|
|
|
qfIndices.present_family.value(),
|
|
|
|
};
|
2024-10-01 16:57:40 -04:00
|
|
|
|
|
|
|
vk::SwapchainCreateInfoKHR createInfo {
|
|
|
|
.surface = mSurface.get(),
|
|
|
|
.minImageCount = imageCount,
|
|
|
|
.imageFormat = surfaceFormat.format,
|
|
|
|
.imageColorSpace = surfaceFormat.colorSpace,
|
|
|
|
.imageExtent = extent,
|
|
|
|
.imageArrayLayers = 1,
|
|
|
|
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
|
2024-10-10 18:51:20 -04:00
|
|
|
.imageSharingMode = qfIndices.graphics_family != qfIndices.present_family ? vk::SharingMode::eConcurrent
|
|
|
|
: vk::SharingMode::eExclusive,
|
|
|
|
.queueFamilyIndexCount =
|
|
|
|
static_cast<u32>(qfIndices.graphics_family != qfIndices.present_family ? 2 : 0),
|
2024-10-01 16:57:40 -04:00
|
|
|
.pQueueFamilyIndices =
|
2024-10-10 18:51:20 -04:00
|
|
|
qfIndices.graphics_family != qfIndices.present_family ? queueFamilyIndices.data() : nullptr,
|
2024-10-01 16:57:40 -04:00
|
|
|
.preTransform = swapChainSupport.capabilities.currentTransform,
|
|
|
|
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
|
|
|
.presentMode = presentMode,
|
2024-10-01 18:30:31 -04:00
|
|
|
.clipped = vk::True,
|
2024-10-01 16:57:40 -04:00
|
|
|
.oldSwapchain = nullptr,
|
|
|
|
};
|
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mSwapChain = mDevice->createSwapchainKHRUnique(createInfo);
|
2024-10-06 17:34:25 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mSwapChainImages = mDevice->getSwapchainImagesKHR(mSwapChain.get());
|
2024-10-01 16:57:40 -04:00
|
|
|
mSwapChainImageFormat = surfaceFormat.format;
|
|
|
|
mSwapChainExtent = extent;
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates image views for the swap chain images.
|
|
|
|
*
|
|
|
|
* This function creates a view for each image in the swap chain, which is used to access the image
|
|
|
|
* contents.
|
|
|
|
*/
|
2024-10-01 17:06:14 -04:00
|
|
|
fn createImageViews() -> void {
|
|
|
|
mSwapChainImageViews.resize(mSwapChainImages.size());
|
|
|
|
|
2024-10-11 19:09:37 -04:00
|
|
|
for (u32 i = 0; i < mSwapChainImages.size(); i++)
|
|
|
|
mSwapChainImageViews[i] =
|
2024-10-11 20:50:23 -04:00
|
|
|
createImageView(mSwapChainImages[i], mSwapChainImageFormat, vk::ImageAspectFlagBits::eColor, 1);
|
2024-10-01 16:10:20 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the render pass.
|
|
|
|
*
|
|
|
|
* This function sets up the render pass, which describes the structure of rendering operations,
|
|
|
|
* including the number and format of attachments used during rendering.
|
|
|
|
*/
|
2024-10-01 18:30:31 -04:00
|
|
|
fn createRenderPass() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::AttachmentDescription colorAttachment {
|
|
|
|
.format = mSwapChainImageFormat,
|
|
|
|
.samples = mMsaaSamples,
|
|
|
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
|
|
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
|
|
|
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
|
|
|
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
|
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
|
|
.finalLayout = vk::ImageLayout::eColorAttachmentOptimal,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::AttachmentDescription depthAttachment {
|
|
|
|
.format = findDepthFormat(),
|
|
|
|
.samples = mMsaaSamples,
|
|
|
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
|
|
|
.storeOp = vk::AttachmentStoreOp::eDontCare,
|
|
|
|
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
|
|
|
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
|
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
|
|
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::AttachmentDescription colorAttachmentResolve {
|
|
|
|
.format = mSwapChainImageFormat,
|
|
|
|
.samples = vk::SampleCountFlagBits::e1,
|
|
|
|
.loadOp = vk::AttachmentLoadOp::eDontCare,
|
|
|
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
|
|
|
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
|
|
|
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
|
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
|
|
.finalLayout = vk::ImageLayout::ePresentSrcKHR,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::AttachmentReference colorAttachmentRef {
|
|
|
|
.attachment = 0,
|
|
|
|
.layout = vk::ImageLayout::eColorAttachmentOptimal,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::AttachmentReference depthAttachmentRef {
|
|
|
|
.attachment = 1,
|
|
|
|
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::AttachmentReference colorAttachmentResolveRef {
|
|
|
|
.attachment = 2,
|
|
|
|
.layout = vk::ImageLayout::eColorAttachmentOptimal,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::SubpassDescription subpass {
|
|
|
|
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
|
|
|
.colorAttachmentCount = 1,
|
|
|
|
.pColorAttachments = &colorAttachmentRef,
|
|
|
|
.pResolveAttachments = &colorAttachmentResolveRef,
|
|
|
|
.pDepthStencilAttachment = &depthAttachmentRef,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::SubpassDependency dependency {
|
|
|
|
.srcSubpass = vk::SubpassExternal,
|
|
|
|
.dstSubpass = {},
|
|
|
|
.srcStageMask =
|
|
|
|
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
|
|
|
.dstStageMask =
|
|
|
|
vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
|
|
|
.srcAccessMask = {},
|
|
|
|
.dstAccessMask =
|
|
|
|
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
|
|
|
};
|
|
|
|
|
|
|
|
std::array<vk::AttachmentDescription, 3> attachments = {
|
|
|
|
colorAttachment,
|
|
|
|
depthAttachment,
|
|
|
|
colorAttachmentResolve,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::RenderPassCreateInfo renderPassInfo {
|
|
|
|
.attachmentCount = static_cast<u32>(attachments.size()),
|
|
|
|
.pAttachments = attachments.data(),
|
|
|
|
.subpassCount = 1,
|
|
|
|
.pSubpasses = &subpass,
|
|
|
|
.dependencyCount = 1,
|
|
|
|
.pDependencies = &dependency,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mRenderPass = mDevice->createRenderPassUnique(renderPassInfo);
|
2024-10-01 18:30:31 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the descriptor set layout.
|
|
|
|
*
|
|
|
|
* This function defines the layout of descriptor sets used in the shader, specifying the types
|
|
|
|
* of resources that will be accessed by the shader.
|
|
|
|
*/
|
2024-10-10 20:39:04 -04:00
|
|
|
fn createDescriptorSetLayout() -> void {
|
|
|
|
vk::DescriptorSetLayoutBinding uboLayoutBinding {
|
|
|
|
.binding = 0,
|
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.stageFlags = vk::ShaderStageFlagBits::eVertex,
|
|
|
|
.pImmutableSamplers = nullptr,
|
|
|
|
};
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
vk::DescriptorSetLayoutBinding lightLayoutBinding {
|
2024-10-11 00:42:58 -04:00
|
|
|
.binding = 1,
|
2024-11-15 21:25:10 -05:00
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
|
|
|
.pImmutableSamplers = nullptr,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::DescriptorSetLayoutBinding cameraLayoutBinding {
|
|
|
|
.binding = 2,
|
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
|
|
|
.pImmutableSamplers = nullptr,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::DescriptorSetLayoutBinding samplerLayoutBinding {
|
|
|
|
.binding = 3,
|
2024-10-11 00:42:58 -04:00
|
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
|
|
|
.pImmutableSamplers = nullptr,
|
|
|
|
};
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
std::array<vk::DescriptorSetLayoutBinding, 4> bindings = {
|
|
|
|
uboLayoutBinding,
|
|
|
|
lightLayoutBinding,
|
|
|
|
cameraLayoutBinding,
|
|
|
|
samplerLayoutBinding,
|
|
|
|
};
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-10 20:39:04 -04:00
|
|
|
vk::DescriptorSetLayoutCreateInfo layoutInfo {
|
2024-10-11 00:42:58 -04:00
|
|
|
.bindingCount = static_cast<u32>(bindings.size()),
|
|
|
|
.pBindings = bindings.data(),
|
2024-10-10 20:39:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
mDescriptorSetLayout = mDevice->createDescriptorSetLayoutUnique(layoutInfo);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the graphics pipeline.
|
|
|
|
*
|
|
|
|
* This function sets up the entire graphics pipeline, including shader stages, vertex input,
|
|
|
|
* input assembly, viewport, rasterization, multisampling, depth testing, color blending, and dynamic
|
|
|
|
* states.
|
|
|
|
*/
|
2024-10-01 18:30:31 -04:00
|
|
|
fn createGraphicsPipeline() -> void {
|
2024-10-18 20:00:24 -04:00
|
|
|
std::vector<u32> vertShaderCode =
|
2024-11-15 15:25:36 -05:00
|
|
|
ShaderCompiler::getCompiledShader(VERTEX_SHADER_PATH, shaderc_shader_kind::shaderc_vertex_shader);
|
2024-10-18 20:00:24 -04:00
|
|
|
std::vector<u32> fragShaderCode =
|
2024-11-15 15:25:36 -05:00
|
|
|
ShaderCompiler::getCompiledShader(FRAGMENT_SHADER_PATH, shaderc_shader_kind::shaderc_fragment_shader);
|
2024-10-01 18:30:31 -04:00
|
|
|
|
|
|
|
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
|
|
|
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineShaderStageCreateInfo vertShaderStageInfo {
|
|
|
|
.stage = vk::ShaderStageFlagBits::eVertex,
|
|
|
|
.module = vertShaderModule.get(),
|
|
|
|
.pName = "main",
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineShaderStageCreateInfo fragShaderStageInfo {
|
|
|
|
.stage = vk::ShaderStageFlagBits::eFragment,
|
|
|
|
.module = fragShaderModule.get(),
|
|
|
|
.pName = "main",
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages = {
|
|
|
|
vertShaderStageInfo,
|
|
|
|
fragShaderStageInfo,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-09 23:00:45 -04:00
|
|
|
vk::VertexInputBindingDescription bindingDescription = Vertex::getBindingDescription();
|
2024-11-15 21:25:10 -05:00
|
|
|
std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions =
|
2024-10-09 23:00:45 -04:00
|
|
|
Vertex::getAttributeDescriptions();
|
|
|
|
|
2024-10-01 18:30:31 -04:00
|
|
|
vk::PipelineVertexInputStateCreateInfo vertexInputInfo {
|
2024-10-09 23:00:45 -04:00
|
|
|
.vertexBindingDescriptionCount = 1,
|
|
|
|
.pVertexBindingDescriptions = &bindingDescription,
|
|
|
|
.vertexAttributeDescriptionCount = static_cast<u32>(attributeDescriptions.size()),
|
2024-10-14 23:32:52 -04:00
|
|
|
.pVertexAttributeDescriptions = attributeDescriptions.data(),
|
2024-10-01 18:30:31 -04:00
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineInputAssemblyStateCreateInfo inputAssembly {
|
|
|
|
.topology = vk::PrimitiveTopology::eTriangleList,
|
|
|
|
.primitiveRestartEnable = vk::False,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineViewportStateCreateInfo viewportState {
|
|
|
|
.viewportCount = 1,
|
|
|
|
.scissorCount = 1,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineRasterizationStateCreateInfo rasterizer {
|
|
|
|
.depthClampEnable = vk::False,
|
|
|
|
.rasterizerDiscardEnable = vk::False,
|
2024-11-15 15:25:36 -05:00
|
|
|
.polygonMode = mWireframeMode ? vk::PolygonMode::eLine : vk::PolygonMode::eFill,
|
2024-10-14 23:32:52 -04:00
|
|
|
.cullMode = vk::CullModeFlagBits::eBack,
|
|
|
|
.frontFace = vk::FrontFace::eCounterClockwise,
|
|
|
|
.depthBiasEnable = vk::False,
|
2024-11-15 15:25:36 -05:00
|
|
|
.lineWidth = mWireframeMode ? 2.0F : 1.0F, // Thicker lines in wireframe mode
|
2024-10-14 23:32:52 -04:00
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineMultisampleStateCreateInfo multisampling {
|
|
|
|
.rasterizationSamples = mMsaaSamples,
|
|
|
|
.sampleShadingEnable = vk::False,
|
|
|
|
};
|
2024-10-11 19:09:37 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineDepthStencilStateCreateInfo depthStencil {
|
|
|
|
.depthTestEnable = vk::True,
|
|
|
|
.depthWriteEnable = vk::True,
|
|
|
|
.depthCompareOp = vk::CompareOp::eLess,
|
|
|
|
.depthBoundsTestEnable = vk::False,
|
|
|
|
.stencilTestEnable = vk::False,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
|
|
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment {
|
|
|
|
.blendEnable = vk::False,
|
|
|
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
|
2024-10-01 18:30:31 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
vk::PipelineColorBlendStateCreateInfo colorBlending {
|
|
|
|
.logicOpEnable = vk::False,
|
|
|
|
.logicOp = vk::LogicOp::eCopy,
|
|
|
|
.attachmentCount = 1,
|
|
|
|
.pAttachments = &colorBlendAttachment,
|
2024-11-15 15:25:36 -05:00
|
|
|
.blendConstants = std::array<f32, 4> { 0.0F, 0.0F, 0.0F, 0.0F },
|
2024-10-01 18:30:31 -04:00
|
|
|
};
|
2024-10-10 20:39:04 -04:00
|
|
|
|
2024-10-01 18:30:31 -04:00
|
|
|
std::vector<vk::DynamicState> dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineDynamicStateCreateInfo dynamicState {
|
|
|
|
.dynamicStateCount = static_cast<u32>(dynamicStates.size()),
|
|
|
|
.pDynamicStates = dynamicStates.data(),
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo {
|
|
|
|
.setLayoutCount = 1,
|
|
|
|
.pSetLayouts = &mDescriptorSetLayout.get(),
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mPipelineLayout = mDevice->createPipelineLayoutUnique(pipelineLayoutInfo);
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::GraphicsPipelineCreateInfo pipelineInfo {
|
|
|
|
.stageCount = static_cast<u32>(shaderStages.size()),
|
|
|
|
.pStages = shaderStages.data(),
|
|
|
|
.pVertexInputState = &vertexInputInfo,
|
|
|
|
.pInputAssemblyState = &inputAssembly,
|
|
|
|
.pViewportState = &viewportState,
|
|
|
|
.pRasterizationState = &rasterizer,
|
|
|
|
.pMultisampleState = &multisampling,
|
|
|
|
.pDepthStencilState = &depthStencil,
|
|
|
|
.pColorBlendState = &colorBlending,
|
|
|
|
.pDynamicState = &dynamicState,
|
|
|
|
.layout = mPipelineLayout.get(),
|
|
|
|
.renderPass = mRenderPass.get(),
|
|
|
|
.subpass = 0,
|
|
|
|
};
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-06 17:34:25 -04:00
|
|
|
vk::Result graphicsPipelineResult = vk::Result::eSuccess;
|
|
|
|
vk::UniquePipeline graphicsPipelineValue;
|
|
|
|
|
|
|
|
std::tie(graphicsPipelineResult, graphicsPipelineValue) =
|
|
|
|
mDevice->createGraphicsPipelineUnique(nullptr, pipelineInfo).asTuple();
|
|
|
|
|
|
|
|
if (graphicsPipelineResult != vk::Result::eSuccess)
|
|
|
|
throw std::runtime_error("Failed to create graphics pipeline!");
|
|
|
|
|
|
|
|
mGraphicsPipeline = std::move(graphicsPipelineValue);
|
2024-10-01 18:30:31 -04:00
|
|
|
}
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
fn createCrosshairPipeline() -> void {
|
|
|
|
// Create pipeline layout (no descriptor sets or push constants needed)
|
|
|
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo {};
|
|
|
|
mCrosshairPipelineLayout = mDevice->createPipelineLayoutUnique(pipelineLayoutInfo);
|
|
|
|
|
|
|
|
// Load shaders
|
|
|
|
auto vertShaderCode =
|
|
|
|
ShaderCompiler::getCompiledShader(CROSSHAIR_VERTEX_SHADER_PATH, shaderc_vertex_shader);
|
|
|
|
auto fragShaderCode =
|
|
|
|
ShaderCompiler::getCompiledShader(CROSSHAIR_FRAGMENT_SHADER_PATH, shaderc_fragment_shader);
|
|
|
|
|
|
|
|
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
|
|
|
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
|
|
|
|
|
|
|
vk::PipelineShaderStageCreateInfo vertShaderStageInfo { .stage = vk::ShaderStageFlagBits::eVertex,
|
|
|
|
.module = vertShaderModule.get(),
|
|
|
|
.pName = "main" };
|
|
|
|
|
|
|
|
vk::PipelineShaderStageCreateInfo fragShaderStageInfo { .stage = vk::ShaderStageFlagBits::eFragment,
|
|
|
|
.module = fragShaderModule.get(),
|
|
|
|
.pName = "main" };
|
|
|
|
|
|
|
|
std::array shaderStages = { vertShaderStageInfo, fragShaderStageInfo };
|
|
|
|
|
|
|
|
// Vertex input
|
|
|
|
auto bindingDescription = CrosshairVertex::getBindingDescription();
|
|
|
|
auto attributeDescriptions = CrosshairVertex::getAttributeDescriptions();
|
|
|
|
|
|
|
|
vk::PipelineVertexInputStateCreateInfo vertexInputInfo {
|
|
|
|
.vertexBindingDescriptionCount = 1,
|
|
|
|
.pVertexBindingDescriptions = &bindingDescription,
|
|
|
|
.vertexAttributeDescriptionCount = static_cast<u32>(attributeDescriptions.size()),
|
|
|
|
.pVertexAttributeDescriptions = attributeDescriptions.data()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Input assembly
|
|
|
|
vk::PipelineInputAssemblyStateCreateInfo inputAssembly { .topology = vk::PrimitiveTopology::eLineList,
|
|
|
|
.primitiveRestartEnable = false };
|
|
|
|
|
|
|
|
// Viewport and scissor
|
|
|
|
vk::PipelineViewportStateCreateInfo viewportState { .viewportCount = 1, .scissorCount = 1 };
|
|
|
|
|
|
|
|
// Rasterization
|
|
|
|
vk::PipelineRasterizationStateCreateInfo rasterizer { .depthClampEnable = false,
|
|
|
|
.rasterizerDiscardEnable = false,
|
|
|
|
.polygonMode = vk::PolygonMode::eFill,
|
|
|
|
.cullMode = vk::CullModeFlagBits::eNone,
|
|
|
|
.frontFace = vk::FrontFace::eCounterClockwise,
|
|
|
|
.depthBiasEnable = false,
|
|
|
|
.lineWidth = 1.0F };
|
|
|
|
|
|
|
|
// Multisampling
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisampling { .rasterizationSamples = mMsaaSamples,
|
|
|
|
.sampleShadingEnable = false };
|
|
|
|
|
|
|
|
// Color blending
|
|
|
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment {
|
|
|
|
.blendEnable = false,
|
|
|
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::PipelineColorBlendStateCreateInfo colorBlending { .logicOpEnable = false,
|
|
|
|
.attachmentCount = 1,
|
|
|
|
.pAttachments = &colorBlendAttachment };
|
|
|
|
|
|
|
|
// Dynamic state
|
|
|
|
std::array dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
|
|
|
|
|
|
|
vk::PipelineDynamicStateCreateInfo dynamicState { .dynamicStateCount =
|
|
|
|
static_cast<u32>(dynamicStates.size()),
|
|
|
|
.pDynamicStates = dynamicStates.data() };
|
|
|
|
|
|
|
|
// Depth and stencil
|
|
|
|
vk::PipelineDepthStencilStateCreateInfo depthStencil { .depthTestEnable = false,
|
|
|
|
.depthWriteEnable = false,
|
|
|
|
.depthCompareOp = vk::CompareOp::eLess,
|
|
|
|
.depthBoundsTestEnable = false,
|
|
|
|
.stencilTestEnable = false };
|
|
|
|
|
|
|
|
// Create the pipeline
|
|
|
|
vk::GraphicsPipelineCreateInfo pipelineInfo { .stageCount = static_cast<u32>(shaderStages.size()),
|
|
|
|
.pStages = shaderStages.data(),
|
|
|
|
.pVertexInputState = &vertexInputInfo,
|
|
|
|
.pInputAssemblyState = &inputAssembly,
|
|
|
|
.pViewportState = &viewportState,
|
|
|
|
.pRasterizationState = &rasterizer,
|
|
|
|
.pMultisampleState = &multisampling,
|
|
|
|
.pDepthStencilState = &depthStencil,
|
|
|
|
.pColorBlendState = &colorBlending,
|
|
|
|
.pDynamicState = &dynamicState,
|
|
|
|
.layout = mCrosshairPipelineLayout.get(),
|
|
|
|
.renderPass = mRenderPass.get(),
|
|
|
|
.subpass = 0 };
|
|
|
|
|
|
|
|
mCrosshairPipeline = mDevice->createGraphicsPipelineUnique(nullptr, pipelineInfo).value;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn createCrosshairBuffers() -> void {
|
|
|
|
// Create vertex buffer
|
|
|
|
vk::DeviceSize bufferSize = sizeof(crosshairVertices[0]) * crosshairVertices.size();
|
|
|
|
|
|
|
|
auto stagingBuffer = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferSrc,
|
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
|
|
|
);
|
|
|
|
|
|
|
|
void* data = mDevice->mapMemory(stagingBuffer.second.get(), 0, bufferSize);
|
|
|
|
memcpy(data, crosshairVertices.data(), bufferSize);
|
|
|
|
mDevice->unmapMemory(stagingBuffer.second.get());
|
|
|
|
|
|
|
|
auto [vertexBuffer, vertexBufferMemory] = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer,
|
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
|
|
|
);
|
|
|
|
|
|
|
|
copyBuffer(stagingBuffer.first.get(), vertexBuffer.get(), bufferSize);
|
|
|
|
mCrosshairVertexBuffer = std::move(vertexBuffer);
|
|
|
|
mCrosshairVertexBufferMemory = std::move(vertexBufferMemory);
|
|
|
|
|
|
|
|
// Create index buffer
|
|
|
|
bufferSize = sizeof(crosshairIndices[0]) * crosshairIndices.size();
|
|
|
|
|
|
|
|
auto stagingBufferIndices = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferSrc,
|
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
|
|
|
);
|
|
|
|
|
|
|
|
data = mDevice->mapMemory(stagingBufferIndices.second.get(), 0, bufferSize);
|
|
|
|
memcpy(data, crosshairIndices.data(), bufferSize);
|
|
|
|
mDevice->unmapMemory(stagingBufferIndices.second.get());
|
|
|
|
|
|
|
|
auto [indexBuffer, indexBufferMemory] = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer,
|
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
|
|
|
);
|
|
|
|
|
|
|
|
copyBuffer(stagingBufferIndices.first.get(), indexBuffer.get(), bufferSize);
|
|
|
|
mCrosshairIndexBuffer = std::move(indexBuffer);
|
|
|
|
mCrosshairIndexBufferMemory = std::move(indexBufferMemory);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates framebuffers for the swap chain images.
|
|
|
|
*
|
|
|
|
* This function creates a framebuffer for each image view in the swap chain, attaching
|
|
|
|
* the color, depth, and resolve attachments.
|
|
|
|
*/
|
2024-10-01 18:33:05 -04:00
|
|
|
fn createFramebuffers() -> void {
|
|
|
|
mSwapChainFramebuffers.resize(mSwapChainImageViews.size());
|
|
|
|
|
|
|
|
for (usize i = 0; i < mSwapChainImageViews.size(); i++) {
|
2024-10-11 21:14:36 -04:00
|
|
|
std::array<vk::ImageView, 3> attachments = { mColorImageView.get(),
|
|
|
|
mDepthImageView.get(),
|
|
|
|
mSwapChainImageViews[i].get() };
|
2024-10-11 19:09:37 -04:00
|
|
|
|
|
|
|
vk::FramebufferCreateInfo framebufferInfo { .renderPass = mRenderPass.get(),
|
|
|
|
.attachmentCount = static_cast<u32>(attachments.size()),
|
|
|
|
.pAttachments = attachments.data(),
|
|
|
|
.width = mSwapChainExtent.width,
|
|
|
|
.height = mSwapChainExtent.height,
|
|
|
|
.layers = 1 };
|
2024-10-01 18:33:05 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mSwapChainFramebuffers[i] = mDevice->createFramebufferUnique(framebufferInfo);
|
2024-10-01 18:33:05 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the command pool.
|
|
|
|
*
|
|
|
|
* This function creates a command pool, which is used to manage the memory used to store
|
|
|
|
* the buffers from which command buffer memory is allocated.
|
|
|
|
*/
|
2024-10-01 18:54:41 -04:00
|
|
|
fn createCommandPool() -> void {
|
|
|
|
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(mPhysicalDevice);
|
|
|
|
|
2024-10-12 15:39:20 -04:00
|
|
|
vk::CommandPoolCreateInfo poolInfo { .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
|
|
|
.queueFamilyIndex = queueFamilyIndices.graphics_family.value() };
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
mCommandPool = mDevice->createCommandPoolUnique(poolInfo);
|
2024-10-01 18:54:41 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates resources for color attachment.
|
|
|
|
*
|
|
|
|
* This function creates the image, memory, and view for the color attachment used in multisampling.
|
|
|
|
*/
|
2024-10-11 21:14:36 -04:00
|
|
|
fn createColorResources() -> void {
|
|
|
|
vk::Format colorFormat = mSwapChainImageFormat;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(mColorImage, mColorImageMemory) = createImage(
|
2024-10-11 21:14:36 -04:00
|
|
|
mSwapChainExtent.width,
|
|
|
|
mSwapChainExtent.height,
|
|
|
|
1,
|
|
|
|
mMsaaSamples,
|
|
|
|
colorFormat,
|
|
|
|
vk::ImageTiling::eOptimal,
|
|
|
|
vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
2024-10-11 21:14:36 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
mColorImageView = createImageView(mColorImage.get(), colorFormat, vk::ImageAspectFlagBits::eColor, 1);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates resources for depth attachment.
|
|
|
|
*
|
|
|
|
* This function creates the image, memory, and view for the depth attachment used in depth testing.
|
|
|
|
*/
|
2024-10-11 19:09:37 -04:00
|
|
|
fn createDepthResources() -> void {
|
|
|
|
vk::Format depthFormat = findDepthFormat();
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(mDepthImage, mDepthImageMemory) = createImage(
|
2024-10-11 19:09:37 -04:00
|
|
|
mSwapChainExtent.width,
|
|
|
|
mSwapChainExtent.height,
|
2024-10-11 20:50:23 -04:00
|
|
|
1,
|
2024-10-11 21:14:36 -04:00
|
|
|
mMsaaSamples,
|
2024-10-11 19:09:37 -04:00
|
|
|
depthFormat,
|
|
|
|
vk::ImageTiling::eOptimal,
|
|
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
2024-10-11 19:09:37 -04:00
|
|
|
);
|
|
|
|
|
2024-10-11 20:50:23 -04:00
|
|
|
mDepthImageView = createImageView(mDepthImage.get(), depthFormat, vk::ImageAspectFlagBits::eDepth, 1);
|
2024-10-11 19:09:37 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Finds a supported format from a list of candidates.
|
|
|
|
*
|
|
|
|
* @param candidates A vector of candidate formats to check.
|
|
|
|
* @param tiling The desired tiling arrangement of the format.
|
|
|
|
* @param features The required format features.
|
|
|
|
* @return The first supported format from the list of candidates.
|
|
|
|
*
|
|
|
|
* This function iterates through a list of candidate formats and returns the first one
|
|
|
|
* that is supported with the specified tiling and features.
|
|
|
|
*/
|
2024-10-11 19:09:37 -04:00
|
|
|
fn findSupportedFormat(
|
|
|
|
const std::vector<vk::Format>& candidates,
|
2024-10-14 23:32:52 -04:00
|
|
|
const vk::ImageTiling& tiling,
|
|
|
|
const vk::FormatFeatureFlags& features
|
2024-10-11 19:09:37 -04:00
|
|
|
) -> vk::Format {
|
|
|
|
for (vk::Format format : candidates) {
|
|
|
|
vk::FormatProperties props = mPhysicalDevice.getFormatProperties(format);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
2024-10-11 19:09:37 -04:00
|
|
|
if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features)
|
|
|
|
return format;
|
2024-10-14 23:32:52 -04:00
|
|
|
|
2024-10-11 19:09:37 -04:00
|
|
|
if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features)
|
|
|
|
return format;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw std::runtime_error("Failed to find supported format!");
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Finds a supported depth format.
|
|
|
|
*
|
|
|
|
* @return A supported depth format.
|
|
|
|
*
|
|
|
|
* This function tries to find a supported depth format from a predefined list of candidates.
|
|
|
|
*/
|
2024-10-11 19:09:37 -04:00
|
|
|
fn findDepthFormat() -> vk::Format {
|
|
|
|
return findSupportedFormat(
|
|
|
|
{ vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint },
|
|
|
|
vk::ImageTiling::eOptimal,
|
|
|
|
vk::FormatFeatureFlagBits::eDepthStencilAttachment
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Checks if a format has a stencil component.
|
|
|
|
*
|
|
|
|
* @param format The format to check.
|
|
|
|
* @return True if the format has a stencil component, false otherwise.
|
|
|
|
*/
|
2024-10-18 14:34:26 -04:00
|
|
|
static fn hasStencilComponent(const vk::Format& format) {
|
2024-10-11 19:09:37 -04:00
|
|
|
return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint;
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the texture image.
|
|
|
|
*
|
|
|
|
* This function loads an image from a file, creates a staging buffer, transfers the image data
|
|
|
|
* to the staging buffer, creates the final texture image, and copies the data from the staging
|
|
|
|
* buffer to the texture image. It also generates mipmaps for the texture.
|
|
|
|
*/
|
2024-10-11 00:42:58 -04:00
|
|
|
fn createTextureImage() -> void {
|
2024-10-18 14:34:26 -04:00
|
|
|
std::filesystem::path texturePath = std::filesystem::current_path() / "textures" / "viking_room.png";
|
|
|
|
stb::UniqueImage image(texturePath);
|
2024-10-11 20:50:23 -04:00
|
|
|
|
2024-10-12 00:07:13 -04:00
|
|
|
u8* pixels = image.getData();
|
|
|
|
i32 texWidth = image.getWidth(), texHeight = image.getHeight();
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-11 20:50:23 -04:00
|
|
|
mMipLevels = static_cast<u32>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
|
|
|
|
|
2024-10-11 00:42:58 -04:00
|
|
|
if (!pixels)
|
|
|
|
throw std::runtime_error("Failed to load texture image!");
|
|
|
|
|
2024-11-15 11:52:37 -05:00
|
|
|
vk::DeviceSize imageSize =
|
|
|
|
static_cast<vk::DeviceSize>(texWidth) * static_cast<vk::DeviceSize>(texHeight) * 4;
|
|
|
|
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::UniqueBuffer stagingBuffer;
|
|
|
|
vk::UniqueDeviceMemory stagingBufferMemory;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(stagingBuffer, stagingBufferMemory) = createBuffer(
|
2024-10-11 00:42:58 -04:00
|
|
|
imageSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferSrc,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
2024-10-11 00:42:58 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
copyData(stagingBufferMemory.get(), imageSize, pixels);
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(mTextureImage, mTextureImageMemory) = createImage(
|
2024-10-11 00:42:58 -04:00
|
|
|
static_cast<u32>(texWidth),
|
|
|
|
static_cast<u32>(texHeight),
|
2024-10-11 20:50:23 -04:00
|
|
|
mMipLevels,
|
2024-10-11 21:14:36 -04:00
|
|
|
vk::SampleCountFlagBits::e1,
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::Format::eR8G8B8A8Srgb,
|
|
|
|
vk::ImageTiling::eOptimal,
|
2024-10-11 20:50:23 -04:00
|
|
|
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
|
|
|
|
vk::ImageUsageFlagBits::eSampled,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
2024-10-11 00:42:58 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
transitionImageLayout(
|
2024-10-11 20:50:23 -04:00
|
|
|
mTextureImage.get(), vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, mMipLevels
|
2024-10-11 00:42:58 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
copyBufferToImage(
|
|
|
|
stagingBuffer.get(), mTextureImage.get(), static_cast<u32>(texWidth), static_cast<u32>(texHeight)
|
|
|
|
);
|
|
|
|
|
2024-10-11 20:50:23 -04:00
|
|
|
generateMipmaps(mTextureImage.get(), vk::Format::eR8G8B8A8Srgb, texWidth, texHeight, mMipLevels);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Generates mipmaps for a texture image.
|
|
|
|
*
|
|
|
|
* @param image The image for which to generate mipmaps.
|
|
|
|
* @param imageFormat The format of the image.
|
|
|
|
* @param texWidth The width of the texture.
|
|
|
|
* @param texHeight The height of the texture.
|
|
|
|
* @param mipLevels The number of mipmap levels to generate.
|
|
|
|
*
|
|
|
|
* This function generates mipmaps for the given texture image by repeatedly scaling down
|
|
|
|
* the image by half until reaching the smallest mip level.
|
|
|
|
*/
|
2024-10-18 14:34:26 -04:00
|
|
|
fn generateMipmaps(
|
|
|
|
const vk::Image& image,
|
|
|
|
const vk::Format& imageFormat,
|
|
|
|
const i32& texWidth,
|
|
|
|
const i32& texHeight,
|
|
|
|
const u32& mipLevels
|
|
|
|
) -> void {
|
2024-10-11 20:50:23 -04:00
|
|
|
vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(imageFormat);
|
|
|
|
|
|
|
|
if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear))
|
|
|
|
throw std::runtime_error("Texture image format does not support linear blitting!");
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
2024-10-11 20:50:23 -04:00
|
|
|
|
|
|
|
vk::ImageMemoryBarrier barrier {
|
|
|
|
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
|
|
|
|
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
|
|
|
|
.image = image,
|
|
|
|
.subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
|
|
.levelCount = 1,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1 }
|
|
|
|
};
|
|
|
|
|
|
|
|
i32 mipWidth = texWidth;
|
|
|
|
i32 mipHeight = texHeight;
|
|
|
|
|
|
|
|
for (u32 i = 1; i < mipLevels; i++) {
|
|
|
|
barrier.subresourceRange.baseMipLevel = i - 1;
|
|
|
|
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
|
|
|
barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
|
|
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.pipelineBarrier(
|
2024-10-11 20:50:23 -04:00
|
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
|
|
{},
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
barrier
|
|
|
|
);
|
|
|
|
|
|
|
|
vk::ImageBlit blit {
|
|
|
|
.srcSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
|
|
.mipLevel = i - 1,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1 },
|
2024-10-11 22:34:15 -04:00
|
|
|
.srcOffsets = std::array<vk::Offset3D, 2> { { { .x = 0, .y = 0, .z = 0 },
|
|
|
|
{ .x = mipWidth, .y = mipHeight, .z = 1 } } },
|
2024-10-11 20:50:23 -04:00
|
|
|
.dstSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
|
|
.mipLevel = i,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1 },
|
2024-10-12 15:39:20 -04:00
|
|
|
.dstOffsets = std::array<vk::Offset3D, 2> { vk::Offset3D {
|
|
|
|
.x = 0,
|
|
|
|
.y = 0,
|
|
|
|
.z = 0,
|
|
|
|
}, vk::Offset3D {
|
|
|
|
.x = mipWidth > 1 ? mipWidth / 2 : 1,
|
|
|
|
.y = mipHeight > 1 ? mipHeight / 2 : 1,
|
|
|
|
.z = 1,
|
|
|
|
} }
|
2024-10-11 20:50:23 -04:00
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.blitImage(
|
2024-10-11 20:50:23 -04:00
|
|
|
image,
|
|
|
|
vk::ImageLayout::eTransferSrcOptimal,
|
|
|
|
image,
|
|
|
|
vk::ImageLayout::eTransferDstOptimal,
|
|
|
|
blit,
|
|
|
|
vk::Filter::eLinear
|
|
|
|
);
|
|
|
|
|
|
|
|
barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
|
|
|
|
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
|
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
|
|
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.pipelineBarrier(
|
2024-10-11 20:50:23 -04:00
|
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
|
|
vk::PipelineStageFlagBits::eFragmentShader,
|
|
|
|
{},
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
barrier
|
|
|
|
);
|
|
|
|
|
|
|
|
if (mipWidth > 1)
|
|
|
|
mipWidth /= 2;
|
|
|
|
if (mipHeight > 1)
|
|
|
|
mipHeight /= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
barrier.subresourceRange.baseMipLevel = mMipLevels - 1;
|
|
|
|
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
|
|
|
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
|
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.pipelineBarrier(
|
2024-10-11 20:50:23 -04:00
|
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
|
|
vk::PipelineStageFlagBits::eFragmentShader,
|
|
|
|
{},
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
barrier
|
2024-10-11 00:42:58 -04:00
|
|
|
);
|
2024-10-11 20:50:23 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
endSingleTimeCommands(commandBuffer);
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Gets the maximum usable sample count for multisampling.
|
|
|
|
*
|
|
|
|
* @return The maximum sample count supported by the device for both color and depth.
|
|
|
|
*
|
|
|
|
* This function determines the highest sample count that is supported by the device
|
|
|
|
* for both color and depth attachments.
|
|
|
|
*/
|
2024-10-11 21:14:36 -04:00
|
|
|
fn getMaxUsableSampleCount() -> vk::SampleCountFlagBits {
|
|
|
|
vk::PhysicalDeviceProperties physicalDeviceProperties = mPhysicalDevice.getProperties();
|
|
|
|
|
|
|
|
vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts &
|
|
|
|
physicalDeviceProperties.limits.framebufferDepthSampleCounts;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Define an array of sample counts in descending order
|
|
|
|
const std::array<vk::SampleCountFlagBits, 7> sampleCounts = {
|
|
|
|
vk::SampleCountFlagBits::e64, vk::SampleCountFlagBits::e32, vk::SampleCountFlagBits::e16,
|
|
|
|
vk::SampleCountFlagBits::e8, vk::SampleCountFlagBits::e4, vk::SampleCountFlagBits::e2,
|
|
|
|
vk::SampleCountFlagBits::e1,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Loop through the array and return the first supported sample count
|
|
|
|
for (const vk::SampleCountFlagBits& count : sampleCounts)
|
|
|
|
if (counts & count)
|
|
|
|
return count;
|
2024-10-11 21:14:36 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Return e1 if no other sample count is supported
|
2024-10-11 21:14:36 -04:00
|
|
|
return vk::SampleCountFlagBits::e1;
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the texture image view.
|
|
|
|
*
|
|
|
|
* This function creates an image view for the texture image, which can be used
|
|
|
|
* to access the texture in shaders.
|
|
|
|
*/
|
2024-10-11 00:42:58 -04:00
|
|
|
fn createTextureImageView() -> void {
|
2024-10-11 20:50:23 -04:00
|
|
|
mTextureImageView = createImageView(
|
|
|
|
mTextureImage.get(), vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor, mMipLevels
|
|
|
|
);
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the texture sampler.
|
|
|
|
*
|
|
|
|
* This function creates a sampler object that defines how the texture should be sampled in shaders.
|
|
|
|
*/
|
2024-10-11 00:42:58 -04:00
|
|
|
fn createTextureSampler() -> void {
|
|
|
|
vk::PhysicalDeviceProperties properties = mPhysicalDevice.getProperties();
|
|
|
|
|
|
|
|
vk::SamplerCreateInfo samplerInfo {
|
|
|
|
.magFilter = vk::Filter::eLinear,
|
|
|
|
.minFilter = vk::Filter::eLinear,
|
|
|
|
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
|
|
|
.addressModeU = vk::SamplerAddressMode::eRepeat,
|
|
|
|
.addressModeV = vk::SamplerAddressMode::eRepeat,
|
|
|
|
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
|
|
|
.mipLodBias = 0.0F,
|
|
|
|
.anisotropyEnable = vk::False,
|
|
|
|
.maxAnisotropy = properties.limits.maxSamplerAnisotropy,
|
|
|
|
.compareEnable = vk::False,
|
|
|
|
.compareOp = vk::CompareOp::eAlways,
|
|
|
|
.minLod = 0.0F,
|
2024-10-11 20:50:23 -04:00
|
|
|
.maxLod = static_cast<f32>(mMipLevels),
|
2024-10-11 00:42:58 -04:00
|
|
|
.borderColor = vk::BorderColor::eIntOpaqueBlack,
|
|
|
|
.unnormalizedCoordinates = vk::False,
|
|
|
|
};
|
|
|
|
|
|
|
|
mTextureSampler = mDevice->createSamplerUnique(samplerInfo);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates a Vulkan image view.
|
|
|
|
*
|
|
|
|
* This function creates and returns a unique Vulkan image view using the provided parameters.
|
|
|
|
*
|
|
|
|
* @param image The Vulkan image for which to create the view.
|
|
|
|
* @param format The format of the image.
|
|
|
|
* @param aspectFlags The aspect flags for the image view.
|
|
|
|
* @param mipLevels The number of mip levels for the image view.
|
|
|
|
*
|
|
|
|
* @return vk::UniqueImageView A unique handle to the created Vulkan image view.
|
|
|
|
*
|
|
|
|
* @details
|
|
|
|
* The function creates an image view with the following properties:
|
|
|
|
* - 2D view type
|
|
|
|
* - Subresource range starting from base mip level 0
|
|
|
|
* - Single array layer starting from base array layer 0
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn createImageView(
|
|
|
|
const vk::Image& image,
|
|
|
|
const vk::Format& format,
|
|
|
|
const vk::ImageAspectFlags& aspectFlags,
|
|
|
|
const u32& mipLevels
|
|
|
|
) -> vk::UniqueImageView {
|
2024-10-18 00:29:10 -04:00
|
|
|
return mDevice->createImageViewUnique({
|
|
|
|
.image = image,
|
|
|
|
.viewType = vk::ImageViewType::e2D,
|
|
|
|
.format = format,
|
|
|
|
.subresourceRange = {
|
|
|
|
.aspectMask = aspectFlags,
|
|
|
|
.baseMipLevel = 0,
|
|
|
|
.levelCount = mipLevels,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1,
|
|
|
|
},
|
|
|
|
});
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn createImage(
|
2024-10-14 23:32:52 -04:00
|
|
|
const u32& width,
|
|
|
|
const u32& height,
|
|
|
|
const u32& mipLevels,
|
|
|
|
const vk::SampleCountFlagBits& numSamples,
|
|
|
|
const vk::Format& format,
|
|
|
|
const vk::ImageTiling& tiling,
|
|
|
|
const vk::ImageUsageFlags& usage,
|
|
|
|
const vk::MemoryPropertyFlags& properties
|
|
|
|
) -> std::pair<vk::UniqueImage, vk::UniqueDeviceMemory> {
|
|
|
|
// Define the image creation info
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::ImageCreateInfo imageInfo {
|
|
|
|
.imageType = vk::ImageType::e2D,
|
|
|
|
.format = format,
|
|
|
|
.extent = { .width = width, .height = height, .depth = 1 },
|
2024-10-11 20:50:23 -04:00
|
|
|
.mipLevels = mipLevels,
|
2024-10-11 00:42:58 -04:00
|
|
|
.arrayLayers = 1,
|
2024-10-11 21:14:36 -04:00
|
|
|
.samples = numSamples,
|
2024-10-11 00:42:58 -04:00
|
|
|
.tiling = tiling,
|
|
|
|
.usage = usage,
|
|
|
|
.sharingMode = vk::SharingMode::eExclusive,
|
|
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the image
|
|
|
|
vk::UniqueImage image = mDevice->createImageUnique(imageInfo);
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the memory requirements for the image
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::MemoryRequirements memRequirements = mDevice->getImageMemoryRequirements(image.get());
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Memory allocation info
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::MemoryAllocateInfo allocInfo {
|
|
|
|
.allocationSize = memRequirements.size,
|
|
|
|
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties),
|
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Allocate memory
|
|
|
|
vk::UniqueDeviceMemory imageMemory = mDevice->allocateMemoryUnique(allocInfo);
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Bind the allocated memory to the image
|
2024-10-11 00:42:58 -04:00
|
|
|
mDevice->bindImageMemory(image.get(), imageMemory.get(), 0);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Return the unique image
|
|
|
|
return { std::move(image), std::move(imageMemory) };
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Transition image between layouts
|
2024-10-11 00:42:58 -04:00
|
|
|
fn transitionImageLayout(
|
2024-10-14 23:32:52 -04:00
|
|
|
const vk::Image& image,
|
|
|
|
const vk::ImageLayout& oldLayout,
|
|
|
|
const vk::ImageLayout& newLayout,
|
|
|
|
const u32& mipLevels
|
2024-10-11 00:42:58 -04:00
|
|
|
) -> void {
|
2024-10-15 20:38:05 -04:00
|
|
|
// Create a command buffer
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Define the image memory barrier
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::ImageMemoryBarrier barrier {
|
|
|
|
.oldLayout = oldLayout,
|
|
|
|
.newLayout = newLayout,
|
|
|
|
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
|
|
|
|
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
|
|
|
|
.image = image,
|
2024-10-14 23:32:52 -04:00
|
|
|
.subresourceRange = {
|
|
|
|
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
|
|
.baseMipLevel = 0,
|
|
|
|
.levelCount = mipLevels,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1,
|
|
|
|
},
|
2024-10-11 00:42:58 -04:00
|
|
|
};
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Define the source and destination stages
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::PipelineStageFlags sourceStage;
|
|
|
|
vk::PipelineStageFlags destinationStage;
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Define the access masks
|
2024-10-11 00:42:58 -04:00
|
|
|
if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) {
|
|
|
|
barrier.srcAccessMask = {};
|
|
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
|
|
|
|
|
|
sourceStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
|
|
destinationStage = vk::PipelineStageFlagBits::eTransfer;
|
|
|
|
} else if (oldLayout == vk::ImageLayout::eTransferDstOptimal &&
|
|
|
|
newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
|
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
|
|
|
|
|
|
sourceStage = vk::PipelineStageFlagBits::eTransfer;
|
|
|
|
destinationStage = vk::PipelineStageFlagBits::eFragmentShader;
|
|
|
|
} else {
|
2024-10-15 20:38:05 -04:00
|
|
|
// Ensure that the layout transition is supported
|
2024-10-11 00:42:58 -04:00
|
|
|
throw std::invalid_argument("Unsupported layout transition!");
|
|
|
|
}
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Record the pipeline barrier
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier);
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// End the command buffer
|
2024-10-14 23:32:52 -04:00
|
|
|
endSingleTimeCommands(commandBuffer);
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
fn copyBufferToImage(const vk::Buffer& buffer, const vk::Image& image, const u32& width, const u32& height)
|
|
|
|
-> void {
|
|
|
|
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
2024-10-11 00:42:58 -04:00
|
|
|
|
|
|
|
vk::BufferImageCopy region {
|
|
|
|
.bufferOffset = 0,
|
|
|
|
.bufferRowLength = 0,
|
|
|
|
.bufferImageHeight = 0,
|
|
|
|
.imageSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
|
|
.mipLevel = 0,
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.layerCount = 1 },
|
2024-10-11 22:34:15 -04:00
|
|
|
.imageOffset = { .x = 0, .y = 0, .z = 0 },
|
|
|
|
.imageExtent = { .width = width, .height = height, .depth = 1 },
|
2024-10-11 00:42:58 -04:00
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, ®ion);
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
endSingleTimeCommands(commandBuffer);
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Loads the 3D model.
|
|
|
|
*
|
|
|
|
* This function loads a 3D model from an OBJ file, extracting vertex and index data.
|
|
|
|
* It also removes duplicate vertices to optimize the model.
|
|
|
|
*/
|
2024-10-11 20:04:28 -04:00
|
|
|
fn loadModel() -> void {
|
|
|
|
tinyobj::attrib_t attrib;
|
|
|
|
std::vector<tinyobj::shape_t> shapes;
|
|
|
|
std::vector<tinyobj::material_t> materials;
|
2024-10-19 00:41:14 -04:00
|
|
|
string warn, err;
|
2024-10-11 20:04:28 -04:00
|
|
|
|
2024-10-18 14:34:26 -04:00
|
|
|
std::filesystem::path modelPath = std::filesystem::current_path() / "models" / "viking_room.obj";
|
|
|
|
|
|
|
|
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, modelPath.string().c_str()))
|
2024-10-11 20:04:28 -04:00
|
|
|
throw std::runtime_error(warn + err);
|
|
|
|
|
|
|
|
std::unordered_map<Vertex, u32> uniqueVertices {};
|
|
|
|
|
|
|
|
for (const tinyobj::shape_t& shape : shapes) {
|
|
|
|
for (const tinyobj::index_t& index : shape.mesh.indices) {
|
|
|
|
Vertex vertex {
|
|
|
|
.pos = {
|
2024-10-11 22:34:15 -04:00
|
|
|
attrib.vertices[static_cast<u32>((3 * index.vertex_index) + 0)],
|
|
|
|
attrib.vertices[static_cast<u32>((3 * index.vertex_index) + 1)],
|
|
|
|
attrib.vertices[static_cast<u32>((3 * index.vertex_index) + 2)],
|
2024-10-11 20:04:28 -04:00
|
|
|
},
|
|
|
|
.color = { 1.0F, 1.0F, 1.0F },
|
2024-11-15 21:25:10 -05:00
|
|
|
.texCoord = {
|
2024-10-11 22:34:15 -04:00
|
|
|
attrib.texcoords[static_cast<u32>((2 * index.texcoord_index) + 0)],
|
|
|
|
1.0F - attrib.texcoords[static_cast<u32>((2 * index.texcoord_index) + 1)],
|
2024-10-14 23:32:52 -04:00
|
|
|
},
|
2024-11-15 21:25:10 -05:00
|
|
|
.normal = {
|
|
|
|
attrib.normals[static_cast<u32>((3 * index.normal_index) + 0)],
|
|
|
|
attrib.normals[static_cast<u32>((3 * index.normal_index) + 1)],
|
|
|
|
attrib.normals[static_cast<u32>((3 * index.normal_index) + 2)],
|
|
|
|
},
|
2024-10-11 20:04:28 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
if (!uniqueVertices.contains(vertex)) {
|
|
|
|
uniqueVertices[vertex] = static_cast<u32>(mVertices.size());
|
|
|
|
mVertices.push_back(vertex);
|
|
|
|
}
|
|
|
|
|
|
|
|
mIndices.push_back(uniqueVertices[vertex]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the vertex buffer.
|
|
|
|
*
|
|
|
|
* This function creates a vertex buffer on the GPU and transfers vertex data from CPU memory.
|
|
|
|
* It uses a staging buffer for the transfer to allow for better performance.
|
|
|
|
*/
|
2024-10-09 23:00:45 -04:00
|
|
|
fn createVertexBuffer() -> void {
|
2024-10-11 20:04:28 -04:00
|
|
|
vk::DeviceSize bufferSize = sizeof(mVertices[0]) * mVertices.size();
|
2024-10-10 16:04:46 -04:00
|
|
|
|
|
|
|
vk::UniqueBuffer stagingBuffer;
|
|
|
|
vk::UniqueDeviceMemory stagingBufferMemory;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(stagingBuffer, stagingBufferMemory) = createBuffer(
|
2024-10-10 16:04:46 -04:00
|
|
|
bufferSize,
|
2024-10-10 20:39:04 -04:00
|
|
|
vk::BufferUsageFlagBits::eTransferSrc,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
2024-10-10 16:04:46 -04:00
|
|
|
);
|
|
|
|
|
2024-10-11 20:04:28 -04:00
|
|
|
copyData(stagingBufferMemory.get(), bufferSize, mVertices.data());
|
2024-10-10 16:04:46 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(mVertexBuffer, mVertexBufferMemory) = createBuffer(
|
2024-10-10 16:04:46 -04:00
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
2024-10-10 16:04:46 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
copyBuffer(stagingBuffer.get(), mVertexBuffer.get(), bufferSize);
|
|
|
|
|
|
|
|
stagingBuffer.reset();
|
|
|
|
stagingBufferMemory.reset();
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the index buffer.
|
|
|
|
*
|
|
|
|
* This function creates an index buffer on the GPU and transfers index data from CPU memory.
|
|
|
|
* It uses a staging buffer for the transfer to allow for better performance.
|
|
|
|
*/
|
2024-10-10 18:51:20 -04:00
|
|
|
fn createIndexBuffer() -> void {
|
2024-10-11 20:04:28 -04:00
|
|
|
vk::DeviceSize bufferSize = sizeof(mIndices[0]) * mIndices.size();
|
2024-10-10 18:51:20 -04:00
|
|
|
|
|
|
|
vk::UniqueBuffer stagingBuffer;
|
|
|
|
vk::UniqueDeviceMemory stagingBufferMemory;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(stagingBuffer, stagingBufferMemory) = createBuffer(
|
2024-10-10 18:51:20 -04:00
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eTransferSrc,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
2024-10-10 18:51:20 -04:00
|
|
|
);
|
|
|
|
|
2024-10-11 20:04:28 -04:00
|
|
|
copyData(stagingBufferMemory.get(), bufferSize, mIndices.data());
|
2024-10-10 18:51:20 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
std::tie(mIndexBuffer, mIndexBufferMemory) = createBuffer(
|
2024-10-10 18:51:20 -04:00
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal
|
2024-10-10 18:51:20 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
copyBuffer(stagingBuffer.get(), mIndexBuffer.get(), bufferSize);
|
|
|
|
|
|
|
|
stagingBuffer.reset();
|
|
|
|
stagingBufferMemory.reset();
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates uniform buffers.
|
|
|
|
*
|
|
|
|
* This function creates uniform buffers for each frame in flight. These buffers are used
|
|
|
|
* to pass uniform data (like transformation matrices) to shaders.
|
|
|
|
*/
|
2024-10-10 20:39:04 -04:00
|
|
|
fn createUniformBuffers() -> void {
|
|
|
|
vk::DeviceSize bufferSize = sizeof(UniformBufferObject);
|
|
|
|
|
|
|
|
mUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
|
|
|
std::tie(mUniformBuffers[idx], mUniformBuffersMemory[idx]) = createBuffer(
|
2024-10-10 20:39:04 -04:00
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
2024-10-10 20:39:04 -04:00
|
|
|
);
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
mUniformBuffersMapped[idx] = mDevice->mapMemory(mUniformBuffersMemory[idx].get(), 0, bufferSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn createLightUniformBuffers() -> void {
|
|
|
|
vk::DeviceSize bufferSize = sizeof(LightInfo);
|
|
|
|
|
|
|
|
mLightUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mLightUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mLightUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
|
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
|
|
|
std::tie(mLightUniformBuffers[idx], mLightUniformBuffersMemory[idx]) = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
|
|
|
);
|
|
|
|
|
|
|
|
mLightUniformBuffersMapped[idx] =
|
|
|
|
mDevice->mapMemory(mLightUniformBuffersMemory[idx].get(), 0, bufferSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn createCameraUniformBuffers() -> void {
|
|
|
|
vk::DeviceSize bufferSize = sizeof(CameraInfo);
|
|
|
|
|
|
|
|
mCameraUniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mCameraUniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mCameraUniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
|
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
|
|
|
std::tie(mCameraUniformBuffers[idx], mCameraUniformBuffersMemory[idx]) = createBuffer(
|
|
|
|
bufferSize,
|
|
|
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
|
|
|
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
|
|
|
|
);
|
|
|
|
|
|
|
|
mCameraUniformBuffersMapped[idx] =
|
|
|
|
mDevice->mapMemory(mCameraUniformBuffersMemory[idx].get(), 0, bufferSize);
|
2024-10-10 20:39:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates the descriptor pool.
|
|
|
|
*
|
|
|
|
* This function creates a descriptor pool from which descriptor sets can be allocated.
|
|
|
|
* The pool is sized to accommodate the number of frames in flight.
|
|
|
|
*/
|
2024-10-10 20:39:04 -04:00
|
|
|
fn createDescriptorPool() -> void {
|
2024-11-15 21:25:10 -05:00
|
|
|
std::array<vk::DescriptorPoolSize, 4> poolSizes = {
|
|
|
|
vk::DescriptorPoolSize {
|
|
|
|
.type = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
|
|
},
|
|
|
|
vk::DescriptorPoolSize {
|
|
|
|
.type = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
|
|
},
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::DescriptorPoolSize {
|
|
|
|
.type = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
|
|
},
|
|
|
|
vk::DescriptorPoolSize {
|
|
|
|
.type = vk::DescriptorType::eCombinedImageSampler,
|
|
|
|
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
|
|
|
|
},
|
2024-10-10 20:39:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
vk::DescriptorPoolCreateInfo poolInfo {
|
2024-10-11 00:42:58 -04:00
|
|
|
.maxSets = MAX_FRAMES_IN_FLIGHT,
|
|
|
|
.poolSizeCount = static_cast<u32>(poolSizes.size()),
|
|
|
|
.pPoolSizes = poolSizes.data(),
|
2024-10-10 20:39:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
mDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates descriptor sets.
|
|
|
|
*
|
|
|
|
* This function allocates and updates descriptor sets for each frame in flight.
|
|
|
|
* These sets bind the uniform buffers and texture sampler to the shader.
|
|
|
|
*/
|
2024-10-10 20:39:04 -04:00
|
|
|
fn createDescriptorSets() -> void {
|
|
|
|
std::vector<vk::DescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, mDescriptorSetLayout.get());
|
|
|
|
|
|
|
|
vk::DescriptorSetAllocateInfo allocInfo {
|
|
|
|
.descriptorPool = mDescriptorPool.get(),
|
|
|
|
.descriptorSetCount = static_cast<u32>(MAX_FRAMES_IN_FLIGHT),
|
|
|
|
.pSetLayouts = layouts.data(),
|
|
|
|
};
|
|
|
|
|
|
|
|
mDescriptorSets = mDevice->allocateDescriptorSets(allocInfo);
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
|
|
|
vk::DescriptorBufferInfo uboBufferInfo {
|
|
|
|
.buffer = mUniformBuffers[idx].get(),
|
2024-10-10 20:39:04 -04:00
|
|
|
.offset = 0,
|
|
|
|
.range = sizeof(UniformBufferObject),
|
|
|
|
};
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
vk::DescriptorBufferInfo lightBufferInfo {
|
|
|
|
.buffer = mLightUniformBuffers[idx].get(),
|
|
|
|
.offset = 0,
|
|
|
|
.range = sizeof(LightInfo),
|
|
|
|
};
|
|
|
|
|
|
|
|
vk::DescriptorBufferInfo cameraBufferInfo {
|
|
|
|
.buffer = mCameraUniformBuffers[idx].get(),
|
|
|
|
.offset = 0,
|
|
|
|
.range = sizeof(CameraInfo),
|
|
|
|
};
|
|
|
|
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::DescriptorImageInfo imageInfo {
|
|
|
|
.sampler = mTextureSampler.get(),
|
|
|
|
.imageView = mTextureImageView.get(),
|
|
|
|
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
|
|
};
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
std::array<vk::WriteDescriptorSet, 4> descriptorWrites = {
|
2024-10-14 23:32:52 -04:00
|
|
|
vk::WriteDescriptorSet {
|
2024-11-15 21:25:10 -05:00
|
|
|
.dstSet = mDescriptorSets[idx],
|
2024-10-14 23:32:52 -04:00
|
|
|
.dstBinding = 0,
|
|
|
|
.dstArrayElement = 0,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
2024-11-15 21:25:10 -05:00
|
|
|
.pBufferInfo = &uboBufferInfo,
|
2024-10-14 23:32:52 -04:00
|
|
|
},
|
|
|
|
vk::WriteDescriptorSet {
|
2024-11-15 21:25:10 -05:00
|
|
|
.dstSet = mDescriptorSets[idx],
|
2024-10-14 23:32:52 -04:00
|
|
|
.dstBinding = 1,
|
|
|
|
.dstArrayElement = 0,
|
|
|
|
.descriptorCount = 1,
|
2024-11-15 21:25:10 -05:00
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.pBufferInfo = &lightBufferInfo,
|
|
|
|
},
|
|
|
|
vk::WriteDescriptorSet {
|
|
|
|
.dstSet = mDescriptorSets[idx],
|
|
|
|
.dstBinding = 2,
|
|
|
|
.dstArrayElement = 0,
|
|
|
|
.descriptorCount = 1,
|
|
|
|
.descriptorType = vk::DescriptorType::eUniformBuffer,
|
|
|
|
.pBufferInfo = &cameraBufferInfo,
|
|
|
|
},
|
|
|
|
vk::WriteDescriptorSet {
|
|
|
|
.dstSet = mDescriptorSets[idx],
|
|
|
|
.dstBinding = 3,
|
|
|
|
.dstArrayElement = 0,
|
|
|
|
.descriptorCount = 1,
|
2024-10-14 23:32:52 -04:00
|
|
|
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
|
|
|
.pImageInfo = &imageInfo,
|
|
|
|
},
|
2024-10-10 20:39:04 -04:00
|
|
|
};
|
|
|
|
|
2024-10-11 00:42:58 -04:00
|
|
|
mDevice->updateDescriptorSets(descriptorWrites, {});
|
2024-10-10 20:39:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates a Vulkan buffer.
|
|
|
|
*
|
|
|
|
* @param deviceSize The size of the buffer to create.
|
|
|
|
* @param bufferUsageFlags The usage flags for the buffer.
|
|
|
|
* @param memoryPropertyFlags The desired properties of the memory to be allocated.
|
|
|
|
* @return A pair containing the created buffer and its associated device memory.
|
|
|
|
*
|
|
|
|
* This function creates a Vulkan buffer with the specified size, usage, and memory properties.
|
|
|
|
*/
|
2024-10-10 16:04:46 -04:00
|
|
|
fn createBuffer(
|
2024-10-14 23:32:52 -04:00
|
|
|
const vk::DeviceSize& deviceSize,
|
|
|
|
const vk::BufferUsageFlags& bufferUsageFlags,
|
|
|
|
const vk::MemoryPropertyFlags& memoryPropertyFlags
|
|
|
|
) -> std::pair<vk::UniqueBuffer, vk::UniqueDeviceMemory> {
|
2024-10-09 23:00:45 -04:00
|
|
|
vk::BufferCreateInfo bufferInfo {
|
2024-10-10 16:04:46 -04:00
|
|
|
.size = deviceSize,
|
|
|
|
.usage = bufferUsageFlags,
|
2024-10-09 23:00:45 -04:00
|
|
|
.sharingMode = vk::SharingMode::eExclusive,
|
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the buffer
|
|
|
|
vk::UniqueBuffer buffer = mDevice->createBufferUnique(bufferInfo);
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the memory requirements for the buffer
|
2024-10-10 16:04:46 -04:00
|
|
|
vk::MemoryRequirements memRequirements = mDevice->getBufferMemoryRequirements(buffer.get());
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Memory allocation info
|
2024-10-09 23:00:45 -04:00
|
|
|
vk::MemoryAllocateInfo allocInfo {
|
|
|
|
.allocationSize = memRequirements.size,
|
2024-10-10 16:04:46 -04:00
|
|
|
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, memoryPropertyFlags),
|
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Allocate memory
|
|
|
|
vk::UniqueDeviceMemory bufferMemory = mDevice->allocateMemoryUnique(allocInfo);
|
2024-10-10 16:04:46 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Bind the allocated memory to the buffer
|
2024-10-10 16:04:46 -04:00
|
|
|
mDevice->bindBufferMemory(buffer.get(), bufferMemory.get(), 0);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Return both the unique buffer and its associated memory
|
|
|
|
return { std::move(buffer), std::move(bufferMemory) };
|
2024-10-10 16:04:46 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Begins a single-time command buffer.
|
|
|
|
*
|
|
|
|
* @return A command buffer ready for recording commands.
|
|
|
|
*
|
|
|
|
* This function allocates and begins a command buffer for one-time use operations.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn beginSingleTimeCommands() -> vk::CommandBuffer {
|
|
|
|
// Define the command buffer allocation info
|
2024-10-10 16:04:46 -04:00
|
|
|
vk::CommandBufferAllocateInfo allocInfo {
|
|
|
|
.commandPool = mCommandPool.get(),
|
|
|
|
.level = vk::CommandBufferLevel::ePrimary,
|
|
|
|
.commandBufferCount = 1,
|
2024-10-09 23:00:45 -04:00
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Allocate the command buffer
|
|
|
|
vk::CommandBuffer commandBuffer = mDevice->allocateCommandBuffers(allocInfo)[0];
|
2024-10-10 16:04:46 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Define the command buffer begin info
|
2024-10-10 16:04:46 -04:00
|
|
|
vk::CommandBufferBeginInfo beginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit };
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Begin the command buffer
|
|
|
|
commandBuffer.begin(beginInfo);
|
2024-10-10 16:04:46 -04:00
|
|
|
|
2024-10-11 00:42:58 -04:00
|
|
|
return commandBuffer;
|
|
|
|
}
|
2024-10-10 16:04:46 -04:00
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Ends and submits a single-time command buffer.
|
|
|
|
*
|
|
|
|
* @param commandBuffer The command buffer to end and submit.
|
|
|
|
*
|
|
|
|
* This function ends the recording of a single-time command buffer, submits it to the queue,
|
|
|
|
* and waits for it to complete before freeing the command buffer.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn endSingleTimeCommands(const vk::CommandBuffer& commandBuffer) -> void {
|
|
|
|
// End the command buffer
|
|
|
|
commandBuffer.end();
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Define the submit info
|
|
|
|
vk::SubmitInfo submitInfo { .commandBufferCount = 1, .pCommandBuffers = &commandBuffer };
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Submit the command buffer
|
2024-10-10 16:04:46 -04:00
|
|
|
mGraphicsQueue.submit(submitInfo, nullptr);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Wait for the queue to finish
|
2024-10-10 16:04:46 -04:00
|
|
|
mGraphicsQueue.waitIdle();
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Free the command buffer
|
|
|
|
mDevice->freeCommandBuffers(mCommandPool.get(), commandBuffer);
|
2024-10-09 23:00:45 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Copies data to a mapped memory.
|
|
|
|
*
|
|
|
|
* @param stagingBufferMemory The device memory to copy to.
|
|
|
|
* @param bufferSize The size of the data to copy.
|
|
|
|
* @param src Pointer to the source data.
|
|
|
|
*
|
|
|
|
* This function maps a memory, copies data to it, and then unmaps the memory.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn copyData(const vk::DeviceMemory& stagingBufferMemory, const vk::DeviceSize& bufferSize, const void* src)
|
|
|
|
-> void {
|
|
|
|
// Map the memory
|
2024-10-11 00:42:58 -04:00
|
|
|
void* data = mDevice->mapMemory(stagingBufferMemory, 0, bufferSize);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Copy the data with memcpy - memcpy(dst, src, size)
|
2024-10-11 00:42:58 -04:00
|
|
|
memcpy(data, src, static_cast<usize>(bufferSize));
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Unmap the memory
|
2024-10-11 00:42:58 -04:00
|
|
|
mDevice->unmapMemory(stagingBufferMemory);
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Copies data from one buffer to another.
|
|
|
|
*
|
|
|
|
* @param srcBuffer The source buffer.
|
|
|
|
* @param dstBuffer The destination buffer.
|
|
|
|
* @param deviceSize The size of data to copy.
|
|
|
|
*
|
|
|
|
* This function records and submits a command to copy data between two buffers.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn copyBuffer(const vk::Buffer& srcBuffer, const vk::Buffer& dstBuffer, const vk::DeviceSize& deviceSize)
|
|
|
|
-> void {
|
|
|
|
// Begin a single time command buffer
|
|
|
|
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Define the copy region
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::BufferCopy copyRegion { .size = deviceSize };
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Copy the buffer
|
|
|
|
commandBuffer.copyBuffer(srcBuffer, dstBuffer, 1, ©Region);
|
2024-10-11 00:42:58 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// End the single time command buffer
|
|
|
|
endSingleTimeCommands(commandBuffer);
|
2024-10-11 00:42:58 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Finds a memory type index that satisfies the given properties.
|
|
|
|
*
|
|
|
|
* @param typeFilter A bit field of memory types that are suitable for the buffer.
|
|
|
|
* @param properties The properties the memory type must have.
|
|
|
|
* @return The index of a suitable memory type.
|
|
|
|
*
|
|
|
|
* This function finds a memory type that satisfies both the type filter and the desired properties.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn findMemoryType(const u32& typeFilter, const vk::MemoryPropertyFlags& properties) -> u32 {
|
|
|
|
// Get the memory properties of the physical device
|
2024-10-09 23:00:45 -04:00
|
|
|
vk::PhysicalDeviceMemoryProperties memProperties = mPhysicalDevice.getMemoryProperties();
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Loop through the memory types and find the one that matches the filter
|
|
|
|
for (u32 idx = 0; idx < memProperties.memoryTypeCount; idx++)
|
|
|
|
if ((typeFilter & (1 << idx)) &&
|
|
|
|
(memProperties.memoryTypes.at(idx).propertyFlags & properties) == properties)
|
|
|
|
return idx;
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Throw an error if no suitable memory type is found
|
2024-10-09 23:00:45 -04:00
|
|
|
throw std::runtime_error("Failed to find a suitable memory type!");
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates command buffers.
|
|
|
|
*
|
|
|
|
* This function allocates command buffers from the command pool. One command buffer is
|
|
|
|
* allocated for each frame in flight.
|
|
|
|
*/
|
2024-10-05 23:08:12 -04:00
|
|
|
fn createCommandBuffers() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Resize the command buffers to hold the maximum number of frames in flight
|
2024-10-05 23:08:12 -04:00
|
|
|
mCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Define the command buffer allocation info
|
|
|
|
vk::CommandBufferAllocateInfo allocInfo {
|
|
|
|
.commandPool = mCommandPool.get(),
|
|
|
|
.level = vk::CommandBufferLevel::ePrimary,
|
|
|
|
.commandBufferCount = static_cast<u32>(mCommandBuffers.size()),
|
|
|
|
};
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Allocate the command buffers
|
2024-10-06 18:14:15 -04:00
|
|
|
mCommandBuffers = mDevice->allocateCommandBuffersUnique(allocInfo);
|
2024-10-01 18:54:41 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Records a command buffer.
|
|
|
|
*
|
|
|
|
* @param commandBuffer The command buffer to record into.
|
|
|
|
* @param imageIndex The index of the swap chain image to render to.
|
|
|
|
*
|
|
|
|
* This function records drawing commands into the given command buffer.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn recordCommandBuffer(const vk::CommandBuffer& commandBuffer, const u32& imageIndex) -> void {
|
|
|
|
// Begin the command buffer
|
2024-11-15 15:25:36 -05:00
|
|
|
commandBuffer.begin({ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Define clear values for color and depth
|
|
|
|
std::array<vk::ClearValue, 2> clearValues = {
|
|
|
|
vk::ClearValue { .color = { std::array<f32, 4> { 0.0F, 0.0F, 0.0F, 1.0F } } },
|
|
|
|
vk::ClearValue { .depthStencil = { 1.0F, 0 } },
|
2024-10-11 19:09:37 -04:00
|
|
|
};
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Begin the render pass
|
2024-10-01 18:54:41 -04:00
|
|
|
vk::RenderPassBeginInfo renderPassInfo {
|
2024-11-15 15:25:36 -05:00
|
|
|
.renderPass = mRenderPass.get(),
|
|
|
|
.framebuffer = mSwapChainFramebuffers[imageIndex].get(),
|
|
|
|
.renderArea = { .offset = { 0, 0 }, .extent = mSwapChainExtent },
|
2024-10-11 19:09:37 -04:00
|
|
|
.clearValueCount = static_cast<u32>(clearValues.size()),
|
2024-11-15 15:25:36 -05:00
|
|
|
.pClearValues = clearValues.data()
|
2024-10-01 18:54:41 -04:00
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Begin the render pass
|
2024-10-01 18:54:41 -04:00
|
|
|
commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);
|
2024-10-14 23:32:52 -04:00
|
|
|
|
|
|
|
// Bind the graphics pipeline to the command buffer
|
2024-10-01 18:54:41 -04:00
|
|
|
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mGraphicsPipeline.get());
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the viewport and scissor.
|
|
|
|
// If the scissor is smaller than the
|
|
|
|
// viewport, then it will be clipped.
|
2024-10-01 18:54:41 -04:00
|
|
|
vk::Viewport viewport {
|
|
|
|
.x = 0.0F,
|
|
|
|
.y = 0.0F,
|
|
|
|
.width = static_cast<f32>(mSwapChainExtent.width),
|
|
|
|
.height = static_cast<f32>(mSwapChainExtent.height),
|
|
|
|
.minDepth = 0.0F,
|
|
|
|
.maxDepth = 1.0F,
|
|
|
|
};
|
2024-10-09 23:00:45 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the scissor
|
2024-10-01 18:54:41 -04:00
|
|
|
vk::Rect2D scissor {
|
2024-10-11 22:34:15 -04:00
|
|
|
.offset = { .x = 0, .y = 0 },
|
2024-10-01 18:54:41 -04:00
|
|
|
.extent = mSwapChainExtent,
|
|
|
|
};
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Set the viewport and scissor
|
2024-10-01 19:00:31 -04:00
|
|
|
commandBuffer.setViewport(0, viewport);
|
2024-10-01 18:54:41 -04:00
|
|
|
commandBuffer.setScissor(0, scissor);
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Bind the vertex buffer
|
2024-10-09 23:00:45 -04:00
|
|
|
commandBuffer.bindVertexBuffers(0, mVertexBuffer.get(), { 0 });
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Bind the index buffer
|
2024-10-11 20:04:28 -04:00
|
|
|
commandBuffer.bindIndexBuffer(mIndexBuffer.get(), 0, vk::IndexType::eUint32);
|
2024-10-10 18:51:20 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Bind the descriptor sets
|
2024-10-10 20:39:04 -04:00
|
|
|
commandBuffer.bindDescriptorSets(
|
|
|
|
vk::PipelineBindPoint::eGraphics,
|
|
|
|
mPipelineLayout.get(),
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
&mDescriptorSets[mCurrentFrame],
|
|
|
|
0,
|
|
|
|
nullptr
|
|
|
|
);
|
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
UniformBufferObject ubo {
|
|
|
|
// Model matrix - glm::rotate(matrix, angle, axis)
|
|
|
|
.model = glm::mat4(1.0F),
|
|
|
|
// View matrix - glm::lookAt(eye, center, up)
|
|
|
|
.view = mView,
|
|
|
|
// Projection matrix - glm::perspective(fov, aspect, near, far)
|
|
|
|
.proj = glm::perspective(
|
2024-11-15 15:25:36 -05:00
|
|
|
glm::radians(mFieldOfView),
|
2024-11-15 12:40:13 -05:00
|
|
|
static_cast<f32>(mSwapChainExtent.width) / static_cast<f32>(mSwapChainExtent.height),
|
|
|
|
0.1F,
|
|
|
|
100.0F
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Flip the Y axis, because glm was designed for OpenGL
|
|
|
|
ubo.proj[1][1] *= -1;
|
|
|
|
|
|
|
|
// Copy the uniform buffer object to the mapped memory
|
|
|
|
memcpy(mUniformBuffersMapped[mCurrentFrame], &ubo, sizeof(ubo));
|
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
LightInfo lightInfo {
|
|
|
|
.position = mLightSettings.position,
|
|
|
|
.color = mLightSettings.color,
|
|
|
|
.ambient_strength = mLightSettings.ambient_strength,
|
|
|
|
.specular_strength = mLightSettings.specular_strength,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Copy the light uniform buffer object to the mapped memory
|
|
|
|
memcpy(mLightUniformBuffersMapped[mCurrentFrame], &lightInfo, sizeof(lightInfo));
|
|
|
|
|
|
|
|
CameraInfo cameraInfo {
|
|
|
|
.position = glm::vec3(mCamera.getPosition()), // Use actual camera position
|
|
|
|
};
|
|
|
|
|
|
|
|
// Copy the camera uniform buffer object to the mapped memory
|
|
|
|
memcpy(mCameraUniformBuffersMapped[mCurrentFrame], &cameraInfo, sizeof(cameraInfo));
|
|
|
|
|
2024-11-15 12:40:13 -05:00
|
|
|
// Example: Add extra clones with different translations
|
2024-11-15 15:25:36 -05:00
|
|
|
std::vector<glm::mat4> modelMatrices = { glm::translate(glm::mat4(1.0F), glm::vec3(2.0F, 0.0F, 0.0F)),
|
|
|
|
glm::translate(glm::mat4(1.0F), glm::vec3(-2.0F, 0.0F, 0.0F)),
|
|
|
|
glm::translate(glm::mat4(1.0F), glm::vec3(0.0F, 2.0F, 0.0F)) };
|
2024-11-15 12:40:13 -05:00
|
|
|
|
|
|
|
for (const auto& modelMatrix : modelMatrices) {
|
|
|
|
// Update model matrix for each clone
|
|
|
|
ubo.model = modelMatrix;
|
|
|
|
memcpy(mUniformBuffersMapped[mCurrentFrame], &ubo, sizeof(ubo));
|
|
|
|
|
|
|
|
// Bind the descriptor sets
|
|
|
|
commandBuffer.bindDescriptorSets(
|
|
|
|
vk::PipelineBindPoint::eGraphics,
|
|
|
|
mPipelineLayout.get(),
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
&mDescriptorSets[mCurrentFrame],
|
|
|
|
0,
|
|
|
|
nullptr
|
|
|
|
);
|
|
|
|
|
|
|
|
// Draw the indexed vertices
|
|
|
|
commandBuffer.drawIndexed(static_cast<u32>(mIndices.size()), 1, 0, 0, 0);
|
|
|
|
}
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Draw the crosshair
|
|
|
|
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mCrosshairPipeline.get());
|
2024-11-15 15:25:36 -05:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
std::array<vk::Buffer, 1> vertexBuffers = { mCrosshairVertexBuffer.get() };
|
|
|
|
std::array<vk::DeviceSize, 1> offsets = { 0 };
|
|
|
|
commandBuffer.bindVertexBuffers(0, 1, vertexBuffers.data(), offsets.data());
|
|
|
|
commandBuffer.bindIndexBuffer(mCrosshairIndexBuffer.get(), 0, vk::IndexType::eUint16);
|
2024-11-15 15:25:36 -05:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Draw the crosshair
|
|
|
|
commandBuffer.drawIndexed(static_cast<u32>(crosshairIndices.size()), 1, 0, 0, 0);
|
2024-11-15 15:25:36 -05:00
|
|
|
|
2024-11-15 21:25:10 -05:00
|
|
|
// Render ImGui if we have a draw data (ImGui::Render was called)
|
|
|
|
if (ImGui::GetDrawData()) {
|
2024-11-15 15:25:36 -05:00
|
|
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer);
|
|
|
|
}
|
2024-10-15 19:49:39 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// End the render pass
|
2024-10-01 18:54:41 -04:00
|
|
|
commandBuffer.endRenderPass();
|
2024-10-06 00:37:42 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// End the command buffer
|
2024-10-06 18:14:15 -04:00
|
|
|
commandBuffer.end();
|
2024-10-01 18:54:41 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates synchronization objects for frame rendering.
|
|
|
|
*
|
|
|
|
* This function creates semaphores and fences used for synchronizing operations
|
|
|
|
* between the CPU and GPU, and between different stages of rendering.
|
|
|
|
*/
|
2024-10-01 18:54:41 -04:00
|
|
|
fn createSyncObjects() -> void {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Resize the vectors to hold the maximum number of frames in flight
|
2024-10-05 23:08:12 -04:00
|
|
|
mImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mRenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
mInFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create the semaphore and fence info
|
2024-10-01 18:54:41 -04:00
|
|
|
vk::SemaphoreCreateInfo semaphoreInfo {};
|
|
|
|
vk::FenceCreateInfo fenceInfo { .flags = vk::FenceCreateFlagBits::eSignaled };
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Loop through the maximum number of frames in flight and create the semaphores and fences
|
2024-10-05 23:08:12 -04:00
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
2024-10-06 18:14:15 -04:00
|
|
|
mImageAvailableSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo);
|
|
|
|
mRenderFinishedSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo);
|
|
|
|
mInFlightFences[idx] = mDevice->createFenceUnique(fenceInfo);
|
2024-10-05 23:08:12 -04:00
|
|
|
}
|
2024-10-01 18:54:41 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Updates the uniform buffer for the current frame.
|
|
|
|
*
|
|
|
|
* @param currentImage The index of the current swap chain image.
|
|
|
|
*
|
|
|
|
* This function updates the uniform buffer object (UBO) with new transformation
|
|
|
|
* matrices for each frame. It calculates a new model matrix based on time,
|
|
|
|
* and updates the view and projection matrices.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn updateUniformBuffer(const u32& currentImage) -> void {
|
2024-10-15 20:38:05 -04:00
|
|
|
// For convenience
|
2024-10-14 23:32:52 -04:00
|
|
|
using namespace std::chrono;
|
2024-10-15 20:38:05 -04:00
|
|
|
using time_point = high_resolution_clock::time_point;
|
|
|
|
|
|
|
|
// Time of the program start
|
2024-10-14 23:32:52 -04:00
|
|
|
static time_point StartTime = high_resolution_clock::now();
|
2024-10-10 20:39:04 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Current time
|
2024-10-14 23:32:52 -04:00
|
|
|
time_point currentTime = high_resolution_clock::now();
|
2024-10-10 20:39:04 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Time since the program started
|
|
|
|
f32 time = duration<f32, seconds::period>(currentTime - StartTime).count();
|
|
|
|
|
|
|
|
// Uniform buffer object
|
2024-10-10 20:39:04 -04:00
|
|
|
UniformBufferObject ubo {
|
2024-10-15 20:38:05 -04:00
|
|
|
// Model matrix - glm::rotate(matrix, angle, axis)
|
2024-10-10 20:39:04 -04:00
|
|
|
.model = glm::rotate(glm::mat4(1.0F), time * glm::radians(90.0F), glm::vec3(0.0F, 0.0F, 1.0F)),
|
2024-10-15 20:38:05 -04:00
|
|
|
// View matrix - glm::lookAt(eye, center, up)
|
2024-10-23 13:10:18 -04:00
|
|
|
.view = mView,
|
2024-10-15 20:38:05 -04:00
|
|
|
// Projection matrix - glm::perspective(fov, aspect, near, far)
|
2024-10-10 20:39:04 -04:00
|
|
|
.proj = glm::perspective(
|
2024-11-15 15:25:36 -05:00
|
|
|
glm::radians(mFieldOfView),
|
2024-10-10 20:39:04 -04:00
|
|
|
static_cast<f32>(mSwapChainExtent.width) / static_cast<f32>(mSwapChainExtent.height),
|
|
|
|
0.1F,
|
2024-10-23 13:10:18 -04:00
|
|
|
100.0F
|
2024-10-10 20:39:04 -04:00
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Flip the Y axis, because glm was designed for OpenGL
|
|
|
|
ubo.proj[1][1] *= -1;
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Copy the uniform buffer object to the mapped memory
|
2024-10-10 20:39:04 -04:00
|
|
|
memcpy(mUniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Renders a single frame.
|
|
|
|
*
|
|
|
|
* This function performs all the steps necessary to render a single frame:
|
|
|
|
* 1. Waits for the previous frame to finish
|
|
|
|
* 2. Acquires an image from the swap chain
|
|
|
|
* 3. Records a command buffer
|
|
|
|
* 4. Submits the command buffer
|
|
|
|
* 5. Presents the swap chain image
|
|
|
|
*/
|
2024-10-01 18:54:41 -04:00
|
|
|
fn drawFrame() -> void {
|
2024-10-06 18:34:42 -04:00
|
|
|
try {
|
2024-10-15 20:38:05 -04:00
|
|
|
// Wait for the fence to signal that the frame is finished
|
2024-10-06 18:34:42 -04:00
|
|
|
vk::Result result =
|
|
|
|
mDevice->waitForFences(mInFlightFences[mCurrentFrame].get(), vk::Bool32(vk::True), UINT64_MAX);
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Make sure the result is successful
|
2024-10-06 18:34:42 -04:00
|
|
|
if (result != vk::Result::eSuccess)
|
|
|
|
throw std::runtime_error("Failed to wait for fences!");
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Clear old pipeline if it exists
|
|
|
|
if (mOldPipeline) {
|
|
|
|
mDevice->waitIdle(); // Wait for all operations to complete
|
|
|
|
mOldPipeline.reset();
|
|
|
|
}
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Acquire the next image from the swap chain
|
|
|
|
auto [imageIndexResult, imageIndexValue] = mDevice->acquireNextImageKHR(
|
2024-10-06 18:34:42 -04:00
|
|
|
mSwapChain.get(), UINT64_MAX, mImageAvailableSemaphores[mCurrentFrame].get(), nullptr
|
|
|
|
);
|
2024-10-05 23:08:12 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Check if the swap chain needs to be recreated
|
2024-10-06 18:34:42 -04:00
|
|
|
if (imageIndexResult == vk::Result::eErrorOutOfDateKHR) {
|
|
|
|
recreateSwapChain();
|
|
|
|
return;
|
|
|
|
}
|
2024-10-05 23:08:12 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Check if the image index is valid
|
2024-10-06 18:34:42 -04:00
|
|
|
if (imageIndexResult != vk::Result::eSuccess && imageIndexResult != vk::Result::eSuboptimalKHR)
|
|
|
|
throw std::runtime_error("Failed to acquire swap chain image!");
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Update the uniform buffer with the current image
|
2024-10-10 20:39:04 -04:00
|
|
|
updateUniformBuffer(mCurrentFrame);
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Reset the current fence
|
2024-10-06 18:34:42 -04:00
|
|
|
mDevice->resetFences(mInFlightFences[mCurrentFrame].get());
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Reset the current command buffer
|
2024-10-06 18:34:42 -04:00
|
|
|
mCommandBuffers[mCurrentFrame]->reset(vk::CommandBufferResetFlagBits::eReleaseResources);
|
2024-10-15 20:38:05 -04:00
|
|
|
|
|
|
|
// Define the command buffer submit info
|
2024-10-06 18:34:42 -04:00
|
|
|
recordCommandBuffer(mCommandBuffers[mCurrentFrame].get(), imageIndexValue);
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-06 18:34:42 -04:00
|
|
|
std::array<vk::PipelineStageFlags, 1> waitStages = {
|
|
|
|
vk::PipelineStageFlagBits::eColorAttachmentOutput
|
|
|
|
};
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-06 18:34:42 -04:00
|
|
|
vk::SubmitInfo submitInfo {
|
|
|
|
.waitSemaphoreCount = 1,
|
|
|
|
.pWaitSemaphores = &mImageAvailableSemaphores[mCurrentFrame].get(),
|
|
|
|
.pWaitDstStageMask = waitStages.data(),
|
|
|
|
.commandBufferCount = 1,
|
|
|
|
.pCommandBuffers = &mCommandBuffers[mCurrentFrame].get(),
|
|
|
|
.signalSemaphoreCount = 1,
|
|
|
|
.pSignalSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
|
|
|
};
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-11-15 15:25:36 -05:00
|
|
|
// Submit the command buffer
|
2024-10-06 18:34:42 -04:00
|
|
|
mGraphicsQueue.submit(submitInfo, mInFlightFences[mCurrentFrame].get());
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-06 18:34:42 -04:00
|
|
|
vk::PresentInfoKHR presentInfo {
|
|
|
|
.waitSemaphoreCount = 1,
|
|
|
|
.pWaitSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
|
|
|
.swapchainCount = 1,
|
|
|
|
.pSwapchains = &mSwapChain.get(),
|
|
|
|
.pImageIndices = &imageIndexValue,
|
|
|
|
};
|
2024-10-01 18:54:41 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Present the swap chain image
|
2024-10-06 18:14:15 -04:00
|
|
|
vk::Result presentResult = mPresentQueue.presentKHR(presentInfo);
|
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Check if the swap chain needs to be recreated
|
2024-10-06 18:14:15 -04:00
|
|
|
if (presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR ||
|
|
|
|
mFramebufferResized) {
|
|
|
|
mFramebufferResized = false;
|
|
|
|
recreateSwapChain();
|
2024-10-15 20:38:05 -04:00
|
|
|
} else if (presentResult != vk::Result::eSuccess) {
|
|
|
|
// Throw if present failed
|
2024-10-06 18:14:15 -04:00
|
|
|
throw std::runtime_error("Failed to present swap chain image!");
|
2024-10-15 20:38:05 -04:00
|
|
|
}
|
2024-10-06 18:14:15 -04:00
|
|
|
|
2024-10-15 20:38:05 -04:00
|
|
|
// Increment the current frame (or loop back to 0)
|
2024-10-06 18:14:15 -04:00
|
|
|
mCurrentFrame = (mCurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
2024-11-15 15:25:36 -05:00
|
|
|
} catch (const vk::SystemError& /*err*/) {
|
2024-10-15 20:38:05 -04:00
|
|
|
// Recreate the swap chain if it's out of date
|
2024-10-05 23:08:12 -04:00
|
|
|
mFramebufferResized = false;
|
|
|
|
recreateSwapChain();
|
2024-10-06 18:14:15 -04:00
|
|
|
return;
|
2024-10-05 23:08:12 -04:00
|
|
|
}
|
2024-10-01 18:54:41 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Creates a shader module from compiled shader code.
|
|
|
|
*
|
|
|
|
* @param code A vector of chars containing the compiled shader code.
|
|
|
|
* @return A unique shader module.
|
|
|
|
*
|
|
|
|
* This function takes compiled shader code and creates a Vulkan shader module from it.
|
|
|
|
*/
|
2024-10-18 14:34:26 -04:00
|
|
|
fn createShaderModule(const std::vector<u32>& code) -> vk::UniqueShaderModule {
|
2024-11-15 11:52:37 -05:00
|
|
|
vk::ShaderModuleCreateInfo createInfo { .codeSize = code.size() * sizeof(u32), .pCode = code.data() };
|
2024-10-01 18:30:31 -04:00
|
|
|
|
2024-10-06 18:14:15 -04:00
|
|
|
return mDevice->createShaderModuleUnique(createInfo);
|
2024-10-01 18:30:31 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Chooses the best surface format for the swap chain.
|
|
|
|
*
|
|
|
|
* @param availableFormats A vector of available surface formats.
|
|
|
|
* @return The chosen surface format.
|
|
|
|
*
|
|
|
|
* This function selects the best surface format from the available options,
|
|
|
|
* preferring SRGB color space when available.
|
|
|
|
*/
|
2024-10-05 23:08:12 -04:00
|
|
|
static fn chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats
|
|
|
|
) -> vk::SurfaceFormatKHR {
|
2024-10-14 23:32:52 -04:00
|
|
|
// If SRGB is available, use it
|
|
|
|
for (const vk::SurfaceFormatKHR& availableFormat : availableFormats)
|
2024-10-01 16:57:40 -04:00
|
|
|
if (availableFormat.format == vk::Format::eB8G8R8A8Srgb &&
|
|
|
|
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
|
|
|
return availableFormat;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Otherwise, use the first available format
|
2024-10-01 16:57:40 -04:00
|
|
|
return availableFormats[0];
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Chooses the best presentation mode for the swap chain.
|
|
|
|
*
|
|
|
|
* @param availablePresentModes A vector of available presentation modes.
|
|
|
|
* @return The chosen presentation mode.
|
|
|
|
*
|
|
|
|
* This function selects the best presentation mode from the available options,
|
|
|
|
* preferring mailbox mode (triple buffering) when available.
|
|
|
|
*/
|
2024-10-05 23:08:12 -04:00
|
|
|
static fn chooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes
|
|
|
|
) -> vk::PresentModeKHR {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if mailbox mode is available (adaptive sync)
|
|
|
|
for (const vk::PresentModeKHR& availablePresentMode : availablePresentModes)
|
2024-10-01 16:57:40 -04:00
|
|
|
if (availablePresentMode == vk::PresentModeKHR::eMailbox)
|
|
|
|
return availablePresentMode;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If mailbox mode is not available, use FIFO mode (vsync)
|
2024-10-01 16:57:40 -04:00
|
|
|
return vk::PresentModeKHR::eFifo;
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Chooses the swap extent (resolution) for the swap chain.
|
|
|
|
*
|
|
|
|
* @param capabilities The surface capabilities of the device.
|
|
|
|
* @return The chosen swap extent.
|
|
|
|
*
|
|
|
|
* This function determines the resolution of the swap chain images,
|
|
|
|
* taking into account the current window size and device limitations.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) -> vk::Extent2D {
|
|
|
|
// If the resolution is not UINT32_MAX, return it
|
|
|
|
// Otherwise, we need to set the resolution manually
|
2024-10-01 16:57:40 -04:00
|
|
|
if (capabilities.currentExtent.width != UINT32_MAX)
|
|
|
|
return capabilities.currentExtent;
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the window's resolution
|
2024-10-01 16:57:40 -04:00
|
|
|
u32 width = 0, height = 0;
|
|
|
|
std::tie(width, height) = mWindow->getFramebufferSize();
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Return the resolution clamped to the supported range
|
|
|
|
return {
|
|
|
|
.width = std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
|
|
|
.height = std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height),
|
|
|
|
};
|
2024-10-01 16:57:40 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Queries the swap chain support details for a physical device.
|
|
|
|
*
|
|
|
|
* @param device The physical device to query.
|
|
|
|
* @return A SwapChainSupportDetails struct containing the support information.
|
|
|
|
*
|
|
|
|
* This function retrieves information about the swap chain support,
|
|
|
|
* including surface capabilities, formats, and presentation modes.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn querySwapChainSupport(const vk::PhysicalDevice& device) -> SwapChainSupportDetails {
|
|
|
|
return {
|
|
|
|
.capabilities = device.getSurfaceCapabilitiesKHR(mSurface.get()),
|
|
|
|
.formats = device.getSurfaceFormatsKHR(mSurface.get()),
|
|
|
|
.present_modes = device.getSurfacePresentModesKHR(mSurface.get()),
|
|
|
|
};
|
2024-10-01 17:06:14 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Checks if a physical device is suitable for the application.
|
|
|
|
*
|
|
|
|
* @param device The physical device to check.
|
|
|
|
* @return True if the device is suitable, false otherwise.
|
|
|
|
*
|
|
|
|
* This function checks if a physical device meets all the requirements
|
|
|
|
* of the application, including queue families, extensions, and features.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn isDeviceSuitable(const vk::PhysicalDevice& device) -> bool {
|
|
|
|
// Get the queue families that support the required operations
|
2024-10-10 18:51:20 -04:00
|
|
|
QueueFamilyIndices qfIndices = findQueueFamilies(device);
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if the device supports the required extensions
|
2024-10-01 17:06:14 -04:00
|
|
|
bool extensionsSupported = checkDeviceExtensionSupport(device);
|
|
|
|
|
|
|
|
bool swapChainAdequate = false;
|
|
|
|
|
|
|
|
if (extensionsSupported) {
|
|
|
|
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if the swap chain is adequate (make sure it has
|
|
|
|
// at least one supported format and presentation mode)
|
2024-10-01 17:06:14 -04:00
|
|
|
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty();
|
|
|
|
}
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if the device supports the required features
|
2024-10-11 00:42:58 -04:00
|
|
|
vk::PhysicalDeviceFeatures supportedFeatures = device.getFeatures();
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If the device supports everything required, return true
|
2024-10-11 00:42:58 -04:00
|
|
|
return qfIndices.isComplete() && extensionsSupported && swapChainAdequate &&
|
|
|
|
supportedFeatures.samplerAnisotropy;
|
2024-10-01 17:06:14 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Checks if a device supports all required extensions.
|
|
|
|
*
|
|
|
|
* @param device The physical device to check.
|
|
|
|
* @return True if all required extensions are supported, false otherwise.
|
|
|
|
*
|
|
|
|
* This function verifies that a physical device supports all the
|
|
|
|
* extensions required by the application.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
static fn checkDeviceExtensionSupport(const vk::PhysicalDevice& device) -> bool {
|
|
|
|
// Get the available extensions
|
2024-10-06 18:14:15 -04:00
|
|
|
std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create a set of required extension names
|
2024-10-01 17:06:14 -04:00
|
|
|
std::set<string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Remove each required extension from the set of available extensions
|
2024-10-06 18:14:15 -04:00
|
|
|
for (const vk::ExtensionProperties& extension : availableExtensions)
|
2024-10-06 17:34:25 -04:00
|
|
|
requiredExtensions.erase(extension.extensionName);
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If the set is empty, all required extensions are supported
|
2024-10-01 17:06:14 -04:00
|
|
|
return requiredExtensions.empty();
|
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Finds queue families that support required operations.
|
|
|
|
*
|
|
|
|
* @param device The physical device to check.
|
|
|
|
* @return A QueueFamilyIndices struct with the found queue family indices.
|
|
|
|
*
|
|
|
|
* This function finds queue families that support graphics operations
|
|
|
|
* and presentation to the window surface.
|
|
|
|
*/
|
2024-10-14 23:32:52 -04:00
|
|
|
fn findQueueFamilies(const vk::PhysicalDevice& device) -> QueueFamilyIndices {
|
|
|
|
// Create a struct to store the queue family indices
|
2024-10-10 18:51:20 -04:00
|
|
|
QueueFamilyIndices qfIndices;
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Get the queue family properties
|
2024-10-01 17:06:14 -04:00
|
|
|
std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties();
|
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// For every queue family,
|
2024-10-01 17:06:14 -04:00
|
|
|
for (u32 i = 0; i < queueFamilies.size(); i++) {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if the queue family supports the required operations
|
2024-10-01 17:06:14 -04:00
|
|
|
if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics)
|
2024-10-10 18:51:20 -04:00
|
|
|
qfIndices.graphics_family = i;
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if the queue family supports presentation
|
2024-10-06 18:14:15 -04:00
|
|
|
vk::Bool32 queuePresentSupport = device.getSurfaceSupportKHR(i, mSurface.get());
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If the queue family supports presentation, set the present family index
|
2024-10-06 18:14:15 -04:00
|
|
|
if (queuePresentSupport)
|
2024-10-10 18:51:20 -04:00
|
|
|
qfIndices.present_family = i;
|
2024-10-01 17:06:14 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// If the queue family supports both operations, we're done
|
2024-10-10 18:51:20 -04:00
|
|
|
if (qfIndices.isComplete())
|
2024-10-01 17:06:14 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-10-10 18:51:20 -04:00
|
|
|
return qfIndices;
|
2024-10-01 17:06:14 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Checks if all requested validation layers are available.
|
|
|
|
*
|
|
|
|
* @return True if all validation layers are available, false otherwise.
|
|
|
|
*
|
|
|
|
* This function verifies that all requested validation layers are
|
|
|
|
* supported by the Vulkan implementation.
|
|
|
|
*/
|
2024-09-30 00:31:08 -04:00
|
|
|
static fn checkValidationLayerSupport() -> bool {
|
2024-10-06 18:14:15 -04:00
|
|
|
std::vector<vk::LayerProperties> availableLayers = vk::enumerateInstanceLayerProperties();
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create a set of available layer names
|
|
|
|
std::unordered_set<std::string_view> availableLayerNames;
|
|
|
|
for (const vk::LayerProperties& layerProperties : availableLayers)
|
|
|
|
availableLayerNames.emplace(layerProperties.layerName);
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Check if each validation layer is available
|
|
|
|
for (const std::string_view layerName : validationLayers)
|
|
|
|
if (availableLayerNames.find(layerName) == availableLayerNames.end())
|
|
|
|
return false; // Layer not found
|
2024-09-30 00:31:08 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
return true; // All validation layers are available
|
2024-09-30 00:31:08 -04:00
|
|
|
}
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief Debug callback function for Vulkan validation layers.
|
|
|
|
*
|
|
|
|
* @param pCallbackData Pointer to a structure containing the message details.
|
|
|
|
* @return vk::False to indicate the call should not be aborted.
|
|
|
|
*
|
|
|
|
* This function is called by Vulkan to report debug messages from
|
|
|
|
* validation layers. It prints the message to the console.
|
|
|
|
*/
|
2024-09-28 18:13:24 -04:00
|
|
|
static VKAPI_ATTR fn VKAPI_CALL debugCallback(
|
2024-10-18 00:29:10 -04:00
|
|
|
VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/,
|
|
|
|
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
|
|
|
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
|
|
|
void* /*pUserData*/
|
2024-09-30 00:57:13 -04:00
|
|
|
) -> vk::Bool32 {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Print the message to the console
|
|
|
|
// Because pCallbackData already gives the message severity
|
|
|
|
// and type, we don't need to print them, so they're unused.
|
2024-09-29 23:02:04 -04:00
|
|
|
fmt::println("Validation layer: {}", pCallbackData->pMessage);
|
2024-09-28 18:13:24 -04:00
|
|
|
|
2024-09-30 21:34:25 -04:00
|
|
|
return vk::False;
|
2024-09-25 23:03:56 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-10-18 00:29:10 -04:00
|
|
|
/**
|
|
|
|
* @brief The main function of the application.
|
|
|
|
*
|
|
|
|
* @return 0 if the application runs successfully, non-zero otherwise.
|
|
|
|
*
|
|
|
|
* This function initializes the Vulkan dynamic dispatcher, creates an instance
|
|
|
|
* of the VulkanApp class, and runs the application. It catches and reports any
|
|
|
|
* exceptions that occur during execution.
|
|
|
|
*/
|
2024-09-28 21:55:26 -04:00
|
|
|
fn main() -> i32 {
|
2024-10-14 23:32:52 -04:00
|
|
|
// Initialize dynamic function dispatcher
|
2024-10-12 15:39:20 -04:00
|
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init();
|
2024-10-06 12:26:44 -04:00
|
|
|
|
2024-10-14 23:32:52 -04:00
|
|
|
// Create an app instance
|
2024-09-29 23:11:12 -04:00
|
|
|
VulkanApp app;
|
2024-09-25 23:03:56 -04:00
|
|
|
|
|
|
|
try {
|
|
|
|
app.run();
|
|
|
|
} catch (const std::exception& e) {
|
2024-10-05 23:08:12 -04:00
|
|
|
fmt::println("{}", e.what());
|
2024-09-25 23:03:56 -04:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|