Compare commits
2 commits
45e8a99bd1
...
2f779d28ac
Author | SHA1 | Date | |
---|---|---|---|
Mars | 2f779d28ac | ||
Mars | 924fb0be7a |
|
@ -14,7 +14,7 @@ common_cpp_args = [
|
||||||
'-Wno-c++98-compat-pedantic',
|
'-Wno-c++98-compat-pedantic',
|
||||||
'-Wno-pre-c++20-compat-pedantic',
|
'-Wno-pre-c++20-compat-pedantic',
|
||||||
'-Wno-padded',
|
'-Wno-padded',
|
||||||
'-mavx2'
|
'-mavx2',
|
||||||
]
|
]
|
||||||
|
|
||||||
add_project_arguments(cpp.get_supported_arguments(common_cpp_args), language: 'cpp')
|
add_project_arguments(cpp.get_supported_arguments(common_cpp_args), language: 'cpp')
|
||||||
|
@ -40,4 +40,4 @@ executable(
|
||||||
sources: files('src/main.cpp'),
|
sources: files('src/main.cpp'),
|
||||||
include_directories: include_directories('include', is_system: true),
|
include_directories: include_directories('include', is_system: true),
|
||||||
dependencies: deps,
|
dependencies: deps,
|
||||||
)
|
)
|
12
shaders/fragment.glsl
Normal file
12
shaders/fragment.glsl
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(binding = 1) uniform sampler2D texSampler;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 fragColor;
|
||||||
|
layout(location = 1) in vec2 fragTexCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = texture(texSampler, fragTexCoord);
|
||||||
|
}
|
20
shaders/vertex.glsl
Normal file
20
shaders/vertex.glsl
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(binding = 0) uniform UniformBufferObject {
|
||||||
|
mat4 model;
|
||||||
|
mat4 view;
|
||||||
|
mat4 proj;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
layout(location = 1) in vec3 inColor;
|
||||||
|
layout(location = 2) in vec2 inTexCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 fragColor;
|
||||||
|
layout(location = 1) out vec2 fragTexCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
||||||
|
fragColor = inColor;
|
||||||
|
fragTexCoord = inTexCoord;
|
||||||
|
}
|
436
src/main.cpp
436
src/main.cpp
|
@ -1,5 +1,4 @@
|
||||||
// Include necessary headers
|
// Include necessary headers
|
||||||
#include <GLFW/glfw3.h>
|
|
||||||
#include <chrono> // For time-related functions
|
#include <chrono> // For time-related functions
|
||||||
#include <fmt/format.h> // For string formatting
|
#include <fmt/format.h> // For string formatting
|
||||||
#include <shaderc/shaderc.hpp> // For shader compilation
|
#include <shaderc/shaderc.hpp> // For shader compilation
|
||||||
|
@ -39,8 +38,8 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||||
#include "vkfw.hpp" // Include GLFW C++ bindings
|
#include "vkfw.hpp" // Include GLFW C++ bindings
|
||||||
|
|
||||||
// Constants for window dimensions
|
// Constants for window dimensions
|
||||||
constexpr i32 WIDTH = 800;
|
constexpr i32 WIDTH = 1920;
|
||||||
constexpr i32 HEIGHT = 600;
|
constexpr i32 HEIGHT = 1080;
|
||||||
|
|
||||||
// CAMERA_SPEED of camera movement
|
// CAMERA_SPEED of camera movement
|
||||||
constexpr f64 CAMERA_SPEED = 1.0;
|
constexpr f64 CAMERA_SPEED = 1.0;
|
||||||
|
@ -48,45 +47,9 @@ constexpr f64 CAMERA_SPEED = 1.0;
|
||||||
// Maximum number of frames that can be processed concurrently
|
// Maximum number of frames that can be processed concurrently
|
||||||
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
|
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
// Vertex shader
|
// Shader file paths
|
||||||
constexpr const char* vertShaderSrc = R"glsl(
|
constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl";
|
||||||
#version 450
|
constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl";
|
||||||
|
|
||||||
layout(binding = 0) uniform UniformBufferObject {
|
|
||||||
mat4 model;
|
|
||||||
mat4 view;
|
|
||||||
mat4 proj;
|
|
||||||
} ubo;
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 inPosition;
|
|
||||||
layout(location = 1) in vec3 inColor;
|
|
||||||
layout(location = 2) in vec2 inTexCoord;
|
|
||||||
|
|
||||||
layout(location = 0) out vec3 fragColor;
|
|
||||||
layout(location = 1) out vec2 fragTexCoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
|
||||||
fragColor = inColor;
|
|
||||||
fragTexCoord = inTexCoord;
|
|
||||||
}
|
|
||||||
)glsl";
|
|
||||||
|
|
||||||
// Fragment shader
|
|
||||||
constexpr const char* fragShaderSrc = R"glsl(
|
|
||||||
#version 450
|
|
||||||
|
|
||||||
layout(binding = 1) uniform sampler2D texSampler;
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 fragColor;
|
|
||||||
layout(location = 1) in vec2 fragTexCoord;
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
outColor = texture(texSampler, fragTexCoord);
|
|
||||||
}
|
|
||||||
)glsl";
|
|
||||||
|
|
||||||
// Validation layers for debug builds
|
// Validation layers for debug builds
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
@ -162,6 +125,7 @@ class VulkanApp {
|
||||||
vk::UniqueDescriptorSetLayout mDescriptorSetLayout; ///< Descriptor set layout
|
vk::UniqueDescriptorSetLayout mDescriptorSetLayout; ///< Descriptor set layout
|
||||||
vk::UniquePipelineLayout mPipelineLayout; ///< Pipeline layout
|
vk::UniquePipelineLayout mPipelineLayout; ///< Pipeline layout
|
||||||
vk::UniquePipeline mGraphicsPipeline; ///< Graphics pipeline
|
vk::UniquePipeline mGraphicsPipeline; ///< Graphics pipeline
|
||||||
|
vk::UniquePipeline mOldPipeline; ///< Previous graphics pipeline for safe deletion
|
||||||
|
|
||||||
vk::UniqueCommandPool mCommandPool; ///< Command pool for allocating command buffers
|
vk::UniqueCommandPool mCommandPool; ///< Command pool for allocating command buffers
|
||||||
|
|
||||||
|
@ -200,12 +164,24 @@ class VulkanApp {
|
||||||
mImageAvailableSemaphores; ///< Signals that an image is available for rendering
|
mImageAvailableSemaphores; ///< Signals that an image is available for rendering
|
||||||
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores; ///< Signals that rendering has finished
|
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores; ///< Signals that rendering has finished
|
||||||
std::vector<vk::UniqueFence> mInFlightFences; ///< Ensures CPU-GPU synchronization
|
std::vector<vk::UniqueFence> mInFlightFences; ///< Ensures CPU-GPU synchronization
|
||||||
|
std::vector<vk::Fence> mImagesInFlight; ///< Tracks which fences are in use by which swap chain images
|
||||||
|
|
||||||
bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized
|
bool mFramebufferResized = false; ///< Flag indicating if the framebuffer was resized
|
||||||
u32 mCurrentFrame = 0; ///< Index of the current frame being rendered
|
u32 mCurrentFrame = 0; ///< Index of the current frame being rendered
|
||||||
|
|
||||||
glm::mat4 mView; ///< View matrix
|
glm::mat4 mView; ///< View matrix
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// ImGui-related state
|
||||||
|
f32 mCameraSpeed = CAMERA_SPEED; ///< Current camera speed
|
||||||
|
f32 mFieldOfView = 90.0F; ///< Current field of view
|
||||||
|
bool mWireframeMode = false; ///< Wireframe rendering mode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Struct to store queue family indices.
|
* @brief Struct to store queue family indices.
|
||||||
*
|
*
|
||||||
|
@ -251,8 +227,8 @@ class VulkanApp {
|
||||||
glm::dvec3 front;
|
glm::dvec3 front;
|
||||||
glm::dvec3 up;
|
glm::dvec3 up;
|
||||||
glm::dvec3 right;
|
glm::dvec3 right;
|
||||||
double yaw;
|
f64 yaw;
|
||||||
double pitch;
|
f64 pitch;
|
||||||
|
|
||||||
Camera()
|
Camera()
|
||||||
: position(2.0, 2.0, 2.0),
|
: position(2.0, 2.0, 2.0),
|
||||||
|
@ -272,16 +248,44 @@ class VulkanApp {
|
||||||
return glm::lookAt(position, position + front, up);
|
return glm::lookAt(position, position + front, up);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moveForward(f64 deltaTime) -> void { position += front * CAMERA_SPEED * deltaTime; }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
fn moveBackward(f64 deltaTime) -> void { position -= front * CAMERA_SPEED * deltaTime; }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
fn moveLeft(f64 deltaTime) -> void { position -= right * CAMERA_SPEED * deltaTime; }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
fn moveRight(f64 deltaTime) -> void { position += right * CAMERA_SPEED * deltaTime; }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
fn rotate(double xoffset, double yoffset) -> void {
|
fn moveUp(f64 deltaTime) -> void { position += glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
|
||||||
const double sensitivity = 0.1;
|
|
||||||
|
fn moveDown(f64 deltaTime) -> void { position -= glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
|
||||||
|
|
||||||
|
fn rotate(f64 xoffset, f64 yoffset) -> void {
|
||||||
|
const f64 sensitivity = 0.1;
|
||||||
yaw += xoffset * sensitivity;
|
yaw += xoffset * sensitivity;
|
||||||
pitch += yoffset * sensitivity;
|
pitch += yoffset * sensitivity;
|
||||||
|
|
||||||
|
@ -311,46 +315,22 @@ class VulkanApp {
|
||||||
|
|
||||||
Camera mCamera; ///< Camera object
|
Camera mCamera; ///< Camera object
|
||||||
|
|
||||||
static fn processInput(vkfw::Window& window, Camera& camera, const f64& deltaTime) -> void {
|
static fn processInput(vkfw::Window& window, Camera& camera, const f32& deltaTime, const f32& cameraSpeed)
|
||||||
|
-> void {
|
||||||
if (window.getKey(vkfw::Key::eW) == vkfw::eTrue)
|
if (window.getKey(vkfw::Key::eW) == vkfw::eTrue)
|
||||||
camera.moveForward(deltaTime);
|
camera.moveForward(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
if (window.getKey(vkfw::Key::eA) == vkfw::eTrue)
|
if (window.getKey(vkfw::Key::eA) == vkfw::eTrue)
|
||||||
camera.moveLeft(deltaTime);
|
camera.moveLeft(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
if (window.getKey(vkfw::Key::eS) == vkfw::eTrue)
|
if (window.getKey(vkfw::Key::eS) == vkfw::eTrue)
|
||||||
camera.moveBackward(deltaTime);
|
camera.moveBackward(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
if (window.getKey(vkfw::Key::eD) == vkfw::eTrue)
|
if (window.getKey(vkfw::Key::eD) == vkfw::eTrue)
|
||||||
camera.moveRight(deltaTime);
|
camera.moveRight(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
|
if (window.getKey(vkfw::Key::eSpace) == vkfw::eTrue)
|
||||||
fmt::println(
|
camera.moveUp(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
"New position: {} {} {}", camera.getPosition()[0], camera.getPosition()[1], camera.getPosition()[2]
|
if (window.getKey(vkfw::Key::eLeftShift) == vkfw::eTrue)
|
||||||
);
|
camera.moveDown(static_cast<f64>(deltaTime * cameraSpeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
// static fn mouseCallback(const vkfw::Window& window, double xpos, double ypos) -> void {
|
|
||||||
// auto& camera = *static_cast<Camera*>(window.getUserPointer());
|
|
||||||
|
|
||||||
// static struct {
|
|
||||||
// bool first_mouse = true;
|
|
||||||
// double last_x = WIDTH / 2.0;
|
|
||||||
// double last_y = HEIGHT / 2.0;
|
|
||||||
// } MouseState;
|
|
||||||
|
|
||||||
// if (MouseState.first_mouse) {
|
|
||||||
// MouseState.last_x = xpos;
|
|
||||||
// MouseState.last_y = ypos;
|
|
||||||
// MouseState.first_mouse = false;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// double xoffset = xpos - MouseState.last_x;
|
|
||||||
// double yoffset = MouseState.last_y - ypos; // Reversed since y-coordinates range from bottom to top
|
|
||||||
|
|
||||||
// MouseState.last_x = xpos;
|
|
||||||
// MouseState.last_y = ypos;
|
|
||||||
|
|
||||||
// camera.rotate(xoffset, yoffset);
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the application window using GLFW.
|
* @brief Initializes the application window using GLFW.
|
||||||
*
|
*
|
||||||
|
@ -372,19 +352,77 @@ class VulkanApp {
|
||||||
mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints);
|
mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints);
|
||||||
|
|
||||||
// Set the user pointer to this instance, allowing us to access it in callbacks
|
// Set the user pointer to this instance, allowing us to access it in callbacks
|
||||||
mWindow->setUserPointer(this); // Store camera pointer for callbacks
|
mWindow->setUserPointer(this);
|
||||||
|
|
||||||
// Configure cursor
|
// Configure cursor for FPS-style camera control
|
||||||
// glfwSetInputMode(mWindow.get(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
mWindow->set<vkfw::InputMode::eCursor>(vkfw::CursorMode::eDisabled);
|
||||||
|
|
||||||
// Set up mouse callback
|
// Set up mouse callback
|
||||||
// mWindow->callbacks()->on_cursor_move = mouseCallback;
|
mWindow->callbacks()->on_cursor_move =
|
||||||
|
[this](const vkfw::Window& /*window*/, f64 xpos, f64 ypos) -> void {
|
||||||
|
if (!mCursorCaptured)
|
||||||
|
return; // Skip camera movement when cursor is not captured
|
||||||
|
|
||||||
|
if (mFirstMouse) {
|
||||||
|
mLastX = xpos;
|
||||||
|
mLastY = ypos;
|
||||||
|
mFirstMouse = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 xoffset = xpos - mLastX;
|
||||||
|
f64 yoffset = mLastY - ypos; // Reversed since y-coordinates range from bottom to top
|
||||||
|
|
||||||
|
mLastX = xpos;
|
||||||
|
mLastY = ypos;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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()); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Set up the window resize callback
|
// Set up the window resize callback
|
||||||
mWindow->callbacks()->on_window_resize =
|
mWindow->callbacks()->on_window_resize =
|
||||||
[](const vkfw::Window& window, usize /*width*/, usize /*height*/) -> void {
|
[this](const vkfw::Window& /*window*/, usize /*width*/, usize /*height*/) -> void {
|
||||||
// Set the framebuffer resized flag when the window is resized
|
// Set the framebuffer resized flag when the window is resized
|
||||||
std::bit_cast<VulkanApp*>(window.getUserPointer())->mFramebufferResized = true;
|
mFramebufferResized = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,34 +541,32 @@ class VulkanApp {
|
||||||
* polls for events and draws frames until the window is closed.
|
* polls for events and draws frames until the window is closed.
|
||||||
*/
|
*/
|
||||||
fn mainLoop() -> void {
|
fn mainLoop() -> void {
|
||||||
// Set initial time variables
|
f64 lastFrame = 0.0;
|
||||||
f64 lastFrame = 0.0;
|
f64 deltaTime = 0.0;
|
||||||
f64 deltaTime = 0.0;
|
f64 lastFpsUpdate = 0.0;
|
||||||
|
i32 frameCounter = 0;
|
||||||
|
|
||||||
// While the window is open,
|
|
||||||
while (!mWindow->shouldClose()) {
|
while (!mWindow->shouldClose()) {
|
||||||
// Calculate time between frames
|
|
||||||
f64 currentFrame = vkfw::getTime();
|
f64 currentFrame = vkfw::getTime();
|
||||||
deltaTime = currentFrame - lastFrame;
|
deltaTime = currentFrame - lastFrame;
|
||||||
lastFrame = currentFrame;
|
lastFrame = currentFrame;
|
||||||
|
|
||||||
// Process input for camera movement
|
processInput(mWindow.get(), mCamera, static_cast<f32>(deltaTime), mCameraSpeed);
|
||||||
processInput(mWindow.get(), mCamera, deltaTime);
|
|
||||||
|
|
||||||
// Create view matrix from camera
|
|
||||||
mView = mCamera.getViewMatrix();
|
mView = mCamera.getViewMatrix();
|
||||||
|
|
||||||
// Update the FPS counter
|
if (currentFrame - lastFpsUpdate > 1.0) {
|
||||||
// updateFPS(mWindow.get(), "Vulkan");
|
mWindow->setTitle(
|
||||||
|
fmt::format("Vulkan - {:.0f}FPS", static_cast<f32>(frameCounter / (currentFrame - lastFpsUpdate)))
|
||||||
|
);
|
||||||
|
lastFpsUpdate = currentFrame;
|
||||||
|
frameCounter = 0;
|
||||||
|
}
|
||||||
|
++frameCounter;
|
||||||
|
|
||||||
// Poll for events
|
|
||||||
vkfw::pollEvents();
|
vkfw::pollEvents();
|
||||||
|
|
||||||
// Draw a frame
|
|
||||||
drawFrame();
|
drawFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the device to finish
|
|
||||||
mDevice->waitIdle();
|
mDevice->waitIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,27 +589,29 @@ class VulkanApp {
|
||||||
* It cleans up the old swap chain and creates a new one with updated properties.
|
* It cleans up the old swap chain and creates a new one with updated properties.
|
||||||
*/
|
*/
|
||||||
fn recreateSwapChain() -> void {
|
fn recreateSwapChain() -> void {
|
||||||
// Get the new width and height
|
i32 width = 0, height = 0;
|
||||||
auto [width, height] = mWindow->getFramebufferSize();
|
|
||||||
|
|
||||||
// If the width or height are 0, wait for events
|
|
||||||
while (width == 0 || height == 0) {
|
while (width == 0 || height == 0) {
|
||||||
std::tie(width, height) = mWindow->getFramebufferSize();
|
std::tie(width, height) = mWindow->getFramebufferSize();
|
||||||
vkfw::waitEvents();
|
vkfw::waitEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the device to finish
|
|
||||||
mDevice->waitIdle();
|
mDevice->waitIdle();
|
||||||
|
|
||||||
// Clean up the swap chain
|
|
||||||
cleanupSwapChain();
|
cleanupSwapChain();
|
||||||
|
|
||||||
// Create a new swap chain
|
|
||||||
createSwapChain();
|
createSwapChain();
|
||||||
createImageViews();
|
createImageViews();
|
||||||
|
createRenderPass();
|
||||||
|
createGraphicsPipeline();
|
||||||
createColorResources();
|
createColorResources();
|
||||||
createDepthResources();
|
createDepthResources();
|
||||||
createFramebuffers();
|
createFramebuffers();
|
||||||
|
createUniformBuffers();
|
||||||
|
createDescriptorPool();
|
||||||
|
createDescriptorSets();
|
||||||
|
createCommandBuffers();
|
||||||
|
|
||||||
|
mImagesInFlight.resize(mSwapChainImages.size(), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -735,22 +773,24 @@ class VulkanApp {
|
||||||
// Set the queue priority
|
// Set the queue priority
|
||||||
f32 queuePriority = 1.0F;
|
f32 queuePriority = 1.0F;
|
||||||
|
|
||||||
// For each unique queue family, create a new queue
|
// For each unique queue family, create a queue create info
|
||||||
for (const u32& queueFamily : uniqueQueueFamilies) {
|
queueCreateInfos.reserve(uniqueQueueFamilies.size());
|
||||||
vk::DeviceQueueCreateInfo queueCreateInfo {
|
|
||||||
|
for (u32 queueFamily : uniqueQueueFamilies)
|
||||||
|
queueCreateInfos.push_back({
|
||||||
.queueFamilyIndex = queueFamily,
|
.queueFamilyIndex = queueFamily,
|
||||||
.queueCount = 1,
|
.queueCount = 1,
|
||||||
.pQueuePriorities = &queuePriority,
|
.pQueuePriorities = &queuePriority,
|
||||||
};
|
});
|
||||||
|
|
||||||
queueCreateInfos.emplace_back(queueCreateInfo);
|
// Enable required features
|
||||||
}
|
|
||||||
|
|
||||||
// Enable anisotropic filtering
|
|
||||||
vk::PhysicalDeviceFeatures deviceFeatures {
|
vk::PhysicalDeviceFeatures deviceFeatures {
|
||||||
|
.fillModeNonSolid = vk::True, // Required for wireframe rendering
|
||||||
|
.wideLines = vk::True, // Required for line width > 1.0
|
||||||
.samplerAnisotropy = vk::True,
|
.samplerAnisotropy = vk::True,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create the logical device
|
||||||
vk::DeviceCreateInfo createInfo {
|
vk::DeviceCreateInfo createInfo {
|
||||||
.queueCreateInfoCount = static_cast<u32>(queueCreateInfos.size()),
|
.queueCreateInfoCount = static_cast<u32>(queueCreateInfos.size()),
|
||||||
.pQueueCreateInfos = queueCreateInfos.data(),
|
.pQueueCreateInfos = queueCreateInfos.data(),
|
||||||
|
@ -759,8 +799,9 @@ class VulkanApp {
|
||||||
.pEnabledFeatures = &deviceFeatures,
|
.pEnabledFeatures = &deviceFeatures,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the logical device and set the graphics and present queues
|
mDevice = mPhysicalDevice.createDeviceUnique(createInfo);
|
||||||
mDevice = mPhysicalDevice.createDeviceUnique(createInfo);
|
|
||||||
|
// Get the graphics and present queues
|
||||||
mGraphicsQueue = mDevice->getQueue(qfIndices.graphics_family.value(), 0);
|
mGraphicsQueue = mDevice->getQueue(qfIndices.graphics_family.value(), 0);
|
||||||
mPresentQueue = mDevice->getQueue(qfIndices.present_family.value(), 0);
|
mPresentQueue = mDevice->getQueue(qfIndices.present_family.value(), 0);
|
||||||
}
|
}
|
||||||
|
@ -967,9 +1008,9 @@ class VulkanApp {
|
||||||
*/
|
*/
|
||||||
fn createGraphicsPipeline() -> void {
|
fn createGraphicsPipeline() -> void {
|
||||||
std::vector<u32> vertShaderCode =
|
std::vector<u32> vertShaderCode =
|
||||||
ShaderCompiler::getCompiledShader(vertShaderSrc, shaderc_shader_kind::shaderc_vertex_shader, "vert");
|
ShaderCompiler::getCompiledShader(VERTEX_SHADER_PATH, shaderc_shader_kind::shaderc_vertex_shader);
|
||||||
std::vector<u32> fragShaderCode =
|
std::vector<u32> fragShaderCode =
|
||||||
ShaderCompiler::getCompiledShader(fragShaderSrc, shaderc_shader_kind::shaderc_fragment_shader, "frag");
|
ShaderCompiler::getCompiledShader(FRAGMENT_SHADER_PATH, shaderc_shader_kind::shaderc_fragment_shader);
|
||||||
|
|
||||||
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
||||||
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
||||||
|
@ -1015,11 +1056,11 @@ class VulkanApp {
|
||||||
vk::PipelineRasterizationStateCreateInfo rasterizer {
|
vk::PipelineRasterizationStateCreateInfo rasterizer {
|
||||||
.depthClampEnable = vk::False,
|
.depthClampEnable = vk::False,
|
||||||
.rasterizerDiscardEnable = vk::False,
|
.rasterizerDiscardEnable = vk::False,
|
||||||
.polygonMode = vk::PolygonMode::eFill,
|
.polygonMode = mWireframeMode ? vk::PolygonMode::eLine : vk::PolygonMode::eFill,
|
||||||
.cullMode = vk::CullModeFlagBits::eBack,
|
.cullMode = vk::CullModeFlagBits::eBack,
|
||||||
.frontFace = vk::FrontFace::eCounterClockwise,
|
.frontFace = vk::FrontFace::eCounterClockwise,
|
||||||
.depthBiasEnable = vk::False,
|
.depthBiasEnable = vk::False,
|
||||||
.lineWidth = 1.0F,
|
.lineWidth = mWireframeMode ? 2.0F : 1.0F, // Thicker lines in wireframe mode
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::PipelineMultisampleStateCreateInfo multisampling {
|
vk::PipelineMultisampleStateCreateInfo multisampling {
|
||||||
|
@ -1046,7 +1087,7 @@ class VulkanApp {
|
||||||
.logicOp = vk::LogicOp::eCopy,
|
.logicOp = vk::LogicOp::eCopy,
|
||||||
.attachmentCount = 1,
|
.attachmentCount = 1,
|
||||||
.pAttachments = &colorBlendAttachment,
|
.pAttachments = &colorBlendAttachment,
|
||||||
.blendConstants = std::array<float, 4> { 0.0F, 0.0F, 0.0F, 0.0F },
|
.blendConstants = std::array<f32, 4> { 0.0F, 0.0F, 0.0F, 0.0F },
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<vk::DynamicState> dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
std::vector<vk::DynamicState> dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
||||||
|
@ -2046,31 +2087,22 @@ class VulkanApp {
|
||||||
* This function records drawing commands into the given command buffer.
|
* This function records drawing commands into the given command buffer.
|
||||||
*/
|
*/
|
||||||
fn recordCommandBuffer(const vk::CommandBuffer& commandBuffer, const u32& imageIndex) -> void {
|
fn recordCommandBuffer(const vk::CommandBuffer& commandBuffer, const u32& imageIndex) -> void {
|
||||||
// Define the command buffer begin info
|
|
||||||
vk::CommandBufferBeginInfo beginInfo {};
|
|
||||||
|
|
||||||
// Begin the command buffer
|
// Begin the command buffer
|
||||||
commandBuffer.begin(beginInfo);
|
commandBuffer.begin({ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
|
||||||
|
|
||||||
// Define the render pass begin info
|
// Define clear values for color and depth
|
||||||
std::array<vk::ClearValue, 2> clearValues {
|
std::array<vk::ClearValue, 2> clearValues = {
|
||||||
// Set the color buffer to black
|
vk::ClearValue { .color = { std::array<f32, 4> { 0.0F, 0.0F, 0.0F, 1.0F } } },
|
||||||
vk::ClearValue { .color = { .uint32 = std::array<u32, 4> { 0, 0, 0, 255 } } },
|
vk::ClearValue { .depthStencil = { 1.0F, 0 } },
|
||||||
// Set the depth buffer to 1.0F
|
|
||||||
vk::ClearValue { .depthStencil = { .depth = 1.0F, .stencil = 0 } },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Define the render pass info
|
// Begin the render pass
|
||||||
vk::RenderPassBeginInfo renderPassInfo {
|
vk::RenderPassBeginInfo renderPassInfo {
|
||||||
// Render pass itself
|
.renderPass = mRenderPass.get(),
|
||||||
.renderPass = mRenderPass.get(),
|
.framebuffer = mSwapChainFramebuffers[imageIndex].get(),
|
||||||
// Current framebuffer
|
.renderArea = { .offset = { 0, 0 }, .extent = mSwapChainExtent },
|
||||||
.framebuffer = mSwapChainFramebuffers[imageIndex].get(),
|
|
||||||
// Render area (entire framebuffer)
|
|
||||||
.renderArea = { .offset = { .x = 0, .y = 0 }, .extent = mSwapChainExtent },
|
|
||||||
// Clear values for the attachments
|
|
||||||
.clearValueCount = static_cast<u32>(clearValues.size()),
|
.clearValueCount = static_cast<u32>(clearValues.size()),
|
||||||
.pClearValues = clearValues.data(),
|
.pClearValues = clearValues.data()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Begin the render pass
|
// Begin the render pass
|
||||||
|
@ -2118,18 +2150,100 @@ class VulkanApp {
|
||||||
nullptr
|
nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw the indexed vertices
|
UniformBufferObject ubo {
|
||||||
commandBuffer.drawIndexed(static_cast<u32>(mIndices.size()), 1, 0, 0, 0);
|
// 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(
|
||||||
|
glm::radians(mFieldOfView),
|
||||||
|
static_cast<f32>(mSwapChainExtent.width) / static_cast<f32>(mSwapChainExtent.height),
|
||||||
|
0.1F,
|
||||||
|
100.0F
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
ImGui_ImplVulkan_NewFrame();
|
// Flip the Y axis, because glm was designed for OpenGL
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ubo.proj[1][1] *= -1;
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
// Your ImGui code here
|
// Copy the uniform buffer object to the mapped memory
|
||||||
ImGui::ShowDemoWindow();
|
memcpy(mUniformBuffersMapped[mCurrentFrame], &ubo, sizeof(ubo));
|
||||||
|
|
||||||
ImGui::Render();
|
// Example: Add extra clones with different translations
|
||||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer);
|
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)) };
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only render ImGui when cursor is not captured
|
||||||
|
if (!mCursorCaptured) {
|
||||||
|
ImGui_ImplVulkan_NewFrame();
|
||||||
|
ImGui_ImplGlfw_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
// Create ImGui window with useful controls
|
||||||
|
ImGui::Begin("Settings", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
|
||||||
|
// Set initial window size (this will be the minimum size due to AlwaysAutoResize)
|
||||||
|
ImGui::SetWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
|
// Camera settings
|
||||||
|
if (ImGui::CollapsingHeader("Camera")) {
|
||||||
|
ImGui::SliderFloat("Camera Speed", &mCameraSpeed, 0.1F, 5.0F);
|
||||||
|
|
||||||
|
glm::dvec3 pos = mCamera.getPosition();
|
||||||
|
ImGui::Text("Position: (%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendering settings
|
||||||
|
if (ImGui::CollapsingHeader("Rendering")) {
|
||||||
|
if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) {
|
||||||
|
// Wait for all operations to complete
|
||||||
|
mDevice->waitIdle();
|
||||||
|
|
||||||
|
// Store the old pipeline for deletion after the next frame
|
||||||
|
if (mGraphicsPipeline)
|
||||||
|
mOldPipeline = std::move(mGraphicsPipeline);
|
||||||
|
|
||||||
|
// Recreate the pipeline
|
||||||
|
createGraphicsPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SliderFloat("Field of View", &mFieldOfView, 45.0F, 120.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance metrics
|
||||||
|
if (ImGui::CollapsingHeader("Performance")) {
|
||||||
|
const f32 framerate = ImGui::GetIO().Framerate;
|
||||||
|
ImGui::Text("%.1f FPS", static_cast<f64>(framerate));
|
||||||
|
ImGui::Text("%.3f ms/frame", static_cast<f64>(1000.0F / framerate));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
// End the render pass
|
// End the render pass
|
||||||
commandBuffer.endRenderPass();
|
commandBuffer.endRenderPass();
|
||||||
|
@ -2193,7 +2307,7 @@ class VulkanApp {
|
||||||
.view = mView,
|
.view = mView,
|
||||||
// Projection matrix - glm::perspective(fov, aspect, near, far)
|
// Projection matrix - glm::perspective(fov, aspect, near, far)
|
||||||
.proj = glm::perspective(
|
.proj = glm::perspective(
|
||||||
glm::radians(45.0F),
|
glm::radians(mFieldOfView),
|
||||||
static_cast<f32>(mSwapChainExtent.width) / static_cast<f32>(mSwapChainExtent.height),
|
static_cast<f32>(mSwapChainExtent.width) / static_cast<f32>(mSwapChainExtent.height),
|
||||||
0.1F,
|
0.1F,
|
||||||
100.0F
|
100.0F
|
||||||
|
@ -2227,6 +2341,12 @@ class VulkanApp {
|
||||||
if (result != vk::Result::eSuccess)
|
if (result != vk::Result::eSuccess)
|
||||||
throw std::runtime_error("Failed to wait for fences!");
|
throw std::runtime_error("Failed to wait for fences!");
|
||||||
|
|
||||||
|
// Clear old pipeline if it exists
|
||||||
|
if (mOldPipeline) {
|
||||||
|
mDevice->waitIdle(); // Wait for all operations to complete
|
||||||
|
mOldPipeline.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Acquire the next image from the swap chain
|
// Acquire the next image from the swap chain
|
||||||
auto [imageIndexResult, imageIndexValue] = mDevice->acquireNextImageKHR(
|
auto [imageIndexResult, imageIndexValue] = mDevice->acquireNextImageKHR(
|
||||||
mSwapChain.get(), UINT64_MAX, mImageAvailableSemaphores[mCurrentFrame].get(), nullptr
|
mSwapChain.get(), UINT64_MAX, mImageAvailableSemaphores[mCurrentFrame].get(), nullptr
|
||||||
|
@ -2268,7 +2388,7 @@ class VulkanApp {
|
||||||
.pSignalSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
.pSignalSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Submit the graphics queue
|
// Submit the command buffer
|
||||||
mGraphicsQueue.submit(submitInfo, mInFlightFences[mCurrentFrame].get());
|
mGraphicsQueue.submit(submitInfo, mInFlightFences[mCurrentFrame].get());
|
||||||
|
|
||||||
vk::PresentInfoKHR presentInfo {
|
vk::PresentInfoKHR presentInfo {
|
||||||
|
@ -2294,7 +2414,7 @@ class VulkanApp {
|
||||||
|
|
||||||
// Increment the current frame (or loop back to 0)
|
// Increment the current frame (or loop back to 0)
|
||||||
mCurrentFrame = (mCurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
mCurrentFrame = (mCurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
} catch (vk::OutOfDateKHRError& /*err*/) {
|
} catch (const vk::SystemError& /*err*/) {
|
||||||
// Recreate the swap chain if it's out of date
|
// Recreate the swap chain if it's out of date
|
||||||
mFramebufferResized = false;
|
mFramebufferResized = false;
|
||||||
recreateSwapChain();
|
recreateSwapChain();
|
||||||
|
|
|
@ -1,53 +1,95 @@
|
||||||
|
/**
|
||||||
|
* @file shaders.hpp
|
||||||
|
* @brief SPIR-V shader compilation and caching system.
|
||||||
|
*
|
||||||
|
* This file provides functionality for compiling GLSL shaders to SPIR-V and
|
||||||
|
* managing a shader cache system. It supports automatic recompilation when
|
||||||
|
* source files are modified and efficient caching of compiled shaders.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <ios>
|
||||||
#include <shaderc/shaderc.hpp>
|
#include <shaderc/shaderc.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles shader compilation and caching operations.
|
||||||
|
*
|
||||||
|
* This class provides static methods for compiling GLSL shaders to SPIR-V
|
||||||
|
* and managing a cache system. It automatically detects when shaders need
|
||||||
|
* to be recompiled based on file timestamps and provides efficient caching
|
||||||
|
* of compiled shader binaries.
|
||||||
|
*/
|
||||||
class ShaderCompiler {
|
class ShaderCompiler {
|
||||||
public:
|
public:
|
||||||
ShaderCompiler() = default;
|
ShaderCompiler() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Compiles or loads a cached SPIR-V shader from a file.
|
* @brief Compiles or retrieves a cached SPIR-V shader.
|
||||||
*
|
*
|
||||||
* @param shaderSource The source code of the shader.
|
* @param shaderPath Path to the GLSL shader source file
|
||||||
* @param kind The type of shader being compiled (vertex, fragment, etc.).
|
* @param kind Type of shader (vertex, fragment, compute, etc.)
|
||||||
* @param shaderName The name used for caching the compiled shader.
|
* @return std::vector<u32> Compiled SPIR-V binary code
|
||||||
* @return std::vector<u32> A vector containing the compiled SPIR-V code.
|
* @throws std::runtime_error If shader compilation fails or file is not found
|
||||||
* @throws std::runtime_error if shader compilation fails.
|
|
||||||
*
|
*
|
||||||
* This function attempts to load a shader from the cache. If the shader
|
* This function performs the following steps:
|
||||||
* is not found in the cache, it compiles the shader from the source code
|
* 1. Checks if a cached version exists and is up-to-date
|
||||||
* and saves the result to the cache for future use.
|
* 2. Loads from cache if available and valid
|
||||||
|
* 3. Otherwise, compiles the shader from source
|
||||||
|
* 4. Caches the newly compiled shader for future use
|
||||||
|
* 5. Returns the SPIR-V binary code
|
||||||
*/
|
*/
|
||||||
static fn getCompiledShader(
|
static fn getCompiledShader(const std::filesystem::path& shaderPath, const shaderc_shader_kind& kind)
|
||||||
const char* shaderSource,
|
-> std::vector<u32> {
|
||||||
const shaderc_shader_kind& kind,
|
|
||||||
const string& shaderName
|
|
||||||
) -> std::vector<u32> {
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
const filesystem::path cacheFile = getCacheFilePath(shaderName);
|
// Convert to absolute path if relative
|
||||||
|
filesystem::path absPath = filesystem::absolute(shaderPath);
|
||||||
|
|
||||||
// Try loading from cache first
|
if (!filesystem::exists(absPath))
|
||||||
vector<u32> spirvCode = loadCachedShader(cacheFile);
|
throw runtime_error("Shader file not found: " + absPath.string());
|
||||||
|
|
||||||
if (!spirvCode.empty()) {
|
const string shaderName = absPath.stem().string();
|
||||||
fmt::println("Loaded shader from cache: {}", cacheFile.string());
|
const filesystem::path cacheFile = getCacheFilePath(shaderName);
|
||||||
return spirvCode;
|
|
||||||
|
// Check if we need to recompile by comparing timestamps
|
||||||
|
if (filesystem::exists(cacheFile)) {
|
||||||
|
const auto sourceTime = filesystem::last_write_time(absPath);
|
||||||
|
const auto cacheTime = filesystem::last_write_time(cacheFile);
|
||||||
|
|
||||||
|
if (cacheTime >= sourceTime) {
|
||||||
|
// Cache is up to date, load it
|
||||||
|
vector<u32> spirvCode = loadCachedShader(cacheFile);
|
||||||
|
if (!spirvCode.empty()) {
|
||||||
|
fmt::println("Loaded shader from cache: {}", cacheFile.string());
|
||||||
|
return spirvCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache miss, compile the shader
|
// Need to compile the shader
|
||||||
spirvCode = compileShader(shaderSource, kind);
|
fmt::println("Compiling shader: {}", absPath.string());
|
||||||
|
|
||||||
|
// Read shader source
|
||||||
|
ifstream file(absPath);
|
||||||
|
if (!file)
|
||||||
|
throw runtime_error("Failed to open shader file: " + absPath.string());
|
||||||
|
|
||||||
|
string shaderSource((istreambuf_iterator<char>(file)), istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Compile the shader
|
||||||
|
vector<u32> spirvCode = compileShader(shaderSource.c_str(), kind);
|
||||||
|
|
||||||
if (spirvCode.empty())
|
if (spirvCode.empty())
|
||||||
throw runtime_error("Shader compilation failed for: " + shaderName);
|
throw runtime_error("Shader compilation failed for: " + absPath.string());
|
||||||
|
|
||||||
// Cache the compiled SPIR-V binary
|
// Cache the compiled SPIR-V binary
|
||||||
saveCompiledShader(spirvCode, cacheFile.string());
|
saveCompiledShader(spirvCode, cacheFile.string());
|
||||||
|
@ -56,13 +98,17 @@ class ShaderCompiler {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief Generates the cache file path based on the operating system.
|
* @brief Determines the platform-specific shader cache directory.
|
||||||
*
|
*
|
||||||
* @param shaderName The name used for the shader file.
|
* @param shaderName Base name of the shader file
|
||||||
* @return string The full path to the cache file.
|
* @return std::filesystem::path Full path to the cache file
|
||||||
*
|
*
|
||||||
* This function determines the appropriate directory for caching shaders
|
* Cache locations:
|
||||||
* based on the operating system and returns the full path for the specified shader name.
|
* - Windows: %LOCALAPPDATA%/VulkanApp/Shaders/
|
||||||
|
* - macOS: ~/Library/Application Support/VulkanApp/Shaders/
|
||||||
|
* - Linux: ~/.local/share/VulkanApp/Shaders/
|
||||||
|
*
|
||||||
|
* The directory is created if it doesn't exist.
|
||||||
*/
|
*/
|
||||||
static fn getCacheFilePath(const string& shaderName) -> std::filesystem::path {
|
static fn getCacheFilePath(const string& shaderName) -> std::filesystem::path {
|
||||||
using namespace std::filesystem;
|
using namespace std::filesystem;
|
||||||
|
@ -76,91 +122,88 @@ class ShaderCompiler {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!exists(cacheDir))
|
if (!exists(cacheDir))
|
||||||
create_directories(cacheDir); // Create the directory if it doesn't exist
|
create_directories(cacheDir);
|
||||||
|
|
||||||
return cacheDir / (shaderName + ".spv");
|
return cacheDir / (shaderName + ".spv");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Compiles GLSL code to SPIR-V.
|
* @brief Loads a cached SPIR-V shader from disk.
|
||||||
*
|
*
|
||||||
* @param source The GLSL source code to compile.
|
* @param cachePath Path to the cached shader file
|
||||||
* @param kind The type of shader being compiled.
|
* @return std::vector<u32> SPIR-V binary code, empty if loading fails
|
||||||
* @return std::vector<u32> A vector containing the compiled SPIR-V code.
|
|
||||||
*
|
*
|
||||||
* This function uses the shaderc library to compile GLSL source code into
|
* Reads the binary SPIR-V data from the cache file. Returns an empty
|
||||||
* SPIR-V binary format. If the compilation fails, an empty vector is returned.
|
* vector if the file cannot be opened or read properly.
|
||||||
*/
|
*/
|
||||||
static fn compileShader(const char* source, const shaderc_shader_kind& kind) -> std::vector<u32> {
|
static fn loadCachedShader(const std::filesystem::path& cachePath) -> std::vector<u32> {
|
||||||
using namespace shaderc;
|
std::ifstream file(cachePath, std::ios::binary);
|
||||||
|
if (!file.is_open())
|
||||||
Compiler compiler;
|
|
||||||
CompileOptions options;
|
|
||||||
|
|
||||||
SpvCompilationResult result = compiler.CompileGlslToSpv(source, kind, "shader.glsl", "main", options);
|
|
||||||
|
|
||||||
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
|
||||||
fmt::println(stderr, "Shader compilation failed: {}", result.GetErrorMessage());
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
|
|
||||||
return { result.cbegin(), result.cend() };
|
// Read file size
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
const std::streamoff fileSize = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
// Allocate buffer and read data
|
||||||
|
std::vector<u32> buffer(static_cast<u32>(fileSize) / sizeof(u32));
|
||||||
|
file.read(std::bit_cast<char*>(buffer.data()), fileSize);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads compiled SPIR-V shader code from a cache file.
|
* @brief Compiles GLSL source code to SPIR-V.
|
||||||
*
|
*
|
||||||
* @param cacheFile The path to the cached SPIR-V file.
|
* @param source GLSL shader source code
|
||||||
* @return std::vector<u32> A vector containing the loaded SPIR-V code.
|
* @param kind Type of shader being compiled
|
||||||
* @throws std::runtime_error if the file cannot be read.
|
* @return std::vector<u32> Compiled SPIR-V binary code
|
||||||
*
|
*
|
||||||
* This function checks if the specified cache file exists and reads its
|
* Uses the shaderc library to compile GLSL to SPIR-V. The compilation
|
||||||
* contents into a vector. If the file cannot be opened, an exception is thrown.
|
* is performed with optimization level set to performance and generates
|
||||||
|
* debug information in debug builds.
|
||||||
*/
|
*/
|
||||||
static fn loadCachedShader(const std::filesystem::path& cacheFile) -> std::vector<u32> {
|
static fn compileShader(const char* source, shaderc_shader_kind kind) -> std::vector<u32> {
|
||||||
using namespace std;
|
shaderc::Compiler compiler;
|
||||||
|
shaderc::CompileOptions options;
|
||||||
|
|
||||||
if (!filesystem::exists(cacheFile))
|
// Set compilation options
|
||||||
return {}; // No cached file
|
#ifdef NDEBUG
|
||||||
|
options.SetOptimizationLevel(shaderc_optimization_level_performance);
|
||||||
|
#else
|
||||||
|
options.SetOptimizationLevel(shaderc_optimization_level_zero);
|
||||||
|
options.SetGenerateDebugInfo();
|
||||||
|
#endif
|
||||||
|
|
||||||
ifstream file(cacheFile, ios::binary);
|
// Compile the shader
|
||||||
|
shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, kind, "shader", options);
|
||||||
|
|
||||||
// Check if the file was successfully opened
|
if (module.GetCompilationStatus() != shaderc_compilation_status_success)
|
||||||
if (!file)
|
return {};
|
||||||
throw runtime_error("Failed to open cached shader file: " + cacheFile.string());
|
|
||||||
|
|
||||||
usize fileSize = filesystem::file_size(cacheFile);
|
return { module.cbegin(), module.cend() };
|
||||||
vector<u32> spirvCode(fileSize / sizeof(u32));
|
|
||||||
|
|
||||||
// Read entire file content into the vector
|
|
||||||
if (!file.read(bit_cast<char*>(spirvCode.data()), static_cast<streamsize>(fileSize)))
|
|
||||||
throw runtime_error("Failed to read cached shader file: " + cacheFile.string());
|
|
||||||
|
|
||||||
return spirvCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Saves compiled SPIR-V binary to a cache file.
|
* @brief Saves compiled SPIR-V code to the cache.
|
||||||
*
|
*
|
||||||
* @param spirvCode The SPIR-V code to save.
|
* @param spirv Compiled SPIR-V binary code
|
||||||
* @param cacheFile The path to the file where the SPIR-V code will be saved.
|
* @param cachePath Path where the cache file should be saved
|
||||||
* @throws std::runtime_error if the file cannot be written.
|
* @return bool True if save was successful, false otherwise
|
||||||
*
|
*
|
||||||
* This function writes the compiled SPIR-V binary to the specified file.
|
* Writes the SPIR-V binary to disk for future use. Creates any
|
||||||
* If the file cannot be opened or written, an exception is thrown.
|
* necessary parent directories if they don't exist.
|
||||||
*/
|
*/
|
||||||
static fn saveCompiledShader(const std::vector<u32>& spirvCode, const string& cacheFile) -> void {
|
static fn saveCompiledShader(const std::vector<u32>& spirv, const std::string& cachePath) -> bool {
|
||||||
using namespace std;
|
std::ofstream file(cachePath, std::ios::binary);
|
||||||
|
if (!file.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
ofstream file(cacheFile, ios::binary);
|
file.write(
|
||||||
|
std::bit_cast<const char*>(spirv.data()), static_cast<std::streamsize>(spirv.size() * sizeof(u32))
|
||||||
|
);
|
||||||
|
|
||||||
// Check if the file was successfully opened
|
return file.good();
|
||||||
if (!file)
|
|
||||||
throw runtime_error("Failed to open file for saving shader: " + cacheFile);
|
|
||||||
|
|
||||||
if (!file.write(
|
|
||||||
bit_cast<const char*>(spirvCode.data()), static_cast<streamsize>(spirvCode.size() * sizeof(u32))
|
|
||||||
))
|
|
||||||
throw runtime_error("Failed to save shader to cache: " + cacheFile);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,128 +1,152 @@
|
||||||
|
/**
|
||||||
|
* @file types.hpp
|
||||||
|
* @brief Core type definitions and aliases for the project.
|
||||||
|
*
|
||||||
|
* This file provides a centralized location for type definitions used throughout
|
||||||
|
* the project. It includes fixed-width integer types, floating-point types, and
|
||||||
|
* commonly used GLM vector types. The type aliases are designed to improve code
|
||||||
|
* readability and ensure consistent type usage across the codebase.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#define fn auto
|
#define fn auto
|
||||||
|
|
||||||
|
// Integer Types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef u8
|
* @brief 8-bit unsigned integer.
|
||||||
* @brief Represents an 8-bit unsigned integer.
|
* @details Range: 0 to 255
|
||||||
*
|
* Used for byte-level operations and color channel values.
|
||||||
* This type alias is used for 8-bit unsigned integers, ranging from 0 to 255.
|
|
||||||
* It is based on the std::uint8_t type.
|
|
||||||
*/
|
*/
|
||||||
using u8 = std::uint8_t;
|
using u8 = std::uint8_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef u16
|
* @brief 16-bit unsigned integer.
|
||||||
* @brief Represents a 16-bit unsigned integer.
|
* @details Range: 0 to 65,535
|
||||||
*
|
* Used for texture coordinates and small indices.
|
||||||
* This type alias is used for 16-bit unsigned integers, ranging from 0 to 65,535.
|
|
||||||
* It is based on the std::uint16_t type.
|
|
||||||
*/
|
*/
|
||||||
using u16 = std::uint16_t;
|
using u16 = std::uint16_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef u32
|
* @brief 32-bit unsigned integer.
|
||||||
* @brief Represents a 32-bit unsigned integer.
|
* @details Range: 0 to 4,294,967,295
|
||||||
*
|
* Used for array sizes, indices, and flags.
|
||||||
* This type alias is used for 32-bit unsigned integers, ranging from 0 to 4,294,967,295.
|
|
||||||
* It is based on the std::uint32_t type.
|
|
||||||
*/
|
*/
|
||||||
using u32 = std::uint32_t;
|
using u32 = std::uint32_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef u64
|
* @brief 64-bit unsigned integer.
|
||||||
* @brief Represents a 64-bit unsigned integer.
|
* @details Range: 0 to 18,446,744,073,709,551,615
|
||||||
*
|
* Used for large indices and timestamps.
|
||||||
* This type alias is used for 64-bit unsigned integers, ranging from 0 to
|
|
||||||
* 18,446,744,073,709,551,615. It is based on the std::uint64_t type.
|
|
||||||
*/
|
*/
|
||||||
using u64 = std::uint64_t;
|
using u64 = std::uint64_t;
|
||||||
|
|
||||||
// Type Aliases for Signed Integers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef i8
|
* @brief 8-bit signed integer.
|
||||||
* @brief Represents an 8-bit signed integer.
|
* @details Range: -128 to 127
|
||||||
*
|
* Used for small signed values and relative offsets.
|
||||||
* This type alias is used for 8-bit signed integers, ranging from -128 to 127.
|
|
||||||
* It is based on the std::int8_t type.
|
|
||||||
*/
|
*/
|
||||||
using i8 = std::int8_t;
|
using i8 = std::int8_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef i16
|
* @brief 16-bit signed integer.
|
||||||
* @brief Represents a 16-bit signed integer.
|
* @details Range: -32,768 to 32,767
|
||||||
*
|
* Used for medium-range signed values.
|
||||||
* This type alias is used for 16-bit signed integers, ranging from -32,768 to 32,767.
|
|
||||||
* It is based on the std::int16_t type.
|
|
||||||
*/
|
*/
|
||||||
using i16 = std::int16_t;
|
using i16 = std::int16_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef i32
|
* @brief 32-bit signed integer.
|
||||||
* @brief Represents a 32-bit signed integer.
|
* @details Range: -2,147,483,648 to 2,147,483,647
|
||||||
*
|
* Primary signed integer type for general use.
|
||||||
* This type alias is used for 32-bit signed integers, ranging from -2,147,483,648 to 2,147,483,647.
|
|
||||||
* It is based on the std::int32_t type.
|
|
||||||
*/
|
*/
|
||||||
using i32 = std::int32_t;
|
using i32 = std::int32_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef i64
|
* @brief 64-bit signed integer.
|
||||||
* @brief Represents a 64-bit signed integer.
|
* @details Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
|
||||||
*
|
* Used for large signed values and time calculations.
|
||||||
* This type alias is used for 64-bit signed integers, ranging from -9,223,372,036,854,775,808 to
|
|
||||||
* 9,223,372,036,854,775,807. It is based on the std::int64_t type.
|
|
||||||
*/
|
*/
|
||||||
using i64 = std::int64_t;
|
using i64 = std::int64_t;
|
||||||
|
|
||||||
// Type Aliases for Floating-Point Numbers
|
// Floating-Point Types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef f32
|
* @brief 32-bit floating-point number.
|
||||||
* @brief Represents a 32-bit floating-point number.
|
* @details IEEE 754 single-precision
|
||||||
*
|
* Used for graphics calculations, positions, and colors.
|
||||||
* This type alias is used for 32-bit floating-point numbers, which follow the IEEE 754 standard.
|
|
||||||
* It is based on the float type.
|
|
||||||
*/
|
*/
|
||||||
using f32 = float;
|
using f32 = float;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef f64
|
* @brief 64-bit floating-point number.
|
||||||
* @brief Represents a 64-bit floating-point number.
|
* @details IEEE 754 double-precision
|
||||||
*
|
* Used for high-precision calculations and physics.
|
||||||
* This type alias is used for 64-bit floating-point numbers, which follow the IEEE 754 standard.
|
|
||||||
* It is based on the double type.
|
|
||||||
*/
|
*/
|
||||||
using f64 = double;
|
using f64 = double;
|
||||||
|
|
||||||
// Type Aliases for Size Types
|
// Size Types
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef usize
|
* @brief Unsigned size type.
|
||||||
* @brief Represents an unsigned size type.
|
* @details Platform-dependent size (32/64-bit)
|
||||||
*
|
* Used for memory sizes and container sizes.
|
||||||
* This type alias is used for representing the size of objects in bytes.
|
|
||||||
* It is based on the std::size_t type, which is the result type of the sizeof operator.
|
|
||||||
*/
|
*/
|
||||||
using usize = std::size_t;
|
using usize = std::size_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef isize
|
* @brief Signed size type.
|
||||||
* @brief Represents a signed size type.
|
* @details Platform-dependent size (32/64-bit)
|
||||||
*
|
* Used for pointer arithmetic and container differences.
|
||||||
* This type alias is used for representing pointer differences.
|
|
||||||
* It is based on the std::ptrdiff_t type, which is the signed integer type returned when
|
|
||||||
* subtracting two pointers.
|
|
||||||
*/
|
*/
|
||||||
using isize = std::ptrdiff_t;
|
using isize = std::ptrdiff_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef string
|
* @brief String type alias.
|
||||||
* @brief Represents a string.
|
* @details Standard string type for text handling.
|
||||||
*/
|
*/
|
||||||
using string = std::string;
|
using string = std::string;
|
||||||
|
|
||||||
|
// GLM Vector Types
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 2D vector with 32-bit float components.
|
||||||
|
* @details Used for 2D positions, texture coordinates.
|
||||||
|
*/
|
||||||
|
using vec2 = glm::f32vec2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 3D vector with 32-bit float components.
|
||||||
|
* @details Used for 3D positions, colors, normals.
|
||||||
|
*/
|
||||||
|
using vec3 = glm::f32vec3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 4D vector with 32-bit float components.
|
||||||
|
* @details Used for homogeneous coordinates, quaternions.
|
||||||
|
*/
|
||||||
|
using vec4 = glm::f32vec4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 2D vector with 64-bit float components.
|
||||||
|
* @details Used for high-precision 2D calculations.
|
||||||
|
*/
|
||||||
|
using dvec2 = glm::f64vec2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 3D vector with 64-bit float components.
|
||||||
|
* @details Used for high-precision 3D positions and directions.
|
||||||
|
*/
|
||||||
|
using dvec3 = glm::f64vec3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 4D vector with 64-bit float components.
|
||||||
|
* @details Used for high-precision homogeneous coordinates.
|
||||||
|
*/
|
||||||
|
using dvec4 = glm::f64vec4;
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/**
|
||||||
|
* @file unique_image.hpp
|
||||||
|
* @brief Provides RAII wrapper for image loading and management.
|
||||||
|
*
|
||||||
|
* This file implements a RAII-compliant image handling class that uses stb_image
|
||||||
|
* for loading various image formats. It ensures proper resource management and
|
||||||
|
* provides a safe interface for image data access.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
@ -7,45 +16,53 @@
|
||||||
|
|
||||||
namespace stb {
|
namespace stb {
|
||||||
/**
|
/**
|
||||||
* @brief A class that handles loading and managing image data.
|
* @brief RAII wrapper for image data loaded via stb_image.
|
||||||
*
|
*
|
||||||
* This class uses the stb_image library to load images from the filesystem
|
* This class provides safe resource management for loaded images, ensuring proper
|
||||||
* and provides access to the image data, dimensions, and channel count.
|
* cleanup of image data. It supports move semantics but prevents copying to maintain
|
||||||
|
* single ownership of image resources.
|
||||||
*/
|
*/
|
||||||
class UniqueImage {
|
class UniqueImage {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a UniqueImage object and loads an image from the specified path.
|
* @brief Constructs a UniqueImage by loading from file.
|
||||||
*
|
*
|
||||||
* @param path The filesystem path to the image file to load.
|
* @param path Filesystem path to the image file.
|
||||||
|
* @throws std::runtime_error If image loading fails.
|
||||||
|
*
|
||||||
|
* Automatically loads the image data from the specified file using stb_image.
|
||||||
|
* The image data is stored in RGBA format with 8 bits per channel.
|
||||||
*/
|
*/
|
||||||
UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }
|
UniqueImage(const std::filesystem::path& path) { load(path.string().c_str()); }
|
||||||
|
|
||||||
// Deleted copy constructor to prevent copying.
|
// Prevent copying to maintain single ownership
|
||||||
UniqueImage(const UniqueImage&) = delete;
|
UniqueImage(const UniqueImage&) = delete;
|
||||||
|
fn operator=(const UniqueImage&) -> UniqueImage& = delete;
|
||||||
// Deleted copy assignment operator to prevent copying.
|
|
||||||
fn operator=(const UniqueImage&)->UniqueImage& = delete;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Move constructor for UniqueImage.
|
* @brief Move constructor for transferring image ownership.
|
||||||
*
|
*
|
||||||
* @param other The UniqueImage object from which to move resources.
|
* @param other Source UniqueImage to move from.
|
||||||
*
|
*
|
||||||
* Transfers ownership of resources from another UniqueImage object.
|
* Transfers ownership of image data from another UniqueImage instance,
|
||||||
|
* leaving the source in a valid but empty state.
|
||||||
*/
|
*/
|
||||||
UniqueImage(UniqueImage&& other) noexcept
|
UniqueImage(UniqueImage&& other) noexcept
|
||||||
: mData(other.mData), mWidth(other.mWidth), mHeight(other.mHeight), mChannels(other.mChannels) {
|
: mData(other.mData),
|
||||||
|
mWidth(static_cast<i32>(other.mWidth)),
|
||||||
|
mHeight(static_cast<i32>(other.mHeight)),
|
||||||
|
mChannels(static_cast<i32>(other.mChannels)) {
|
||||||
other.mData = nullptr;
|
other.mData = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Move assignment operator for UniqueImage.
|
* @brief Move assignment operator for transferring image ownership.
|
||||||
*
|
*
|
||||||
* @param other The UniqueImage object from which to move resources.
|
* @param other Source UniqueImage to move from.
|
||||||
* @return Reference to this object.
|
* @return Reference to this object.
|
||||||
*
|
*
|
||||||
* Transfers ownership of resources from another UniqueImage object.
|
* Safely transfers ownership of image data, ensuring proper cleanup of
|
||||||
|
* existing resources before the transfer.
|
||||||
*/
|
*/
|
||||||
fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
|
fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
|
@ -53,18 +70,19 @@ namespace stb {
|
||||||
stbi_image_free(mData);
|
stbi_image_free(mData);
|
||||||
|
|
||||||
mData = other.mData;
|
mData = other.mData;
|
||||||
mWidth = other.mWidth;
|
mWidth = static_cast<i32>(other.mWidth);
|
||||||
mHeight = other.mHeight;
|
mHeight = static_cast<i32>(other.mHeight);
|
||||||
mChannels = other.mChannels;
|
mChannels = static_cast<i32>(other.mChannels);
|
||||||
other.mData = nullptr;
|
other.mData = nullptr;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destructor for UniqueImage.
|
* @brief Destructor that ensures proper cleanup of image resources.
|
||||||
*
|
*
|
||||||
* Frees the image data if it is allocated.
|
* Automatically frees the image data using stb_image_free when the
|
||||||
|
* object goes out of scope.
|
||||||
*/
|
*/
|
||||||
~UniqueImage() {
|
~UniqueImage() {
|
||||||
if (mData)
|
if (mData)
|
||||||
|
@ -72,50 +90,51 @@ namespace stb {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the image data.
|
* @brief Get raw pointer to image data.
|
||||||
*
|
* @return Pointer to the raw image data in memory.
|
||||||
* @return Pointer to the image data in memory.
|
*
|
||||||
|
* The data is stored in row-major order with either RGB or RGBA format,
|
||||||
|
* depending on the source image.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] fn getData() const -> u8* { return mData; }
|
[[nodiscard]] fn getData() const -> u8* { return mData; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the width of the image.
|
* @brief Get image width in pixels.
|
||||||
*
|
* @return Width of the image.
|
||||||
* @return The width of the image in pixels.
|
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] fn getWidth() const -> i32 { return mWidth; }
|
[[nodiscard]] fn getWidth() const -> i32 { return mWidth; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the height of the image.
|
* @brief Get image height in pixels.
|
||||||
*
|
* @return Height of the image.
|
||||||
* @return The height of the image in pixels.
|
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] fn getHeight() const -> i32 { return mHeight; }
|
[[nodiscard]] fn getHeight() const -> i32 { return mHeight; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the number of channels in the image.
|
* @brief Get number of color channels.
|
||||||
*
|
* @return Number of channels (e.g., 3 for RGB, 4 for RGBA).
|
||||||
* @return The number of channels in the image (e.g., 3 for RGB, 4 for RGBA).
|
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] fn getChannels() const -> i32 { return mChannels; }
|
[[nodiscard]] fn getChannels() const -> i32 { return mChannels; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u8* mData = nullptr; ///< Pointer to the image data.
|
|
||||||
i32 mWidth = 0; ///< Width of the image in pixels.
|
|
||||||
i32 mHeight = 0; ///< Height of the image in pixels.
|
|
||||||
i32 mChannels = 0; ///< Number of channels in the image.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads an image from a file.
|
* @brief Internal helper function to load image data.
|
||||||
*
|
*
|
||||||
* @param filename The name of the file from which to load the image.
|
* @param path Path to the image file.
|
||||||
* @throws std::runtime_error If the image fails to load.
|
* @throws std::runtime_error If image loading fails.
|
||||||
|
*
|
||||||
|
* Uses stb_image to load the image data, automatically detecting the
|
||||||
|
* format and number of channels from the file.
|
||||||
*/
|
*/
|
||||||
fn load(const char* filename) -> void {
|
fn load(const char* path) -> void {
|
||||||
mData = stbi_load(filename, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
|
mData = stbi_load(path, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
|
||||||
|
|
||||||
if (!mData)
|
if (!mData)
|
||||||
throw std::runtime_error("Failed to load image: " + string(stbi_failure_reason()));
|
throw std::runtime_error(fmt::format("Failed to load texture image: {}", path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u8* mData = nullptr; ///< Raw image data in memory
|
||||||
|
i32 mWidth = 0; ///< Image width in pixels
|
||||||
|
i32 mHeight = 0; ///< Image height in pixels
|
||||||
|
i32 mChannels = 0; ///< Number of color channels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/**
|
||||||
|
* @file vertex.hpp
|
||||||
|
* @brief Defines the vertex structure and its associated utilities for 3D rendering.
|
||||||
|
*
|
||||||
|
* This file contains the Vertex structure used for 3D model representation in the Vulkan
|
||||||
|
* graphics pipeline. It includes position, color, and texture coordinate data, along with
|
||||||
|
* Vulkan-specific descriptors for vertex input handling.
|
||||||
|
*/
|
||||||
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
@ -8,45 +17,81 @@
|
||||||
|
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
// Vertex data for the model
|
/**
|
||||||
|
* @brief Represents a vertex in 3D space with color and texture information.
|
||||||
|
*
|
||||||
|
* This structure defines a vertex with all its attributes required for rendering,
|
||||||
|
* including position in 3D space, RGB color, and texture coordinates. It also
|
||||||
|
* provides methods for Vulkan vertex input configuration.
|
||||||
|
*/
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
// Position of the vertex
|
vec3 pos; ///< Position of the vertex in 3D space (x, y, z)
|
||||||
glm::vec3 pos;
|
vec3 color; ///< RGB color values, each component in range [0, 1]
|
||||||
// Color of the vertex (in RGB)
|
vec2 tex_coord; ///< Texture coordinates (u, v) for texture mapping
|
||||||
glm::vec3 color;
|
|
||||||
// Texture coordinates of the vertex
|
|
||||||
glm::vec2 tex_coord;
|
|
||||||
|
|
||||||
// Returns the binding description for the vertex
|
/**
|
||||||
|
* @brief Provides the vertex binding description for Vulkan.
|
||||||
|
*
|
||||||
|
* @return vk::VertexInputBindingDescription Describes how to bind vertex data to GPU memory.
|
||||||
|
*
|
||||||
|
* The binding description specifies:
|
||||||
|
* - Binding index (0)
|
||||||
|
* - Stride (size of one vertex)
|
||||||
|
* - Input rate (per-vertex data)
|
||||||
|
*/
|
||||||
static fn getBindingDescription() -> vk::VertexInputBindingDescription {
|
static fn getBindingDescription() -> vk::VertexInputBindingDescription {
|
||||||
return { .binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex };
|
return { .binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the attribute descriptions for the vertex
|
/**
|
||||||
|
* @brief Provides attribute descriptions for vertex data interpretation.
|
||||||
|
*
|
||||||
|
* @return std::array<vk::VertexInputAttributeDescription, 3> Array of descriptions for position, color, and texture coordinates.
|
||||||
|
*
|
||||||
|
* The attribute descriptions specify:
|
||||||
|
* - Location indices (0 for position, 1 for color, 2 for texture coordinates)
|
||||||
|
* - Binding point (0)
|
||||||
|
* - Data format (R32G32B32 for vec3, R32G32 for vec2)
|
||||||
|
* - Offset of each attribute in the vertex structure
|
||||||
|
*/
|
||||||
static fn getAttributeDescriptions() -> std::array<vk::VertexInputAttributeDescription, 3> {
|
static fn getAttributeDescriptions() -> std::array<vk::VertexInputAttributeDescription, 3> {
|
||||||
return {
|
return {
|
||||||
// Position attribute
|
vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) },
|
||||||
vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) },
|
vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) },
|
||||||
// Color attribute
|
vk::VertexInputAttributeDescription { 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, tex_coord) }
|
||||||
vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) },
|
|
||||||
// Texture coordinate attribute
|
|
||||||
vk::VertexInputAttributeDescription { 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, tex_coord) }
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overload the equality operator for the vertex
|
/**
|
||||||
fn operator==(const Vertex& other) const->bool {
|
* @brief Compares two vertices for equality.
|
||||||
|
*
|
||||||
|
* @param other The vertex to compare with.
|
||||||
|
* @return bool True if vertices are identical in position, color, and texture coordinates.
|
||||||
|
*/
|
||||||
|
fn operator==(const Vertex& other) const -> bool {
|
||||||
return pos == other.pos && color == other.color && tex_coord == other.tex_coord;
|
return pos == other.pos && color == other.color && tex_coord == other.tex_coord;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hash function for the vertex
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
/**
|
||||||
|
* @brief Hash function specialization for Vertex type.
|
||||||
|
*
|
||||||
|
* This specialization allows Vertex objects to be used as keys in unordered containers.
|
||||||
|
* The hash combines position, color, and texture coordinate data using bit operations
|
||||||
|
* to create a unique hash value.
|
||||||
|
*/
|
||||||
template <>
|
template <>
|
||||||
struct hash<Vertex> {
|
struct hash<Vertex> {
|
||||||
fn operator()(Vertex const& vertex) const->size_t {
|
/**
|
||||||
return ((hash<glm::vec3>()(vertex.pos) ^ (hash<glm::vec3>()(vertex.color) << 1)) >> 1) ^
|
* @brief Computes hash value for a vertex.
|
||||||
(hash<glm::vec2>()(vertex.tex_coord) << 1);
|
*
|
||||||
|
* @param vertex The vertex to hash.
|
||||||
|
* @return size_t Hash value combining all vertex attributes.
|
||||||
|
*/
|
||||||
|
fn operator()(Vertex const& vertex) const -> size_t {
|
||||||
|
return ((hash<vec3>()(vertex.pos) ^ (hash<vec3>()(vertex.color) << 1)) >> 1) ^
|
||||||
|
(hash<vec2>()(vertex.tex_coord) << 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue