Compare commits

..

No commits in common. "refactor" and "main" have entirely different histories.

29 changed files with 2327 additions and 36855 deletions

View file

@ -20,7 +20,6 @@ IndentWidth: 2
NamespaceIndentation: All
SpaceBeforeCpp11BracedList: true
SpacesBeforeTrailingComments: 1
Standard: Latest
IncludeBlocks: Regroup
IncludeCategories:

View file

@ -1,3 +1,4 @@
# noinspection SpellCheckingInspection
Checks: >
*,
-ctad-maybe-unsupported,
@ -7,11 +8,9 @@ Checks: >
-bugprone-implicit-widening-of-multiplication-result,
-cert-env33-c,
-concurrency-mt-unsafe,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-fuchsia-*,
-google-*,

4
.clangd Normal file
View file

@ -0,0 +1,4 @@
Diagnostics:
Suppress: >
-Wmissing-template-arg-list-after-template-kw,
-Wctad-maybe-unsupported

3
.envrc
View file

@ -1 +1,2 @@
use_flake
export NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1
use_flake . --impure

1
.gitignore vendored
View file

@ -2,4 +2,3 @@
.direnv/
.vscode/
build/
imgui.ini

View file

@ -1,10 +1,6 @@
# This file was generated by nvfetcher, please do not modify it manually.
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
{
fetchgit,
fetchurl,
fetchFromGitHub,
dockerTools,
}: {
fmt = {
pname = "fmt";
version = "11.0.2";

View file

@ -1,78 +1,27 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1688025799,
"narHash": "sha256-ktpB4dRtnksm9F5WawoIkEneh1nrEvuxb5lJFt1iOyw=",
"owner": "nix-community",
"repo": "flake-compat",
"rev": "8bf105319d44f6b9f0d764efa4fdef9f1cc9ba1c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flake-compat",
"type": "github"
}
},
"nixos-asahi": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1731536673,
"narHash": "sha256-bEkcE98/AwmKzlipCruFdf3KK3CBmtfzabquHtrKURM=",
"owner": "zzywysm",
"repo": "nixos-asahi",
"rev": "09d4e26b7d49323faad37264763cf57988cfd720",
"type": "github"
},
"original": {
"owner": "zzywysm",
"repo": "nixos-asahi",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1731139594,
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
"owner": "nixos",
"lastModified": 1727065772,
"narHash": "sha256-U9baiEXL2YsS67QKlBAPIUq+OB+eUPKv8n1vGNdhiec=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
"rev": "989dc4cbf6a95f2e5fefc8cd61d2198a8fb6834a",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1731640008,
"narHash": "sha256-81hruQPQXZf1xtcyYct9XPvBWvKIk6/DSDCc5XcYKT4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "63de88ed5f65084bb5cde3bdcb716e28cc03a933",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1726871744,
"narHash": "sha256-V5LpfdHyQkUF7RfOaDPrZDP+oqz88lTJrMT1+stXNwo=",
"lastModified": 1726481836,
"narHash": "sha256-MWTBH4dd5zIz2iatDb8IkqSjIeFum9jAqkFxgHLdzO4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a1d92660c6b3b7c26fb883500a80ea9d33321be2",
"rev": "20f9370d5f588fb8c72e844c54511cab054b5f40",
"type": "github"
},
"original": {
@ -84,28 +33,11 @@
},
"root": {
"inputs": {
"nixos-asahi": "nixos-asahi",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs",
"treefmt-nix": "treefmt-nix",
"utils": "utils"
}
},
"rust-overlay": {
"flake": false,
"locked": {
"lastModified": 1686795910,
"narHash": "sha256-jDa40qRZ0GRQtP9EMZdf+uCbvzuLnJglTUI2JoHfWDc=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "5c2b97c0a9bc5217fc3dfb1555aae0fb756d99f9",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
@ -123,14 +55,14 @@
},
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1730321837,
"narHash": "sha256-vK+a09qq19QNu2MlLcvN4qcRctJbqWkX7ahgPZ/+maI=",
"lastModified": 1726734507,
"narHash": "sha256-VUH5O5AcOSxb0uL/m34dDkxFKP6WLQ6y4I1B4+N3L2w=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "746901bb8dba96d154b66492a29f5db0693dbfcc",
"rev": "ee41a466c2255a3abe6bc50fc6be927cdee57a9f",
"type": "github"
},
"original": {
@ -144,11 +76,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {

View file

@ -2,7 +2,6 @@
description = "C/C++ environment";
inputs = {
nixos-asahi.url = "github:zzywysm/nixos-asahi";
nixpkgs.url = "github:NixOS/nixpkgs";
treefmt-nix.url = "github:numtide/treefmt-nix";
utils.url = "github:numtide/flake-utils";
@ -11,20 +10,13 @@
outputs = {
self,
nixpkgs,
nixos-asahi,
treefmt-nix,
utils,
...
}:
utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
config = {
allowUnfree = true;
allowUnsupportedSystem = true;
};
};
pkgs = import nixpkgs {inherit system;};
stdenv =
if pkgs.hostPlatform.isLinux
@ -42,21 +34,14 @@
fmt = mkPkg "fmt";
imgui = pkgs.imgui.override {
IMGUI_BUILD_GLFW_BINDING = true;
IMGUI_BUILD_VULKAN_BINDING = true;
};
deps = with pkgs; [
fmt
glfw
glm
imgui
shaderc.dev
shaderc.lib
vulkan-extension-layer
vulkan-memory-allocator
vulkan-utility-libraries
vulkan-headers
vulkan-loader
vulkan-tools
];
@ -97,6 +82,7 @@
projectRootFile = "flake.nix";
programs = {
alejandra.enable = true;
deadnix.enable = true;
clang-format = {
enable = true;
@ -106,17 +92,15 @@
};
devShell = mkShell.override {inherit stdenv;} {
buildInputs =
packages =
[
alejandra
bear
cmake
(llvmPackages_18.clang-tools.override {enableLibcxx = true;})
lldb
meson
nil
ninja
#nvfetcher
nvfetcher
pkg-config
unzip
@ -129,20 +113,6 @@
VULKAN_SDK = "${vulkan-headers}";
VK_LAYER_PATH = "${vulkan-validation-layers}/share/vulkan/explicit_layer.d";
VK_ICD_FILENAMES =
if stdenv.isDarwin
then "${darwin.moltenvk}/share/vulkan/icd.d/MoltenVK_icd.json"
else let
vulkanDir =
if stdenv.hostPlatform.isx86_64
then "${mesa.drivers}/share/vulkan/icd.d"
else "${nixos-asahi.packages.aarch64-linux.mesa-asahi-edge.drivers}/share/vulkan/icd.d";
vulkanFiles = builtins.filter (file: builtins.match ".*\\.json$" file != null) (builtins.attrNames (builtins.readDir vulkanDir));
vulkanPaths = lib.concatStringsSep ":" (map (file: "${vulkanDir}/${file}") vulkanFiles);
in
if stdenv.hostPlatform.isx86_64
then "${linuxPackages_latest.nvidia_x11_beta}/share/vulkan/icd.d/nvidia_icd.x86_64.json:${vulkanPaths}"
else vulkanPaths;
shellHook = ''
export PATH="${llvmPackages_18.clang-tools.override {enableLibcxx = true;}}/bin:$PATH"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ project(
'graphics-test',
'cpp',
version: '0.1.0',
default_options: ['cpp_std=c++26', 'warning_level=everything', 'buildtype=debugoptimized'],
default_options: ['cpp_std=c++20', 'warning_level=everything', 'buildtype=debugoptimized'],
)
cpp = meson.get_compiler('cpp')
@ -12,32 +12,37 @@ common_cpp_args = [
'-Wno-c++20-extensions',
'-Wno-c++98-compat',
'-Wno-c++98-compat-pedantic',
'-Wno-pre-c++20-compat-pedantic',
'-Wno-disabled-macro-expansion',
'-Wno-missing-prototypes',
'-Wno-padded',
'-mavx2',
'-Wno-pre-c++20-compat-pedantic',
'-Wno-switch-default',
'-Wno-unsafe-buffer-usage',
'-Wunused-function',
'-fvisibility=hidden',
]
add_project_arguments(cpp.get_supported_arguments(common_cpp_args), language: 'cpp')
deps = [
dependency('fmt', include_type: 'system'),
dependency('glfw3', include_type: 'system'),
dependency('glm', include_type: 'system'),
dependency('vulkan', include_type: 'system'),
dependency('shaderc', include_type: 'system'),
source_file_names = [
'src/main.cpp',
]
imgui_dep = dependency('imgui', required: false, include_type: 'system')
sources = []
if not imgui_dep.found()
imgui_dep = cpp.find_library('imgui', required: true)
endif
foreach file : source_file_names
sources += files(file)
endforeach
deps += imgui_dep
deps = [
dependency('fmt', static: true),
dependency('glfw3'),
dependency('glm'),
dependency('vulkan'),
]
executable(
'graphics-test',
sources: files('src/main.cpp'),
include_directories: include_directories('include', is_system: true),
sources,
dependencies: deps,
)

File diff suppressed because it is too large Load diff

View file

@ -1,12 +0,0 @@
#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);
}

View file

@ -1,20 +0,0 @@
#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;
}

View file

@ -1,34 +0,0 @@
#pragma once
#include <array>
#include <vulkan/vulkan.hpp>
#include "../core/types.hpp"
namespace config {
// Window settings
constexpr i32 WIDTH = 1920;
constexpr i32 HEIGHT = 1080;
// Vulkan settings
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
// Shader paths
constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl";
constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl";
// Validation layers for debug builds
#ifndef NDEBUG
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };
#endif
// Required device extensions (platform-specific)
#ifdef __APPLE__
constexpr std::array<const char*, 2> deviceExtensions = { vk::KHRSwapchainExtensionName,
vk::KHRPortabilitySubsetExtensionName };
#else
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
#endif
} // namespace config

View file

@ -1,95 +0,0 @@
#pragma once
#include <unordered_set>
#include <vulkan/vulkan.hpp>
#include "../config/config.hpp"
#include "queue_structures.hpp"
#include "types.hpp"
namespace core {
/**
* @brief Gets the maximum usable sample count for MSAA.
*
* @param device Physical device to check
* @return vk::SampleCountFlagBits Maximum sample count supported
*/
static fn getMaxUsableSampleCount(const vk::PhysicalDevice& device) -> vk::SampleCountFlagBits {
vk::PhysicalDeviceProperties physicalDeviceProperties = device.getProperties();
vk::SampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts &
physicalDeviceProperties.limits.framebufferDepthSampleCounts;
if (counts & vk::SampleCountFlagBits::e64)
return vk::SampleCountFlagBits::e64;
if (counts & vk::SampleCountFlagBits::e32)
return vk::SampleCountFlagBits::e32;
if (counts & vk::SampleCountFlagBits::e16)
return vk::SampleCountFlagBits::e16;
if (counts & vk::SampleCountFlagBits::e8)
return vk::SampleCountFlagBits::e8;
if (counts & vk::SampleCountFlagBits::e4)
return vk::SampleCountFlagBits::e4;
if (counts & vk::SampleCountFlagBits::e2)
return vk::SampleCountFlagBits::e2;
return vk::SampleCountFlagBits::e1;
}
/**
* @brief Checks if a device supports the required extensions.
*
* @param device Physical device to check
* @return bool True if all required extensions are supported
*/
static fn checkDeviceExtensionSupport(const vk::PhysicalDevice& device) -> bool {
// Get the available extensions
std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
// Create a set of required extensions
std::unordered_set<std::string> requiredExtensions(
config::deviceExtensions.begin(), config::deviceExtensions.end()
);
// For each available extension,
for (const auto& extension : availableExtensions)
// Remove it from the required extensions set if it's required
requiredExtensions.erase(extension.extensionName);
// If the set is empty, all required extensions are supported
return requiredExtensions.empty();
}
/**
* @brief Checks if a physical device is suitable for the application.
*
* @param device Physical device to check
* @param surface Surface to check presentation support against
* @return bool True if device is suitable, false otherwise
*/
static fn isDeviceSuitable(const vk::PhysicalDevice& device, const vk::SurfaceKHR& surface) -> bool {
// Get the queue families that support the required operations
QueueFamilyIndices qfIndices = QueueFamilyIndices::findQueueFamilies(device, surface);
// Check if the device supports the required extensions
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport =
SwapChainSupportDetails::querySwapChainSupport(device, surface);
// Check if the swap chain is adequate (make sure it has
// at least one supported format and presentation mode)
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty();
}
// Check if the device supports the required features
vk::PhysicalDeviceFeatures supportedFeatures = device.getFeatures();
return qfIndices.isComplete() && extensionsSupported && swapChainAdequate &&
supportedFeatures.samplerAnisotropy;
}
} // namespace core

View file

@ -1,144 +0,0 @@
#pragma once
#include <optional>
#include <vector>
#include <vulkan/vulkan.hpp>
#include "types.hpp"
/**
* @brief Struct to store queue family indices.
*
* This struct contains the indices of the graphics and presentation queue families.
*/
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(); }
/**
* @brief Finds queue families that support graphics and presentation.
*
* @param device Physical device to check
* @param surface Surface to check presentation support against
* @return QueueFamilyIndices Struct containing queue family indices
*/
static fn findQueueFamilies(const vk::PhysicalDevice& device, const vk::SurfaceKHR& surface)
-> QueueFamilyIndices {
QueueFamilyIndices indices;
std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties();
for (u32 i = 0; i < queueFamilies.size(); i++) {
const auto& queueFamily = queueFamilies[i];
// Check for graphics support
if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)
indices.graphics_family = i;
// Check for presentation support
if (device.getSurfaceSupportKHR(i, surface))
indices.present_family = i;
if (indices.isComplete())
break;
}
return indices;
}
};
/**
* @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
/**
* @brief Queries swap chain support details from a physical device.
*
* @param device Physical device to query
* @param surface Surface to check against
* @return SwapChainSupportDetails Struct containing surface capabilities and supported formats
*/
static fn querySwapChainSupport(const vk::PhysicalDevice& device, const vk::SurfaceKHR& surface)
-> SwapChainSupportDetails {
SwapChainSupportDetails details;
details.capabilities = device.getSurfaceCapabilitiesKHR(surface);
details.formats = device.getSurfaceFormatsKHR(surface);
details.present_modes = device.getSurfacePresentModesKHR(surface);
return details;
}
/**
* @brief Chooses the best surface format for the swap chain.
*
* @param availableFormats List of available surface formats
* @return vk::SurfaceFormatKHR The chosen surface format
*/
static fn chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats
) -> vk::SurfaceFormatKHR {
// Prefer SRGB with nonlinear color space
for (const auto& availableFormat : availableFormats)
if (availableFormat.format == vk::Format::eB8G8R8A8Srgb &&
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
return availableFormat;
// If preferred format not found, use first available
return availableFormats[0];
}
/**
* @brief Chooses the best presentation mode for the swap chain.
*
* @param availablePresentModes List of available presentation modes
* @return vk::PresentModeKHR The chosen presentation mode
*/
static fn chooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes
) -> vk::PresentModeKHR {
// Prefer mailbox mode (triple buffering)
for (const auto& availablePresentMode : availablePresentModes)
if (availablePresentMode == vk::PresentModeKHR::eMailbox)
return availablePresentMode;
// Fallback to FIFO (vsync)
return vk::PresentModeKHR::eFifo;
}
/**
* @brief Chooses the swap extent (resolution) for the swap chain.
*
* @param capabilities Surface capabilities
* @param width Desired width
* @param height Desired height
* @return vk::Extent2D The chosen swap extent
*/
static fn chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width, u32 height)
-> vk::Extent2D {
if (capabilities.currentExtent.width != std::numeric_limits<u32>::max())
return capabilities.currentExtent;
vk::Extent2D actualExtent = { width, height };
actualExtent.width =
std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
actualExtent.height =
std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
return actualExtent;
}
};

View file

@ -1,152 +0,0 @@
/**
* @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
#include <cstddef>
#include <cstdint>
#include <string>
#include <glm/glm.hpp>
#define fn auto
// Integer Types
/**
* @brief 8-bit unsigned integer.
* @details Range: 0 to 255
* Used for byte-level operations and color channel values.
*/
using u8 = std::uint8_t;
/**
* @brief 16-bit unsigned integer.
* @details Range: 0 to 65,535
* Used for texture coordinates and small indices.
*/
using u16 = std::uint16_t;
/**
* @brief 32-bit unsigned integer.
* @details Range: 0 to 4,294,967,295
* Used for array sizes, indices, and flags.
*/
using u32 = std::uint32_t;
/**
* @brief 64-bit unsigned integer.
* @details Range: 0 to 18,446,744,073,709,551,615
* Used for large indices and timestamps.
*/
using u64 = std::uint64_t;
/**
* @brief 8-bit signed integer.
* @details Range: -128 to 127
* Used for small signed values and relative offsets.
*/
using i8 = std::int8_t;
/**
* @brief 16-bit signed integer.
* @details Range: -32,768 to 32,767
* Used for medium-range signed values.
*/
using i16 = std::int16_t;
/**
* @brief 32-bit signed integer.
* @details Range: -2,147,483,648 to 2,147,483,647
* Primary signed integer type for general use.
*/
using i32 = std::int32_t;
/**
* @brief 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.
*/
using i64 = std::int64_t;
// Floating-Point Types
/**
* @brief 32-bit floating-point number.
* @details IEEE 754 single-precision
* Used for graphics calculations, positions, and colors.
*/
using f32 = float;
/**
* @brief 64-bit floating-point number.
* @details IEEE 754 double-precision
* Used for high-precision calculations and physics.
*/
using f64 = double;
// Size Types
/**
* @brief Unsigned size type.
* @details Platform-dependent size (32/64-bit)
* Used for memory sizes and container sizes.
*/
using usize = std::size_t;
/**
* @brief Signed size type.
* @details Platform-dependent size (32/64-bit)
* Used for pointer arithmetic and container differences.
*/
using isize = std::ptrdiff_t;
/**
* @brief String type alias.
* @details Standard string type for text handling.
*/
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;

View file

@ -1,147 +0,0 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "../core/types.hpp"
#include "vkfw.hpp"
// Camera speed constant from main.cpp
constexpr f64 CAMERA_SPEED = 1.0;
/**
* @brief Represents a 3D camera in the scene.
*/
struct Camera {
glm::dvec3 position; ///< Camera's position in 3D space
glm::dvec3 front; ///< Direction the camera is facing
glm::dvec3 up; ///< Camera's up vector
glm::dvec3 right; ///< Camera's right vector
f64 yaw; ///< Yaw angle (rotation around vertical axis)
f64 pitch; ///< Pitch angle (rotation around horizontal axis)
/**
* @brief Constructs a Camera with default settings.
*/
Camera()
: position(2.0, 2.0, 2.0),
front(glm::normalize(glm::dvec3(-2.0, -2.0, -2.0))),
up(0.0, 0.0, 1.0),
right(glm::normalize(glm::cross(front, up))),
yaw(-135.0),
pitch(-35.26) {
updateCameraVectors();
}
/**
* @brief Gets the camera's current position.
* @return The camera's position as a 3D vector.
*/
[[nodiscard]] fn getPosition() const -> glm::dvec3 { return position; }
/**
* @brief Calculates and returns the view matrix for the camera.
* @return The view matrix as a 4x4 matrix.
*/
[[nodiscard]] fn getViewMatrix() const -> glm::mat4 { return glm::lookAt(position, position + front, up); }
/**
* @brief Moves the camera forward.
* @param deltaTime Time elapsed since last frame.
*/
fn moveForward(f64 deltaTime) -> void {
glm::dvec3 horizontalFront = glm::normalize(glm::dvec3(front.x, front.y, 0.0));
position += horizontalFront * CAMERA_SPEED * deltaTime;
}
/**
* @brief Moves the camera backward.
* @param deltaTime Time elapsed since last frame.
*/
fn moveBackward(f64 deltaTime) -> void {
glm::dvec3 horizontalFront = glm::normalize(glm::dvec3(front.x, front.y, 0.0));
position -= horizontalFront * CAMERA_SPEED * deltaTime;
}
/**
* @brief Moves the camera to the left.
* @param deltaTime Time elapsed since last frame.
*/
fn moveLeft(f64 deltaTime) -> void {
glm::dvec3 horizontalRight = glm::normalize(glm::dvec3(right.x, right.y, 0.0));
position -= horizontalRight * CAMERA_SPEED * deltaTime;
}
/**
* @brief Moves the camera to the right.
* @param deltaTime Time elapsed since last frame.
*/
fn moveRight(f64 deltaTime) -> void {
glm::dvec3 horizontalRight = glm::normalize(glm::dvec3(right.x, right.y, 0.0));
position += horizontalRight * CAMERA_SPEED * deltaTime;
}
/**
* @brief Moves the camera upward.
* @param deltaTime Time elapsed since last frame.
*/
fn moveUp(f64 deltaTime) -> void { position += glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
/**
* @brief Moves the camera downward.
* @param deltaTime Time elapsed since last frame.
*/
fn moveDown(f64 deltaTime) -> void { position -= glm::dvec3(0.0, 0.0, 1.0) * CAMERA_SPEED * deltaTime; }
/**
* @brief Rotates the camera based on mouse movement.
* @param xoffset Horizontal mouse movement.
* @param yoffset Vertical mouse movement.
*/
fn rotate(f64 xoffset, f64 yoffset) -> void {
const f64 sensitivity = 0.1;
yaw += xoffset * sensitivity;
pitch += yoffset * sensitivity;
pitch = glm::clamp(pitch, -89.0, 89.0);
updateCameraVectors();
}
/**
* @brief Processes input for camera movement and rotation.
* @param window The GLFW window.
* @param camera The camera to be controlled.
* @param deltaTime Time elapsed since last frame.
* @param cameraSpeed Speed multiplier for camera movement.
*/
static fn processInput(vkfw::Window& window, Camera& camera, const f32& deltaTime, const f32& cameraSpeed)
-> void {
if (window.getKey(vkfw::Key::eW) == vkfw::eTrue)
camera.moveForward(static_cast<f64>(deltaTime * cameraSpeed));
if (window.getKey(vkfw::Key::eA) == vkfw::eTrue)
camera.moveLeft(static_cast<f64>(deltaTime * cameraSpeed));
if (window.getKey(vkfw::Key::eS) == vkfw::eTrue)
camera.moveBackward(static_cast<f64>(deltaTime * cameraSpeed));
if (window.getKey(vkfw::Key::eD) == vkfw::eTrue)
camera.moveRight(static_cast<f64>(deltaTime * cameraSpeed));
if (window.getKey(vkfw::Key::eSpace) == vkfw::eTrue)
camera.moveUp(static_cast<f64>(deltaTime * cameraSpeed));
if (window.getKey(vkfw::Key::eLeftShift) == vkfw::eTrue)
camera.moveDown(static_cast<f64>(deltaTime * cameraSpeed));
}
private:
/**
* @brief Updates the camera's orientation vectors based on yaw and pitch.
*/
fn updateCameraVectors() -> void {
front = glm::normalize(glm::dvec3(
cos(glm::radians(yaw)) * cos(glm::radians(pitch)),
sin(glm::radians(yaw)) * cos(glm::radians(pitch)),
sin(glm::radians(pitch))
));
right = glm::normalize(glm::cross(front, glm::dvec3(0.0, 0.0, 1.0)));
up = glm::normalize(glm::cross(right, front));
}
};

View file

@ -1,278 +0,0 @@
#pragma once
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
#include "../core/types.hpp"
namespace graphics {
/**
* @brief Creates a Vulkan image view.
*
* This function creates and returns a unique Vulkan image view using the provided parameters.
*
* @param device The logical device to create the view on
* @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
*/
static fn createImageView(
const vk::Device& device,
const vk::Image& image,
const vk::Format& format,
const vk::ImageAspectFlags& aspectFlags,
const u32& mipLevels
) -> vk::UniqueImageView {
return device.createImageViewUnique({
.image = image,
.viewType = vk::ImageViewType::e2D,
.format = format,
.subresourceRange = {
.aspectMask = aspectFlags,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = 1,
},
});
}
/**
* @brief Finds a suitable memory type for allocation.
*
* @param physicalDevice The physical device to check
* @param typeFilter Filter for memory types
* @param properties Required memory properties
* @return u32 Index of the suitable memory type
*/
static fn findMemoryType(
const vk::PhysicalDevice& physicalDevice,
const u32& typeFilter,
const vk::MemoryPropertyFlags& properties
) -> u32 {
vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties();
for (u32 i = 0; i < memProperties.memoryTypeCount; i++)
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
return i;
throw std::runtime_error("Failed to find suitable memory type!");
}
/**
* @brief Creates a Vulkan image and allocates memory for it.
*
* @param device The logical device to create the image on
* @param width Width of the image
* @param height Height of the image
* @param mipLevels Number of mip levels
* @param numSamples Number of samples for multisampling
* @param format Format of the image
* @param tiling Tiling mode of the image
* @param usage Usage flags for the image
* @param properties Memory property flags
* @return std::pair<vk::UniqueImage, vk::UniqueDeviceMemory> A pair containing the image and its memory
*/
static fn createImage(
const vk::Device& device,
const vk::PhysicalDevice& physicalDevice,
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
vk::ImageCreateInfo imageInfo {
.imageType = vk::ImageType::e2D,
.format = format,
.extent = { .width = width, .height = height, .depth = 1 },
.mipLevels = mipLevels,
.arrayLayers = 1,
.samples = numSamples,
.tiling = tiling,
.usage = usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
};
// Create the image
vk::UniqueImage image = device.createImageUnique(imageInfo);
// Get the memory requirements for the image
vk::MemoryRequirements memRequirements = device.getImageMemoryRequirements(image.get());
// Memory allocation info
vk::MemoryAllocateInfo allocInfo {
.allocationSize = memRequirements.size,
.memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties),
};
// Allocate memory
vk::UniqueDeviceMemory imageMemory = device.allocateMemoryUnique(allocInfo);
// Bind the allocated memory to the image
device.bindImageMemory(image.get(), imageMemory.get(), 0);
return { std::move(image), std::move(imageMemory) };
}
/**
* @brief Transitions an image between layouts.
*
* @param device The logical device
* @param commandPool The command pool to allocate command buffers from
* @param queue The queue to submit commands to
* @param image The image to transition
* @param oldLayout The old layout
* @param newLayout The new layout
* @param mipLevels Number of mip levels
*/
static fn transitionImageLayout(
const vk::Device& device,
const vk::CommandPool& commandPool,
const vk::Queue& queue,
const vk::Image& image,
const vk::ImageLayout& oldLayout,
const vk::ImageLayout& newLayout,
const u32& mipLevels
) -> void {
// Create a command buffer
vk::CommandBufferAllocateInfo allocInfo {
.commandPool = commandPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
vk::UniqueCommandBuffer commandBuffer = std::move(device.allocateCommandBuffersUnique(allocInfo)[0]);
// Begin recording
commandBuffer->begin({ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
// Define the image memory barrier
vk::ImageMemoryBarrier barrier {
.oldLayout = oldLayout,
.newLayout = newLayout,
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.image = image,
.subresourceRange = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = mipLevels,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
// Define the source and destination stages
vk::PipelineStageFlags sourceStage;
vk::PipelineStageFlags destinationStage;
// Define the access masks
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 {
// Ensure that the layout transition is supported
throw std::invalid_argument("Unsupported layout transition!");
}
// Record the pipeline barrier
commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier);
// End recording
commandBuffer->end();
// Submit the command buffer
vk::SubmitInfo submitInfo {
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.get(),
};
queue.submit(submitInfo);
queue.waitIdle();
}
/**
* @brief Copies data from a buffer to an image.
*
* @param device The logical device
* @param commandPool The command pool to allocate command buffers from
* @param queue The queue to submit commands to
* @param buffer Source buffer
* @param image Destination image
* @param width Image width
* @param height Image height
*/
static fn copyBufferToImage(
const vk::Device& device,
const vk::CommandPool& commandPool,
const vk::Queue& queue,
const vk::Buffer& buffer,
const vk::Image& image,
const u32& width,
const u32& height
) -> void {
// Create a command buffer
vk::CommandBufferAllocateInfo allocInfo {
.commandPool = commandPool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
vk::UniqueCommandBuffer commandBuffer = std::move(device.allocateCommandBuffersUnique(allocInfo)[0]);
// Begin recording
commandBuffer->begin({ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
vk::BufferImageCopy region {
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1 },
.imageOffset = { .x = 0, .y = 0, .z = 0 },
.imageExtent = { .width = width, .height = height, .depth = 1 },
};
commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, 1, &region);
// End recording
commandBuffer->end();
// Submit the command buffer
vk::SubmitInfo submitInfo {
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer.get(),
};
queue.submit(submitInfo);
queue.waitIdle();
}
} // namespace graphics

View file

@ -1,230 +0,0 @@
/**
* @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
#include <filesystem>
#include <fmt/format.h>
#include <fstream>
#include <ios>
#include <shaderc/shaderc.hpp>
#include <string>
#include <vector>
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
#include "../core/types.hpp"
namespace graphics {
/**
* @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 {
public:
ShaderCompiler() = default;
/**
* @brief Compiles or retrieves a cached SPIR-V shader.
*
* @param shaderPath Path to the GLSL shader source file
* @param kind Type of shader (vertex, fragment, compute, etc.)
* @return std::vector<u32> Compiled SPIR-V binary code
* @throws std::runtime_error If shader compilation fails or file is not found
*
* This function performs the following steps:
* 1. Checks if a cached version exists and is up-to-date
* 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(const std::filesystem::path& shaderPath, const shaderc_shader_kind& kind)
-> std::vector<u32> {
using namespace std;
// Convert to absolute path if relative
filesystem::path absPath = filesystem::absolute(shaderPath);
if (!filesystem::exists(absPath))
throw runtime_error("Shader file not found: " + absPath.string());
const string shaderName = absPath.stem().string();
const filesystem::path cacheFile = getCacheFilePath(shaderName);
// 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;
}
}
}
// Need to compile the shader
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())
throw runtime_error("Shader compilation failed for: " + absPath.string());
// Cache the compiled SPIR-V binary
saveCompiledShader(spirvCode, cacheFile.string());
return spirvCode;
}
/**
* @brief Creates a shader module from SPIR-V code.
*
* @param device Logical device to create the shader module on
* @param code SPIR-V binary code
* @return vk::UniqueShaderModule Unique handle to the created shader module
*/
static fn createShaderModule(const vk::Device& device, const std::vector<u32>& code)
-> vk::UniqueShaderModule {
vk::ShaderModuleCreateInfo createInfo { .codeSize = code.size() * sizeof(u32), .pCode = code.data() };
return device.createShaderModuleUnique(createInfo);
}
private:
/**
* @brief Determines the platform-specific shader cache directory.
*
* @param shaderName Base name of the shader file
* @return std::filesystem::path Full path to the cache file
*
* Cache locations:
* - 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 {
using namespace std::filesystem;
#ifdef _WIN32
path cacheDir = path(getenv("LOCALAPPDATA")) / "VulkanApp" / "Shaders";
#elif defined(__APPLE__)
path cacheDir = path(getenv("HOME")) / "Library" / "Application Support" / "VulkanApp" / "Shaders";
#else // Assume Linux or other UNIX-like systems
path cacheDir = path(getenv("HOME")) / ".local" / "share" / "VulkanApp" / "Shaders";
#endif
if (!exists(cacheDir))
create_directories(cacheDir);
return cacheDir / (shaderName + ".spv");
}
/**
* @brief Loads a cached SPIR-V shader from disk.
*
* @param cachePath Path to the cached shader file
* @return std::vector<u32> SPIR-V binary code, empty if loading fails
*
* Reads the binary SPIR-V data from the cache file. Returns an empty
* vector if the file cannot be opened or read properly.
*/
static fn loadCachedShader(const std::filesystem::path& cachePath) -> std::vector<u32> {
std::ifstream file(cachePath, std::ios::binary);
if (!file.is_open())
return {};
// 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 Compiles GLSL source code to SPIR-V.
*
* @param source GLSL shader source code
* @param kind Type of shader being compiled
* @return std::vector<u32> Compiled SPIR-V binary code
*
* Uses the shaderc library to compile GLSL to SPIR-V. The compilation
* is performed with optimization level set to performance and generates
* debug information in debug builds.
*/
static fn compileShader(const char* source, shaderc_shader_kind kind) -> std::vector<u32> {
shaderc::Compiler compiler;
shaderc::CompileOptions options;
// Set compilation options
#ifdef NDEBUG
options.SetOptimizationLevel(shaderc_optimization_level_performance);
#else
options.SetOptimizationLevel(shaderc_optimization_level_zero);
options.SetGenerateDebugInfo();
#endif
// Compile the shader
shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(source, kind, "shader", options);
if (module.GetCompilationStatus() != shaderc_compilation_status_success)
return {};
return { module.cbegin(), module.cend() };
}
/**
* @brief Saves compiled SPIR-V code to the cache.
*
* @param spirv Compiled SPIR-V binary code
* @param cachePath Path where the cache file should be saved
* @return bool True if save was successful, false otherwise
*
* Writes the SPIR-V binary to disk for future use. Creates any
* necessary parent directories if they don't exist.
*/
static fn saveCompiledShader(const std::vector<u32>& spirv, const std::string& cachePath) -> bool {
std::ofstream file(cachePath, std::ios::binary);
if (!file.is_open())
return false;
file.write(
std::bit_cast<const char*>(spirv.data()), static_cast<std::streamsize>(spirv.size() * sizeof(u32))
);
return file.good();
}
}; // class ShaderCompiler
} // namespace graphics

View file

@ -1,14 +0,0 @@
#pragma once
#include <glm/glm.hpp>
/**
* @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
};

View file

@ -1,141 +0,0 @@
/**
* @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 <fmt/format.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include "../core/types.hpp"
namespace stb {
/**
* @brief RAII wrapper for image data loaded via stb_image.
*
* This class provides safe resource management for loaded images, ensuring proper
* cleanup of image data. It supports move semantics but prevents copying to maintain
* single ownership of image resources.
*/
class UniqueImage {
public:
/**
* @brief Constructs a UniqueImage by loading from file.
*
* @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()); }
// Prevent copying to maintain single ownership
UniqueImage(const UniqueImage&) = delete;
fn operator=(const UniqueImage&)->UniqueImage& = delete;
/**
* @brief Move constructor for transferring image ownership.
*
* @param other Source UniqueImage to move from.
*
* Transfers ownership of image data from another UniqueImage instance,
* leaving the source in a valid but empty state.
*/
UniqueImage(UniqueImage&& other) noexcept
: mData(other.mData),
mWidth(static_cast<i32>(other.mWidth)),
mHeight(static_cast<i32>(other.mHeight)),
mChannels(static_cast<i32>(other.mChannels)) {
other.mData = nullptr;
}
/**
* @brief Move assignment operator for transferring image ownership.
*
* @param other Source UniqueImage to move from.
* @return Reference to this object.
*
* Safely transfers ownership of image data, ensuring proper cleanup of
* existing resources before the transfer.
*/
fn operator=(UniqueImage&& other) noexcept -> UniqueImage& {
if (this != &other) {
if (mData)
stbi_image_free(mData);
mData = other.mData;
mWidth = static_cast<i32>(other.mWidth);
mHeight = static_cast<i32>(other.mHeight);
mChannels = static_cast<i32>(other.mChannels);
other.mData = nullptr;
}
return *this;
}
/**
* @brief Destructor that ensures proper cleanup of image resources.
*
* Automatically frees the image data using stb_image_free when the
* object goes out of scope.
*/
~UniqueImage() {
if (mData)
stbi_image_free(mData);
}
/**
* @brief Get raw pointer to image data.
* @return Pointer to the raw 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; }
/**
* @brief Get image width in pixels.
* @return Width of the image.
*/
[[nodiscard]] fn getWidth() const -> i32 { return mWidth; }
/**
* @brief Get image height in pixels.
* @return Height of the image.
*/
[[nodiscard]] fn getHeight() const -> i32 { return mHeight; }
/**
* @brief Get number of color channels.
* @return Number of channels (e.g., 3 for RGB, 4 for RGBA).
*/
[[nodiscard]] fn getChannels() const -> i32 { return mChannels; }
private:
/**
* @brief Internal helper function to load image data.
*
* @param path Path to the image file.
* @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* path) -> void {
mData = stbi_load(path, &mWidth, &mHeight, &mChannels, STBI_rgb_alpha);
if (!mData)
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
};
}

View file

@ -1,98 +0,0 @@
/**
* @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_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtx/hash.hpp>
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
#include "../core/types.hpp"
/**
* @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 {
vec3 pos; ///< Position of the vertex in 3D space (x, y, z)
vec3 color; ///< RGB color values, each component in range [0, 1]
vec2 tex_coord; ///< Texture coordinates (u, v) for texture mapping
/**
* @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 {
return { .binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex };
}
/**
* @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> {
return {
vk::VertexInputAttributeDescription { 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) },
vk::VertexInputAttributeDescription { 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) },
vk::VertexInputAttributeDescription { 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, tex_coord) }
};
}
/**
* @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;
}
};
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 <>
struct hash<Vertex> {
/**
* @brief Computes hash value for a vertex.
*
* @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);
}
};
}

File diff suppressed because it is too large Load diff

1709
src/util/magic_enum.hpp Normal file

File diff suppressed because it is too large Load diff

128
src/util/types.h Normal file
View file

@ -0,0 +1,128 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#define fn auto
/**
* @typedef u8
* @brief Represents an 8-bit unsigned integer.
*
* 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;
/**
* @typedef u16
* @brief Represents a 16-bit unsigned integer.
*
* 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;
/**
* @typedef u32
* @brief Represents a 32-bit unsigned integer.
*
* 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;
/**
* @typedef u64
* @brief Represents a 64-bit unsigned integer.
*
* 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;
// Type Aliases for Signed Integers
/**
* @typedef i8
* @brief Represents an 8-bit signed integer.
*
* 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;
/**
* @typedef i16
* @brief Represents a 16-bit signed integer.
*
* 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;
/**
* @typedef i32
* @brief Represents a 32-bit signed integer.
*
* 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;
/**
* @typedef i64
* @brief Represents a 64-bit signed integer.
*
* 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;
// Type Aliases for Floating-Point Numbers
/**
* @typedef f32
* @brief Represents a 32-bit floating-point number.
*
* 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;
/**
* @typedef f64
* @brief Represents a 64-bit floating-point number.
*
* 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;
// Type Aliases for Size Types
/**
* @typedef usize
* @brief Represents an unsigned size type.
*
* 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;
/**
* @typedef isize
* @brief Represents a signed size type.
*
* 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;
/**
* @typedef string
* @brief Represents a string.
*/
using string = std::string;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 940 KiB