diff --git a/.clang-format b/.clang-format index 151c0f5..b4615d0 100644 --- a/.clang-format +++ b/.clang-format @@ -1,23 +1,39 @@ -# .clang-format +--- +Language: Cpp BasedOnStyle: LLVM IndentWidth: 4 TabWidth: 4 UseTab: Never - -# Don’t indent contents of namespaces -NamespaceIndentation: None - -# Align consecutive assignments -AlignConsecutiveAssignments: true - -# Optionally align declarations too (if you like the visual symmetry) -AlignConsecutiveDeclarations: true - -# Keep parameters and arguments aligned (can be visually helpful) +ColumnLimit: 120 # No forced wrapping; useful for long VulkanHpp calls +AccessModifierOffset: -4 AlignAfterOpenBracket: Align - -# Optional: control brace placement if you like a specific style -BreakBeforeBraces: Attach - -# Optional: max line length -ColumnLimit: 100 +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false +Cpp11BracedListStyle: true +DerivePointerAlignment: false +NamespaceIndentation: None +PointerAlignment: Left +SortIncludes: true +IncludeBlocks: Preserve \ No newline at end of file diff --git a/.gitignore b/.gitignore index 694a5e2..2f2af59 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ build/ cmake-build-debug/ vcpkg_installed/ .idea -resources/ \ No newline at end of file +resources/ +*.fbx +workspace/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a298cf..5869e80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,9 @@ find_package(spdlog CONFIG REQUIRED) find_package(VulkanMemoryAllocator CONFIG REQUIRED) find_package(glm CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED) +find_package(assimp CONFIG REQUIRED) -add_executable(Reactor src/core/main.cpp +add_library(ReactorLib STATIC src/vulkan/VulkanContext.cpp src/vulkan/VulkanContext.hpp src/pch.hpp @@ -55,9 +56,39 @@ add_executable(Reactor src/core/main.cpp src/core/OrbitController.cpp src/core/OrbitController.hpp src/core/Application.cpp - src/core/Application.hpp) + src/core/Application.hpp + src/vulkan/Vertex.hpp + src/vulkan/ImageUtils.hpp + src/vulkan/ImageUtils.cpp + src/vulkan/Mesh.cpp + src/vulkan/Mesh.hpp + src/vulkan/MeshGenerators.hpp + src/vulkan/MeshGenerators.cpp + src/core/ModelIO.hpp + src/core/ModelIO.cpp + src/vulkan/ShadowMapping.hpp + src/vulkan/ShadowMapping.cpp +) -target_link_libraries(Reactor PRIVATE Vulkan::Vulkan Vulkan::Headers glfw fmt::fmt spdlog::spdlog GPUOpen::VulkanMemoryAllocator glm::glm) -target_link_libraries(Reactor PRIVATE imgui::imgui) +target_compile_definitions(ReactorLib PUBLIC GLM_ENABLE_EXPERIMENTAL) +target_compile_definitions(ReactorLib PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE) -target_precompile_headers(Reactor PRIVATE src/pch.hpp) +target_link_libraries(ReactorLib PUBLIC + Vulkan::Vulkan + Vulkan::Headers + glfw + fmt::fmt + spdlog::spdlog + GPUOpen::VulkanMemoryAllocator + glm::glm + imgui::imgui + assimp::assimp +) + +target_precompile_headers(ReactorLib PRIVATE src/pch.hpp) + +add_executable(Editor src/core/main.cpp) +target_link_libraries(Editor PRIVATE ReactorLib) + +add_executable(BuildModel src/tools/ModelBuilder.cpp) +target_link_libraries(BuildModel PRIVATE ReactorLib) diff --git a/shaders/composite.frag b/shaders/composite.frag index 8c03234..aee3a8a 100644 --- a/shaders/composite.frag +++ b/shaders/composite.frag @@ -5,14 +5,27 @@ layout(location = 0) out vec4 outColor; layout(binding = 0) uniform sampler2D uInputImage; +// Binding 2: Multisampled depth texture from geometry pass +layout(binding = 2) uniform sampler2DMS uDepthImage; + layout(set = 0, binding = 1) uniform CompositeParams { - float uExposure; // Default: 1.0 - float uContrast; // Default: 1.0 - float uSaturation; // Default: 1.0 - float uVignetteIntensity; // Default: 0.5 - float uVignetteFalloff; // Default 0.5 + float uExposure;// Default: 1.0 + float uContrast;// Default: 1.0 + float uSaturation;// Default: 1.0 + float uVignetteIntensity;// Default: 0.5 + float uVignetteFalloff;// Default 0.5 + float uFogDensity; }; +// add camera projection uniforms to the UBO or pass them in another way +const float Z_NEAR = 0.1; +const float Z_FAR = 100.0; + +float linearizeDepth(float depth) { + float z_n = 2.0 * depth - 1.0; + return (2.0 * Z_NEAR * Z_FAR) / (Z_FAR + Z_NEAR - z_n * (Z_FAR - Z_NEAR)); +} + // ACES Filmic Tone Mapping Curve vec3 tonemapACES(vec3 x) { const float a = 2.51; @@ -24,6 +37,21 @@ vec3 tonemapACES(vec3 x) { } void main() { + + ivec2 texelCoord = ivec2(gl_FragCoord.xy); + + float depth = texelFetch(uDepthImage, texelCoord, 0).r; + + // --- Fog Calculation --- + vec3 fogColor = vec3(0.5, 0.6, 0.7);// A cool, hazy blue + float fogStart = 0.5; + float fogEnd = 120.0; + float viewDistance = linearizeDepth(depth); + // Calculate fog amount (0.0 = no fog, 1.0 = full fog) + float fogFactor = 1.0 - exp(-viewDistance * uFogDensity); + // float fogFactor = uFogDensity; + // ---------------------- + // Sample the HDR input image vec3 hdrColor = texture(uInputImage, fragUV).rgb; @@ -43,5 +71,8 @@ void main() { vec3 grayscale = vec3(dot(finalColor, vec3(0.299, 0.587, 0.114))); finalColor = mix(grayscale, finalColor, uSaturation); + // Mix the final scene color with the fog color + finalColor = mix(finalColor, fogColor, fogFactor); + outColor = vec4(finalColor, 1.0); } \ No newline at end of file diff --git a/shaders/triangle.frag b/shaders/triangle.frag index e99cf82..20b5af0 100644 --- a/shaders/triangle.frag +++ b/shaders/triangle.frag @@ -1,8 +1,55 @@ #version 450 -layout(location = 0) in vec3 vColor; +layout(location = 0) in vec3 inWorldPos; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec4 inLightSpacePos; + +layout(binding = 1) uniform LightUBO { + vec4 lightDirection; + vec4 lightColor; + float lightIntensity; +} ubo; + +layout(binding = 2) uniform sampler2DShadow shadowMap; + +// Final output color layout(location = 0) out vec4 outColor; +float calculateShadow() +{ + // Perform perspective divide + vec3 projCoords = inLightSpacePos.xyz / inLightSpacePos.w; + + // Convert from [-1, 1] to [0, 1] texture coordinates + projCoords.xy = projCoords.xy * 0.5 + 0.5; + + // Get the closest depth from the shadow map (from light's perspective) + // The 'z' coordinate of projCoords is the current fragment's depth from the light + // texture() for sampler2DShadow performs the depth comparison automatically + float shadow = texture(shadowMap, projCoords); + + return shadow; +} + void main() { - outColor = vec4(vColor, 1.0); + vec3 objectColor = vec3(0.8, 0.8, 0.8); + + vec3 ambient = objectColor * 0.1; + + // 2. Calculate the diffuse (directional) light component. + vec3 normal = normalize(inNormal); + vec3 lightDir = normalize(-ubo.lightDirection.xyz); + float diffuseFactor = max(dot(normal, lightDir), 0.0); + vec3 diffuse = objectColor * ubo.lightColor.rgb * ubo.lightIntensity * diffuseFactor; + + // 3. Get the shadow factor (1.0 for lit, 0.0 for shadowed). + float shadow = calculateShadow(); + + // 4. Combine the components for the final color. + // The final color is the ambient light, plus the direct (diffuse) light, + // which IS blocked by shadows. + vec3 finalColor = ambient + (diffuse * shadow); + + outColor = vec4(finalColor, 1.0); + } \ No newline at end of file diff --git a/shaders/triangle.vert b/shaders/triangle.vert index 120cd7a..c020edc 100644 --- a/shaders/triangle.vert +++ b/shaders/triangle.vert @@ -1,74 +1,29 @@ #version 450 -layout(set = 0, binding = 0) uniform UBO { +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; +layout(location = 3) in vec2 inTexCoord; + +layout(location = 0) out vec3 outWorldPos; +layout(location = 1) out vec3 outNormal; +layout(location = 2) out vec4 outLightSpacePos; + +layout(binding = 0) uniform SceneUBO { mat4 view; mat4 projection; -}; + mat4 lightSpaceMatrix; +} ubo; -layout(location = 0) out vec3 vColor; +layout(push_constant) uniform PushConstants { + mat4 model; +} push; void main() { - // Hardcoded cube made of 12 triangles (36 vertices) - vec3 positions[36] = vec3[]( - // Front face - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, 0.5), - - // Back face - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, -0.5, -0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - - // Left face - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, 0.5, 0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, 0.5), - vec3(-0.5, 0.5, -0.5), - - // Right face - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - - // Top face - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, 0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, 0.5, -0.5), - - // Bottom face - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, -0.5, 0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, -0.5, -0.5), - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, -0.5, 0.5) - ); - - vec3 colors[6] = vec3[]( - vec3(1.0, 0.0, 0.0), - vec3(0.0, 1.0, 0.0), - vec3(0.0, 0.0, 1.0), - vec3(1.0, 1.0, 0.0), - vec3(1.0, 0.0, 1.0), - vec3(0.0, 1.0, 1.0) - ); + vec4 worldPos = push.model * vec4(inPosition, 1.0); + gl_Position = ubo.projection * ubo.view * worldPos; - int face = gl_VertexIndex / 6; // Each face has 6 vertices - gl_Position = projection * view * vec4(positions[gl_VertexIndex], 1.0); - vColor = colors[face]; + outWorldPos = worldPos.xyz; + outNormal = normalize(mat3(push.model) * inNormal); + outLightSpacePos = ubo.lightSpaceMatrix * worldPos; } \ No newline at end of file diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 8014ec1..1850352 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -14,6 +14,7 @@ void Application::initialize() { m_window = std::make_unique(1280, 720, "Reactor", *m_eventManager); m_camera = std::make_unique(); m_orbitController = std::make_unique(*m_camera); + m_orbitController->setSimCityView(20.0f, 45.0f); // subscribe controller to input events. m_eventManager->subscribe(EventType::MouseMoved, m_orbitController.get()); @@ -33,9 +34,9 @@ void Application::initialize() { m_renderer = std::make_unique(config, *m_window, *m_camera); } -void Application::run() { +void Application::run() const { while (!m_window->shouldClose()) { - m_window->pollEvents(); + Window::pollEvents(); m_renderer->drawFrame(); } } diff --git a/src/core/Application.hpp b/src/core/Application.hpp index c1b672d..d9b9344 100644 --- a/src/core/Application.hpp +++ b/src/core/Application.hpp @@ -14,7 +14,16 @@ class Application { Application(); ~Application(); - void run(); + // Prevent copying + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + // Allow moving if needed + Application(Application&&) = default; + Application& operator=(Application&&) = default; + + // Runs the main application loop. Returns program exit code. + void run() const; private: void initialize(); diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp index 05310a0..594f38a 100644 --- a/src/core/Camera.cpp +++ b/src/core/Camera.cpp @@ -1,17 +1,144 @@ -// -// Created by rfdic on 7/7/2025. -// - #include "Camera.hpp" -#include -#include +#include +#include +#include +#include +#include namespace reactor { Camera::Camera(): m_view(glm::mat4(1.0f)), m_projection(glm::mat4(1.0f)) { - m_projection = glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 100.0f); + updateProjection(); + updateView(); +} + +void Camera::setProjectionType(ProjectionType type) { + if (m_type != type) { + m_type = type; + m_projDirty = true; + } +} + +void Camera::setPerspective(float fov, float aspect, float near, float far) { + if (fov <= 0.0f || fov >= 180.0f || aspect <= 0.0f || near <= 0.0f || far <= 0.0f) { + throw std::invalid_argument("Invalid camera parameters"); + } + m_fov = fov; + m_aspect = aspect; + m_near = near; + m_far = far; + setProjectionType(ProjectionType::Perspective); + m_projDirty = true; +} + +void Camera::setPosition(const glm::vec3& position) { + m_position = position; + m_viewDirty = true; +} + +void Camera::setTarget(const glm::vec3& target) { + glm::vec3 direction = glm::normalize(target - m_position); + m_orientation = glm::quatLookAt(direction, m_up); + m_viewDirty = true; +} + +void Camera::setUp(const glm::vec3& up) { + m_up = glm::normalize(up); + m_viewDirty = true; +} + +void Camera::lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up) { + m_position = eye; + setTarget(target); + m_up = normalize(up); + m_viewDirty = true; +} + +void Camera::move(const glm::vec3& delta) { + m_position += delta; + m_viewDirty = true; +} + +void Camera::moveRelative(const glm::vec3 &delta) { + m_position += getRight() * delta.x + getUp() * delta.y + getForward() * delta.z; + m_viewDirty = true; +} + + +void Camera::rotate(float yaw, float pitch, float roll) { + glm::quat qYaw = glm::angleAxis(glm::radians(yaw), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::quat qPitch = glm::angleAxis(glm::radians(pitch), getRight()); + glm::quat qRoll = glm::angleAxis(glm::radians(roll), getForward()); + m_orientation = glm::normalize(qYaw * m_orientation * qPitch * qRoll); + m_up = getUp(); + m_viewDirty = true; +} + +void Camera::dolly(float distance) { + glm::vec3 direction = getForward(); + m_position += direction * distance; + m_viewDirty = true; } +// --- Matrix getters --- + +const glm::mat4& Camera::getViewMatrix() const { + if (m_viewDirty) { + const_cast(this)->updateView(); + } + return m_view; +} + +const glm::mat4& Camera::getProjectionMatrix() const { + if (m_projDirty) { + const_cast(this)->updateProjection(); + } + return m_projection; +} + +float Camera::getFOV() const { + return m_fov; +} + +glm::vec3 Camera::getPosition() const { + return m_position; +} + +glm::vec3 Camera::getTarget() const { + return m_position + getForward(); +} + +glm::vec3 Camera::getForward() const { + return glm::vec3(m_orientation * glm::vec3(0.0f, 0.0f, -1.0f)); +} + +glm::vec3 Camera::getRight() const { + return glm::vec3(m_orientation * glm::vec3(1.0f, 0.0f, 0.0f)); +} + +glm::vec3 Camera::getUp() const { + return glm::normalize(m_orientation * glm::vec3(0.0f, 1.0f, 0.0f)); +} + +float Camera::getDistanceToTarget() const { + return 1.0; +} + +void Camera::updateView() { + glm::mat4 rotation = glm::toMat4(glm::conjugate(m_orientation)); + glm::mat4 translation = glm::translate(glm::mat4(1.0f), -m_position); + m_view = rotation * translation; + m_viewDirty = false; +} + +void Camera::updateProjection() { + if (m_type == ProjectionType::Perspective) { + m_projection = glm::perspective(glm::radians(m_fov), m_aspect, m_near, m_far); + } else { + m_projection = glm::ortho(m_left, m_right, m_bottom, m_top, m_near, m_far); + } + m_projDirty = false; +} } // reactor \ No newline at end of file diff --git a/src/core/Camera.hpp b/src/core/Camera.hpp index 18ffb53..d3f8104 100644 --- a/src/core/Camera.hpp +++ b/src/core/Camera.hpp @@ -1,24 +1,61 @@ #pragma once -#include "EventManager.hpp" #include +#include +#include namespace reactor { +enum class ProjectionType { + Perspective, + Orthographic, +}; + class Camera { public: - Camera(); - [[nodiscard]] glm::mat4 getView() const { return m_view; } - [[nodiscard]] glm::mat4 getProjection() const { return m_projection; } - - void setView(const glm::mat4& view) { m_view = view; } + void setProjectionType(ProjectionType type); + void setPerspective(float fov, float aspect, float near, float far); + + void setPosition(const glm::vec3& position); + void setTarget(const glm::vec3& target); + void setUp(const glm::vec3& up); + + void lookAt(const glm::vec3& eye, const glm::vec3& target, const glm::vec3& up); + void move(const glm::vec3& delta); + void moveRelative(const glm::vec3& delta); + void rotate(float yaw, float pitch, float roll); + void dolly(float distance); + + // getters + const glm::mat4& getViewMatrix() const; + const glm::mat4& getProjectionMatrix() const; + float getFOV() const; + glm::vec3 getPosition() const; + glm::vec3 getTarget() const; + glm::vec3 getForward() const; + glm::vec3 getRight() const; + glm::vec3 getUp() const; + float getDistanceToTarget() const; private: - glm::mat4 m_view; - glm::mat4 m_projection; + void updateView(); + void updateProjection(); + + ProjectionType m_type{ProjectionType::Perspective}; + glm::mat4 m_view{1.0f}; + glm::mat4 m_projection{1.0f}; + + float m_fov{45.0f}, m_aspect{16.0f / 9.0f}, m_near{0.1f}, m_far{100.0f}; + float m_left, m_right, m_bottom, m_top; + + glm::vec3 m_position{0.0f, 0.0f, 5.0f}; + glm::quat m_orientation{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec3 m_up{0.0f, 1.0f, 0.0f}; + mutable bool m_viewDirty{true}; + mutable bool m_projDirty{true}; }; } // reactor diff --git a/src/core/ModelIO.cpp b/src/core/ModelIO.cpp new file mode 100644 index 0000000..ea9ab52 --- /dev/null +++ b/src/core/ModelIO.cpp @@ -0,0 +1,163 @@ +// +// Created by rfdic on 7/22/2025. +// +#include "ModelIO.hpp" + +#include + +#include +#include +#include + +namespace reactor +{ +bool processAndExportScene(const aiScene*, const std::string&); + + +// Processes the assimp scene and writes it to our custom binary format. +bool processAndExportScene(const aiScene* scene, const std::string& outputPath) +{ + // Open the output file in binary mode. + std::ofstream outFile(outputPath, std::ios::binary); + if (!outFile.is_open()) { + spdlog::error("Failed to open output file for writing: {}", outputPath); + return false; + } + + // --- Write File Header --- + const char magic[8] = "R_MESH"; + uint32_t version = 1; + uint32_t meshCount = scene->mNumMeshes; + + outFile.write(magic, sizeof(magic)); + outFile.write(reinterpret_cast(&version), sizeof(version)); + outFile.write(reinterpret_cast(&meshCount), sizeof(meshCount)); + + spdlog::info("Exporting {} meshes to {}", meshCount, outputPath); + + // --- Write Each Mesh's Data --- + for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + aiMesh* pMesh = scene->mMeshes[i]; + + std::vector vertices; + std::vector indices; + + // Extract vertex data + vertices.reserve(pMesh->mNumVertices); + for (unsigned int v = 0; v < pMesh->mNumVertices; ++v) { + reactor::Vertex vertex{}; + vertex.pos = {pMesh->mVertices[v].x, pMesh->mVertices[v].y, pMesh->mVertices[v].z}; + + if (pMesh->HasNormals()) { + vertex.normal = {pMesh->mNormals[v].x, pMesh->mNormals[v].y, pMesh->mNormals[v].z}; + } + + // Set a default color, or extract from mesh if available + vertex.color = {1.0f, 1.0f, 1.0f}; + if (pMesh->HasVertexColors(0)) { + vertex.color = {pMesh->mColors[0][v].r, pMesh->mColors[0][v].g, pMesh->mColors[0][v].b}; + } + + if (pMesh->HasTextureCoords(0)) { + vertex.texCoord = {pMesh->mTextureCoords[0][v].x, pMesh->mTextureCoords[0][v].y}; + } + vertices.push_back(vertex); + } + + // Extract index data + indices.reserve(pMesh->mNumFaces * 3); + for (unsigned int f = 0; f < pMesh->mNumFaces; ++f) { + aiFace face = pMesh->mFaces[f]; + // We assume the mesh is triangulated (aiProcess_Triangulate) + for (unsigned int j = 0; j < face.mNumIndices; ++j) { + indices.push_back(face.mIndices[j]); + } + } + + // --- Write Mesh Chunk to File --- + uint64_t vertexCount = vertices.size(); + uint64_t indexCount = indices.size(); + + outFile.write(reinterpret_cast(&vertexCount), sizeof(vertexCount)); + outFile.write(reinterpret_cast(&indexCount), sizeof(indexCount)); + outFile.write(reinterpret_cast(vertices.data()), vertexCount * sizeof(reactor::Vertex)); + outFile.write(reinterpret_cast(indices.data()), indexCount * sizeof(uint32_t)); + + spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); + } + + outFile.close(); + spdlog::info("Successfully exported model to {}", outputPath); + return true; +} + +// Example of how you would load the data back in your engine. +std::vector loadModelFromBinary(const std::string& path) { + std::vector allMeshes; + + std::ifstream inFile(path, std::ios::binary); + if (!inFile.is_open()) { + spdlog::error("Failed to open model file for reading: {}", path); + return allMeshes; + } + + // --- Read and Validate Header --- + char magic[8]; + uint32_t version; + uint32_t meshCount; + + inFile.read(magic, sizeof(magic)); + inFile.read(reinterpret_cast(&version), sizeof(version)); + inFile.read(reinterpret_cast(&meshCount), sizeof(meshCount)); + + if (std::string(magic) != "R_MESH" || version != 1) { + spdlog::error("Invalid model file or version mismatch: {}", path); + return allMeshes; + } + + allMeshes.resize(meshCount); + spdlog::info("Loading {} meshes from {}", meshCount, path); + + // --- Read Each Mesh's Data --- + for (uint32_t i = 0; i < meshCount; ++i) { + uint64_t vertexCount; + uint64_t indexCount; + + inFile.read(reinterpret_cast(&vertexCount), sizeof(vertexCount)); + inFile.read(reinterpret_cast(&indexCount), sizeof(indexCount)); + + allMeshes[i].vertices.resize(vertexCount); + allMeshes[i].indices.resize(indexCount); + + inFile.read(reinterpret_cast(allMeshes[i].vertices.data()), vertexCount * sizeof(reactor::Vertex)); + inFile.read(reinterpret_cast(allMeshes[i].indices.data()), indexCount * sizeof(uint32_t)); + + spdlog::info(" - Mesh {}: {} vertices, {} indices", i, vertexCount, indexCount); + } + + inFile.close(); + return allMeshes; +} + + +bool importAndExport(const std::string& importPath, const std::string& exportPath) { + Assimp::Importer importer; + + // Use aiProcess_FlipUVs since Vulkan's coordinate system is different from OpenGL's + const aiScene* scene = importer.ReadFile(importPath, + aiProcess_CalcTangentSpace | + aiProcess_Triangulate | + aiProcess_JoinIdenticalVertices | + aiProcess_SortByPType | + aiProcess_FlipUVs); + + if (nullptr == scene) { + spdlog::error("Assimp import error: {}", importer.GetErrorString()); + return false; + } + + processAndExportScene(scene, exportPath); + + return true; +} +} diff --git a/src/core/ModelIO.hpp b/src/core/ModelIO.hpp new file mode 100644 index 0000000..8452cc1 --- /dev/null +++ b/src/core/ModelIO.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "../vulkan/Vertex.hpp" + +namespace reactor +{ + +// A structure to hold mesh data, which is what we'll load back in the engine. +struct MeshData +{ + std::vector vertices; + std::vector indices; +}; + +std::vector loadModelFromBinary(const std::string&); +bool importAndExport(const std::string&, const std::string&); + +} // namespace reactor \ No newline at end of file diff --git a/src/core/OrbitController.cpp b/src/core/OrbitController.cpp index e690a44..c20cc06 100644 --- a/src/core/OrbitController.cpp +++ b/src/core/OrbitController.cpp @@ -10,6 +10,13 @@ namespace reactor { OrbitController::OrbitController(Camera &camera) : m_camera(camera) {} +void OrbitController::setSimCityView(float initialDistance, float initialElevationDegrees) { + m_distance = initialDistance; + m_elevation = glm::radians(initialElevationDegrees); + updateCamera(); + } + + void OrbitController::onEvent(const Event &event) { switch (event.type) { case EventType::MouseMoved: @@ -18,30 +25,40 @@ void OrbitController::onEvent(const Event &event) { double dy = event.mouseMove.y - m_lastY; m_azimuth += static_cast(dx) * ROTATE_SPEED; m_elevation += static_cast(dy) * ROTATE_SPEED; - // clamp elevation - float max_elev = glm::radians(85.0f); - m_elevation = glm::clamp(m_elevation, -max_elev, max_elev); + m_elevation = glm::clamp(m_elevation, MIN_ELEVATION, MAX_ELEVATION); updateCamera(); m_lastX = event.mouseMove.x; m_lastY = event.mouseMove.y; } + if (m_panning) { + double dx = event.mouseMove.x - m_lastPanX; + double dy = event.mouseMove.y - m_lastPanY; + updatePan(static_cast(dx), static_cast(dy)); + m_lastPanX = event.mouseMove.x; + m_lastPanY = event.mouseMove.y; + } break; case EventType::MouseButtonPressed: if (event.mouseButton.button == 0) { m_rotating = true; + m_lastX = event.mouseButton.x; + m_lastY = event.mouseButton.y; + } else if (event.mouseButton.button == 1) { + m_panning = true; + m_lastPanX = event.mouseButton.x; + m_lastPanY = event.mouseButton.y; } break; case EventType::MouseButtonReleased: if (event.mouseButton.button == 0) { m_rotating = false; + } else if (event.mouseButton.button == 1) { + m_panning = false; } break; case EventType::KeyPressed: break; - case EventType::KeyReleased: - break; - - } + } } void OrbitController::updateCamera() { @@ -49,11 +66,33 @@ void OrbitController::updateCamera() { const float y = m_distance * sinf(m_elevation); const float z = m_distance * cosf(m_elevation) * cosf(m_azimuth); - const glm::vec3 position(x, y, z); - constexpr glm::vec3 target(0.0f); - constexpr glm::vec3 up(0.0f, 1.0f, 0.0f); + const glm::vec3 position = m_target + glm::vec3(x, y, z); + const glm::vec3 up(0.0f, 1.0f, 0.0f); + + m_camera.lookAt(position, m_target, up); + } + +void OrbitController::updatePan(float dx, float dy) { + const float fovScale = std::tan(glm::radians(m_camera.getFOV() / 2.0f)); + const float speed = (m_distance * fovScale * PAN_SPEED); + + // get camera facing direction + const glm::vec3 position = m_camera.getPosition(); + const glm::vec3 facingDir = glm::normalize(position - m_target); + + // horizontal right + const glm::vec3 up(0.0f, 1.0f, 0.0f); + const glm::vec3 right = glm::normalize(glm::cross(facingDir, up)); + + // horizontal forward + const glm::vec3 groundForward = glm::normalize(glm::vec3(facingDir.x, 0.0f, facingDir.z)); + + const glm::vec3 delta = (right * -dx * speed) + (groundForward * -dy * speed); + + m_camera.move(delta); + m_target += delta; + updateCamera(); - m_camera.setView(glm::lookAt(position, target, up)); } diff --git a/src/core/OrbitController.hpp b/src/core/OrbitController.hpp index f7e7372..cba1967 100644 --- a/src/core/OrbitController.hpp +++ b/src/core/OrbitController.hpp @@ -11,22 +11,32 @@ class OrbitController final : public IEventListener { void onEvent(const Event& event) override; + void setSimCityView(float initialDistance = 20.f, float initialElevationDegrees = 45.0f); + private: Camera& m_camera; + glm::vec3 m_target{0.0f}; float m_distance = 5.0f; float m_azimuth = 0.0f; float m_elevation = 0.0f; bool m_rotating = false; + bool m_panning = false; double m_lastX = 0.0; double m_lastY = 0.0; + double m_lastPanX = 0.0; + double m_lastPanY = 0.0; void updateCamera(); + void updatePan(float dx, float dy); static constexpr float ROTATE_SPEED = 0.01f; + static constexpr float PAN_SPEED = 0.0005f; static constexpr float ZOOM_SPEED = 0.5f; static constexpr float MIN_DISTANCE = 0.1f; static constexpr float MAX_DISTANCE = 50.0f; + static constexpr float MIN_ELEVATION = glm::radians(20.0f); + static constexpr float MAX_ELEVATION = glm::radians(85.0f); }; } // reactor diff --git a/src/core/Uniforms.hpp b/src/core/Uniforms.hpp index 314798e..d11d467 100644 --- a/src/core/Uniforms.hpp +++ b/src/core/Uniforms.hpp @@ -4,12 +4,16 @@ struct SceneUBO { glm::mat4 view; glm::mat4 projection; + glm::mat4 lightSpaceMatrix; }; -struct LightingUBO { - glm::vec4 lightPosition; - glm::vec4 lightColor; - float lightIntensity; +struct DirectionalLightUBO +{ + glm::vec4 lightDirection = glm::vec4(-0.5f, 1.0f, -0.5f, 0.0f); + glm::vec4 lightColor = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + float lightIntensity = 1.0f; + // add 12-bytes of padding + float pad[3]; }; struct CompositeUBO { @@ -18,4 +22,5 @@ struct CompositeUBO { float uSaturation = 1.0f; float uVignetteIntensity = 0.5f; float uVignetteFalloff = 0.5f; + float uFogDensity = 0.001f; }; \ No newline at end of file diff --git a/src/core/Window.cpp b/src/core/Window.cpp index a18121b..380e653 100644 --- a/src/core/Window.cpp +++ b/src/core/Window.cpp @@ -24,8 +24,8 @@ m_width(width), m_height(height), m_title(title), m_eventManager(eventManager) { glfwSetFramebufferSizeCallback(m_window, framebufferResizeCallback); glfwSetKeyCallback(m_window, keyCallback); - glfwSetCursorPosCallback(m_window, cursorPosCallback); - glfwSetMouseButtonCallback(m_window, mouseButtonCallback); + // glfwSetCursorPosCallback(m_window, cursorPosCallback); + // glfwSetMouseButtonCallback(m_window, mouseButtonCallback); } // The destructor cleans up GLFW resources. diff --git a/src/core/Window.hpp b/src/core/Window.hpp index 25cf268..3a1300b 100644 --- a/src/core/Window.hpp +++ b/src/core/Window.hpp @@ -43,6 +43,8 @@ namespace reactor { // Creates a Vulkan surface (the connection between Vulkan and the window system). vk::SurfaceKHR createVulkanSurface(vk::Instance instance); + EventManager& getEventManager() const { return m_eventManager; } + // -- Resize Handling -- bool wasResized() const { return m_framebufferResized; } void resetResizedFlag() { m_framebufferResized = false; } diff --git a/src/imgui/Imgui.cpp b/src/imgui/Imgui.cpp index dd0580d..5049f63 100644 --- a/src/imgui/Imgui.cpp +++ b/src/imgui/Imgui.cpp @@ -4,6 +4,7 @@ #include "Imgui.hpp" +#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES #include #include #include @@ -11,11 +12,11 @@ namespace reactor { -Imgui::Imgui(VulkanContext &vulkanContext, Window &window) : m_device(vulkanContext.device()) { +Imgui::Imgui(VulkanContext &vulkanContext, Window &window, EventManager &eventManager) : +m_device(vulkanContext.device()), m_eventManager(eventManager) { IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); - (void)io; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); @@ -125,17 +126,59 @@ void Imgui::ShowDockspace() { void Imgui::ShowSceneView() { ImGui::Begin("Scene View"); - const ImVec2 size = ImGui::GetContentRegionAvail(); if (m_sceneImguiId) { - const ImTextureID id = - reinterpret_cast(static_cast(m_sceneImguiId)); - - ImGui::Image( - id, - size, - ImVec2(0, 1), - ImVec2(1, 0)); + + const ImVec2 size = ImGui::GetContentRegionAvail(); + + if (size.x > 0.0f && size.y > 0.0f) + { + const auto id = + reinterpret_cast(static_cast(m_sceneImguiId)); + + ImGui::Image( + id, + size, + ImVec2(0, 1), + ImVec2(1, 0)); + + ImVec2 image_pos = ImGui::GetItemRectMin(); + ImGui::SetCursorScreenPos(image_pos); + ImGui::InvisibleButton("scene_viewport", size); + + if (ImGui::IsItemHovered()) { + ImGuiIO& io = ImGui::GetIO(); + // Post MouseMoved if position changed + if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) { + Event e{}; + e.type = EventType::MouseMoved; + e.mouseMove.x = static_cast(io.MousePos.x); + e.mouseMove.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + + // Post button presses/releases for left (0), right (1), middle (2) + for (int btn = 0; btn < 3; ++btn) { + if (ImGui::IsMouseClicked(btn)) { + Event e{}; + e.type = EventType::MouseButtonPressed; + e.mouseButton.button = btn; + e.mouseButton.x = static_cast(io.MousePos.x); + e.mouseButton.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + if (ImGui::IsMouseReleased(btn)) { + Event e{}; + e.type = EventType::MouseButtonReleased; + e.mouseButton.button = btn; + e.mouseButton.x = static_cast(io.MousePos.x); + e.mouseButton.y = static_cast(io.MousePos.y); + m_eventManager.post(e); + } + } + + } + } } else { ImGui::Text("No scene image"); } @@ -148,6 +191,8 @@ void Imgui::ShowInspector() { ImGui::SliderFloat("Exposure", &m_exposure, 0.0, 2.0); ImGui::SliderFloat("Contrast", &m_contrast, 0.0, 2.0); ImGui::SliderFloat("Saturation", &m_saturation, 0.0, 2.0); + + ImGui::SliderFloat("Fog Density", &m_fogDensity, 0.0, 0.5); ImGui::End(); } diff --git a/src/imgui/Imgui.hpp b/src/imgui/Imgui.hpp index c5cdae8..fa19de6 100644 --- a/src/imgui/Imgui.hpp +++ b/src/imgui/Imgui.hpp @@ -13,7 +13,7 @@ namespace reactor { class Imgui { public: - Imgui(VulkanContext& vulkanContext, Window& window); + Imgui(VulkanContext& vulkanContext, Window& window, EventManager& eventManager); ~Imgui(); void createFrame(); @@ -22,6 +22,7 @@ class Imgui { [[nodiscard]] float getExposure() const { return m_exposure; } [[nodiscard]] float getContrast() const { return m_contrast; } [[nodiscard]] float getSaturation() const { return m_saturation; } + [[nodiscard]] float getFogDensity() const { return m_fogDensity; } static vk::DescriptorSet createDescriptorSet(vk::ImageView imageView, vk::Sampler sampler); void setSceneDescriptorSet(const vk::DescriptorSet descriptorSet) { m_sceneImguiId = descriptorSet; }; @@ -29,6 +30,7 @@ class Imgui { private: vk::Device m_device; + EventManager& m_eventManager; vk::DescriptorPool m_descriptorPool; vk::DescriptorSet m_sceneImguiId; @@ -36,6 +38,7 @@ class Imgui { float m_exposure = 1.0f; float m_contrast = 1.0f; float m_saturation = 1.0f; + float m_fogDensity = 0.001f; void ShowDockspace(); void ShowSceneView(); diff --git a/src/pch.hpp b/src/pch.hpp index e24f857..c8dbb98 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -1,9 +1,4 @@ -// -// Created by Robert F. Dickerson on 6/8/25. -// - -#ifndef PCH_HPP -#define PCH_HPP +#pragma once // Common standard library includes #include @@ -15,12 +10,18 @@ #include #include #include +#include #include #include #include +// VMA allocator #include + #include +#include +#include + +#include -#endif //PCH_HPP diff --git a/src/tools/ModelBuilder.cpp b/src/tools/ModelBuilder.cpp new file mode 100644 index 0000000..ad2ae8a --- /dev/null +++ b/src/tools/ModelBuilder.cpp @@ -0,0 +1,19 @@ +#include "../core/ModelIO.hpp" + +#include + +int main() +{ + // Define input and output paths + const std::string inputModel = "../workspace/monkey.obj"; + const std::string outputModel = "./monkey.mesh"; + + // Run the import and export process + if(reactor::importAndExport(inputModel, outputModel)) { + // Optional: Test the loader to verify the export + spdlog::info("--- Verifying export by loading file back ---"); + reactor::loadModelFromBinary(outputModel); + } + + return 0; +} diff --git a/src/vulkan/Allocator.cpp b/src/vulkan/Allocator.cpp index 84187a8..cbe2667 100644 --- a/src/vulkan/Allocator.cpp +++ b/src/vulkan/Allocator.cpp @@ -6,22 +6,87 @@ #include -namespace reactor { +#include "Buffer.hpp" - Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance) { - VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.physicalDevice = physicalDevice; - allocatorInfo.device = device; - allocatorInfo.instance = instance; +namespace reactor +{ - vmaCreateAllocator(&allocatorInfo, &m_allocator); +Allocator::Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex) + : m_device(device), m_graphicsQueue(graphicsQueue), m_graphicQueueFamilyIndex(graphicsQueueFamilyIndex) +{ + VmaAllocatorCreateInfo allocatorInfo = {}; + allocatorInfo.physicalDevice = physicalDevice; + allocatorInfo.device = device; + allocatorInfo.instance = instance; - spdlog::info("Allocator created"); - } + vmaCreateAllocator(&allocatorInfo, &m_allocator); - Allocator::~Allocator() { - if (m_allocator) vmaDestroyAllocator(m_allocator); - } + spdlog::info("Allocator created"); +} +Allocator::~Allocator() +{ + if (m_allocator) + vmaDestroyAllocator(m_allocator); +} -} // reactor \ No newline at end of file +std::unique_ptr Allocator::createBufferWithData(const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, const std::string& name) +{ + // Create CPU-visible staging buffer + Buffer stagingBuffer( + *this, + size, + vk::BufferUsageFlagBits::eTransferSrc, + VMA_MEMORY_USAGE_CPU_ONLY, + name + " Staging"); + + // Map and copy data to staging buffer + void* mappedData; + vmaMapMemory(m_allocator, stagingBuffer.allocation(), &mappedData); + memcpy(mappedData, data, size); + vmaUnmapMemory(m_allocator, stagingBuffer.allocation()); + + // Create GPU-local destination buffer + // Add the transfer destination usage flag + usage |= vk::BufferUsageFlagBits::eTransferDst; + auto destBuffer = std::make_unique( + *this, + size, + usage, + VMA_MEMORY_USAGE_GPU_ONLY, + name); + + // Perform the copy + immediateSubmit([&](vk::CommandBuffer cmd) { + vk::BufferCopy copyRegion(0, 0, size); + cmd.copyBuffer(stagingBuffer.getHandle(), destBuffer->getHandle(), 1, ©Region); + }); + + return destBuffer; +} + +void Allocator::immediateSubmit( + std::function&& function) +{ + // Create a temporary command pool and buffer + vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, m_graphicQueueFamilyIndex); + vk::CommandPool cmdPool = m_device.createCommandPool(poolInfo); + + vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); + vk::CommandBuffer cmd = m_device.allocateCommandBuffers(allocInfo)[0]; + + // Record and submit commands + cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + function(cmd); // Execute the provided command recording lambda + cmd.end(); + + vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); + m_graphicsQueue.submit(submitInfo, nullptr); + m_graphicsQueue.waitIdle(); // Wait for the operation to complete + + // Clean up temporary resources + m_device.freeCommandBuffers(cmdPool, cmd); + m_device.destroyCommandPool(cmdPool); +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Allocator.hpp b/src/vulkan/Allocator.hpp index 3564913..248e432 100644 --- a/src/vulkan/Allocator.hpp +++ b/src/vulkan/Allocator.hpp @@ -1,26 +1,55 @@ #pragma once -#include #include +#include +#include -namespace reactor { +namespace reactor +{ +class Buffer; -class Allocator { +class Allocator +{ public: - Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance); + Allocator(vk::PhysicalDevice physicalDevice, vk::Device device, vk::Instance instance, vk::Queue graphicsQueue, uint32_t graphicsQueueFamilyIndex); ~Allocator(); - VmaAllocator get() const { return m_allocator; } + VmaAllocator getAllocator() const + { + return m_allocator; + } + vk::Device getDevice() const + { + return m_device; + } + vk::Queue getGraphicsQueue() const + { + return m_graphicsQueue; + } + uint32_t getGraphicsQueueFamilyIndex() const + { + return m_graphicQueueFamilyIndex; + } + + // New factory method + std::unique_ptr createBufferWithData( + const void* data, + vk::DeviceSize size, + vk::BufferUsageFlags usage, + const std::string& name = ""); // Non-copyable Allocator(const Allocator&) = delete; Allocator& operator=(const Allocator&) = delete; private: - VmaAllocator m_allocator = VK_NULL_HANDLE; -}; - -} // reactor + void immediateSubmit(std::function&& function); + VmaAllocator m_allocator = nullptr; + vk::Device m_device; + vk::Queue m_graphicsQueue; + uint32_t m_graphicQueueFamilyIndex; +}; +} // namespace reactor diff --git a/src/vulkan/Buffer.cpp b/src/vulkan/Buffer.cpp index 04dbeec..beb45af 100644 --- a/src/vulkan/Buffer.cpp +++ b/src/vulkan/Buffer.cpp @@ -20,7 +20,7 @@ namespace reactor { allocInfo.usage = memoryUsage; const VkResult result = vmaCreateBuffer( - m_allocator.get(), + m_allocator.getAllocator(), reinterpret_cast(&bufferInfo), &allocInfo, reinterpret_cast(&m_buffer), @@ -34,7 +34,7 @@ namespace reactor { Buffer::~Buffer() { if (m_buffer && m_allocation) { - vmaDestroyBuffer(m_allocator.get(), m_buffer, m_allocation); + vmaDestroyBuffer(m_allocator.getAllocator(), m_buffer, m_allocation); spdlog::info("Buffer {} destroyed", m_name.c_str()); @@ -43,5 +43,15 @@ namespace reactor { } } +void* Buffer::map() { + void* data; + vmaMapMemory(m_allocator.getAllocator(), m_allocation, &data); + return data; +} + +void Buffer::unmap() { + vmaUnmapMemory(m_allocator.getAllocator(), m_allocation); +} + } // reactor \ No newline at end of file diff --git a/src/vulkan/Buffer.hpp b/src/vulkan/Buffer.hpp index 1a65670..f9db8b4 100644 --- a/src/vulkan/Buffer.hpp +++ b/src/vulkan/Buffer.hpp @@ -18,11 +18,12 @@ class Buffer { Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; - [[nodiscard]] vk::Buffer buffer() const { return m_buffer; } + [[nodiscard]] vk::Buffer getHandle() const { return m_buffer; } [[nodiscard]] VmaAllocation allocation() const { return m_allocation; } [[nodiscard]] vk::DeviceSize size() const { return m_size; } - + void* map(); + void unmap(); private: Allocator& m_allocator; diff --git a/src/vulkan/DescriptorSet.cpp b/src/vulkan/DescriptorSet.cpp index d3830dc..f57e2a9 100644 --- a/src/vulkan/DescriptorSet.cpp +++ b/src/vulkan/DescriptorSet.cpp @@ -8,8 +8,8 @@ namespace reactor { - DescriptorSet::DescriptorSet(vk::Device device, size_t framesInFlight, const std::vector &bindings) - : m_device(device) + DescriptorSet::DescriptorSet(vk::Device device, vk::DescriptorPool pool, size_t framesInFlight, const std::vector &bindings) + : m_device(device), m_pool(pool) { // create a descriptor set layout vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindings); @@ -20,13 +20,6 @@ namespace reactor { poolSizes.push_back({binding.descriptorType, static_cast(framesInFlight)}); } - vk::DescriptorPoolCreateInfo poolInfo( - vk::DescriptorPoolCreateFlags(), - static_cast(framesInFlight), - static_cast(poolSizes.size()), poolSizes.data()); - - m_pool = device.createDescriptorPool(poolInfo); - // allocate one set per frame std::vector layouts(framesInFlight, m_layout); vk::DescriptorSetAllocateInfo allocInfo(m_pool, layouts); @@ -35,13 +28,12 @@ namespace reactor { } DescriptorSet::~DescriptorSet() { - if (m_pool) m_device.destroyDescriptorPool(m_pool); if (m_layout) m_device.destroyDescriptorSetLayout(m_layout); } void DescriptorSet::updateUniformBuffer(size_t frame, const Buffer &buffer) { vk::DescriptorBufferInfo bufferInfo; - bufferInfo.buffer = buffer.buffer(); + bufferInfo.buffer = buffer.getHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer.size(); diff --git a/src/vulkan/DescriptorSet.hpp b/src/vulkan/DescriptorSet.hpp index 0601f9f..3bd2ac8 100644 --- a/src/vulkan/DescriptorSet.hpp +++ b/src/vulkan/DescriptorSet.hpp @@ -12,6 +12,7 @@ namespace reactor { public: DescriptorSet( vk::Device device, + vk::DescriptorPool pool, size_t framesInFlight, const std::vector& bindings); diff --git a/src/vulkan/Image.cpp b/src/vulkan/Image.cpp index faef1e5..2cf5e30 100644 --- a/src/vulkan/Image.cpp +++ b/src/vulkan/Image.cpp @@ -12,7 +12,7 @@ namespace reactor { allocInfo.usage = memoryUsage; const VkResult result = vmaCreateImage( - m_allocator.get(), + m_allocator.getAllocator(), reinterpret_cast(&imageInfo), &allocInfo, reinterpret_cast(&m_image), @@ -59,7 +59,7 @@ namespace reactor { void Image::cleanup() { if (m_image && m_allocation) { - vmaDestroyImage(m_allocator.get(), m_image, m_allocation); + vmaDestroyImage(m_allocator.getAllocator(), m_image, m_allocation); spdlog::info("Image destroyed"); m_image = VK_NULL_HANDLE; m_allocation = VK_NULL_HANDLE; diff --git a/src/vulkan/ImageUtils.cpp b/src/vulkan/ImageUtils.cpp new file mode 100644 index 0000000..3014158 --- /dev/null +++ b/src/vulkan/ImageUtils.cpp @@ -0,0 +1,61 @@ +#include "ImageUtils.hpp" + +namespace reactor::utils +{ + +ImageBuilder::ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent) + : m_device(device), m_allocator(allocator), m_extent(defaultExtent) +{ + m_imageInfo.imageType = vk::ImageType::e2D; + m_imageInfo.extent = vk::Extent3D{m_extent.width, m_extent.height, 1}; + m_imageInfo.mipLevels = 1; + m_imageInfo.arrayLayers = 1; + m_imageInfo.tiling = vk::ImageTiling::eOptimal; + m_imageInfo.initialLayout = vk::ImageLayout::eUndefined; + m_imageInfo.sharingMode = vk::SharingMode::eExclusive; + m_imageInfo.samples = vk::SampleCountFlagBits::e1; + + m_viewInfo.viewType = vk::ImageViewType::e2D; + m_viewInfo.subresourceRange.baseMipLevel = 0; + m_viewInfo.subresourceRange.levelCount = 1; + m_viewInfo.subresourceRange.baseArrayLayer = 0; + m_viewInfo.subresourceRange.layerCount = 1; + m_viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; +} + +ImageBuilder& ImageBuilder::setFormat(vk::Format format) +{ + m_imageInfo.format = format; + m_viewInfo.format = format; + return *this; +} + +ImageBuilder& ImageBuilder::setUsage(vk::ImageUsageFlags usage) +{ + m_imageInfo.usage = usage; + return *this; +} + +ImageBuilder& ImageBuilder::setSamples(vk::SampleCountFlagBits samples) +{ + m_imageInfo.samples = samples; + return *this; +} + +ImageBuilder& ImageBuilder::setAspectMask(vk::ImageAspectFlags aspect) +{ + m_viewInfo.subresourceRange.aspectMask = aspect; + return *this; +} + +ImageBuilder::BuiltImage ImageBuilder::build() +{ + auto image = std::make_unique(m_allocator, m_imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + + m_viewInfo.image = image->get(); + vk::ImageView view = m_device.createImageView(m_viewInfo); + + return {.image = std::move(image), .view = view}; +} + +} // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/ImageUtils.hpp b/src/vulkan/ImageUtils.hpp new file mode 100644 index 0000000..9206306 --- /dev/null +++ b/src/vulkan/ImageUtils.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "Image.hpp" + +namespace reactor::utils +{ +class ImageBuilder +{ +public: + ImageBuilder(const vk::Device& device, Allocator& allocator, const vk::Extent2D& defaultExtent); + + ImageBuilder& setFormat(vk::Format); + ImageBuilder& setUsage(vk::ImageUsageFlags usage); + ImageBuilder& setSamples(vk::SampleCountFlagBits samples); + ImageBuilder& setAspectMask(vk::ImageAspectFlags aspect); + + struct BuiltImage + { + std::unique_ptr image; + vk::ImageView view; + }; + + BuiltImage build(); + +private: + const vk::Device& m_device; + Allocator& m_allocator; + vk::Extent2D m_extent; + + vk::ImageCreateInfo m_imageInfo{}; + vk::ImageViewCreateInfo m_viewInfo{}; +}; +} // namespace reactor::utils \ No newline at end of file diff --git a/src/vulkan/Mesh.cpp b/src/vulkan/Mesh.cpp new file mode 100644 index 0000000..ee1b3e3 --- /dev/null +++ b/src/vulkan/Mesh.cpp @@ -0,0 +1,102 @@ +#include "Mesh.hpp" +#include // For memcpy + +#include "Allocator.hpp" + +namespace reactor { + +// Forward declaration of a helper function to perform the copy. +// This keeps the constructor clean. +// Helper function for one-time command submission. +// This could be placed in a utility file or within the Allocator class. +void immediateSubmit( + Allocator& allocator, + std::function&& function) +{ + vk::Device device = allocator.getDevice(); + vk::Queue graphicsQueue = allocator.getGraphicsQueue(); + uint32_t queueFamilyIndex = allocator.getGraphicsQueueFamilyIndex(); + + // Create a temporary command pool and buffer + vk::CommandPoolCreateInfo poolInfo(vk::CommandPoolCreateFlagBits::eTransient, queueFamilyIndex); + vk::CommandPool cmdPool = device.createCommandPool(poolInfo); + + vk::CommandBufferAllocateInfo allocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); + vk::CommandBuffer cmd = device.allocateCommandBuffers(allocInfo)[0]; + + // Record and submit commands + vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit); + cmd.begin(beginInfo); + + function(cmd); // Execute the provided command recording function + + cmd.end(); + + vk::SubmitInfo submitInfo(0, nullptr, nullptr, 1, &cmd); + graphicsQueue.submit(submitInfo, nullptr); + graphicsQueue.waitIdle(); // Wait for the operation to complete + + // Clean up temporary resources + device.freeCommandBuffers(cmdPool, cmd); + device.destroyCommandPool(cmdPool); +} + +Mesh::Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices) { + vk::DeviceSize vertexSize = vertices.size() * sizeof(Vertex); + vk::DeviceSize indexSize = indices.size() * sizeof(uint32_t); + m_indexCount = static_cast(indices.size()); + + // Staging buffers (CPU-visible) + Buffer stagingVertex(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Vertex"); + void* data; + vmaMapMemory(allocator.getAllocator(), stagingVertex.allocation(), &data); + memcpy(data, vertices.data(), static_cast(vertexSize)); + vmaUnmapMemory(allocator.getAllocator(), stagingVertex.allocation()); + + Buffer stagingIndex(allocator, indexSize, vk::BufferUsageFlagBits::eTransferSrc, VMA_MEMORY_USAGE_CPU_TO_GPU, "Staging Index"); + vmaMapMemory(allocator.getAllocator(), stagingIndex.allocation(), &data); + memcpy(data, indices.data(), static_cast(indexSize)); + vmaUnmapMemory(allocator.getAllocator(), stagingIndex.allocation()); + + // GPU buffers (device-local) + m_vertexBuffer = std::make_unique(allocator, vertexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Vertex Buffer"); + m_indexBuffer = std::make_unique(allocator, indexSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, VMA_MEMORY_USAGE_GPU_ONLY, "Index Buffer"); + + + // One-time transfer from staging to device-local buffers + immediateSubmit(allocator, [&](vk::CommandBuffer cmd) { + vk::BufferCopy vertexCopyRegion{}; + vertexCopyRegion.srcOffset = 0; + vertexCopyRegion.dstOffset = 0; + vertexCopyRegion.size = vertexSize; + cmd.copyBuffer(stagingVertex.getHandle(), m_vertexBuffer->getHandle(), 1, &vertexCopyRegion); + + vk::BufferCopy indexCopyRegion{}; + indexCopyRegion.srcOffset = 0; + indexCopyRegion.dstOffset = 0; + indexCopyRegion.size = indexSize; + cmd.copyBuffer(stagingIndex.getHandle(), m_indexBuffer->getHandle(), 1, &indexCopyRegion); + }); + + +} + + +Mesh::Mesh(Mesh&& other) noexcept + : m_vertexBuffer(std::move(other.m_vertexBuffer)), + m_indexBuffer(std::move(other.m_indexBuffer)), + m_indexCount(other.m_indexCount) { + other.m_indexCount = 0; +} + +Mesh& Mesh::operator=(Mesh&& other) noexcept { + if (this != &other) { + m_vertexBuffer = std::move(other.m_vertexBuffer); + m_indexBuffer = std::move(other.m_indexBuffer); + m_indexCount = other.m_indexCount; + other.m_indexCount = 0; + } + return *this; +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Mesh.hpp b/src/vulkan/Mesh.hpp new file mode 100644 index 0000000..66041e3 --- /dev/null +++ b/src/vulkan/Mesh.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Buffer.hpp" +#include "Vertex.hpp" +#include + +#include + +namespace reactor +{ + +class Mesh +{ +public: + Mesh(Allocator& allocator, const std::vector& vertices, const std::vector& indices); + + // Movable but not copyable (due to Buffer) + Mesh(Mesh&& other) noexcept; + Mesh& operator=(Mesh&& other) noexcept; + Mesh(const Mesh&) = delete; + Mesh& operator=(const Mesh&) = delete; + + ~Mesh() = default; + + vk::Buffer getVertexBuffer() const + { + return m_vertexBuffer->getHandle(); + } + vk::Buffer getIndexBuffer() const + { + return m_indexBuffer->getHandle(); + } + uint32_t getIndexCount() const + { + return m_indexCount; + } + +private: + std::unique_ptr m_vertexBuffer; + std::unique_ptr m_indexBuffer; + uint32_t m_indexCount = 0; + + void createBuffer(Allocator& allocator, const void* data, vk::DeviceSize size, vk::BufferUsageFlags usage, std::unique_ptr& buffer, const std::string& name); +}; + +} // namespace reactor diff --git a/src/vulkan/MeshGenerators.cpp b/src/vulkan/MeshGenerators.cpp new file mode 100644 index 0000000..01cfa9b --- /dev/null +++ b/src/vulkan/MeshGenerators.cpp @@ -0,0 +1,114 @@ +#include "MeshGenerators.hpp" +#include "Vertex.hpp" + +namespace reactor +{ +std::vector generatePlaneVertices(int subdivisions, float size) +{ + std::vector vertices; + int rows = subdivisions + 1; + vertices.reserve(rows * rows); + for (int z = 0; z < rows; ++z) + { + for (int x = 0; x < rows; ++x) + { + float u = static_cast(x) / subdivisions; + float v = static_cast(z) / subdivisions; + Vertex vert{}; + vert.pos = glm::vec3((u - 0.5f) * size, 0.0f, (v - 0.5f) * size); + vert.normal = glm::vec3(0.0f, 1.0f, 0.0f); + vert.color = glm::vec3(0.2f, 0.8f, 0.2f); // Greenish ground + vert.texCoord = glm::vec2(u, v); + vertices.push_back(vert); + } + } + return vertices; +} + +std::vector generatePlaneIndices(int subdivisions) +{ + std::vector indices; + int rows = subdivisions + 1; + for (int z = 0; z < subdivisions; ++z) + { + for (int x = 0; x < subdivisions; ++x) + { + uint32_t bottomLeft = z * rows + x; + uint32_t bottomRight = bottomLeft + 1; + uint32_t topLeft = bottomLeft + rows; + uint32_t topRight = topLeft + 1; + indices.insert(indices.end(), {bottomLeft, topLeft, bottomRight, bottomRight, topLeft, topRight}); + } + } + return indices; +} + +std::vector generateUnitCubeVertices() +{ + // A cube has 6 faces, and each face has 4 vertices. + // To have sharp edges with distinct normals for each face, + // we need 24 vertices in total, not 8. + // The vertex order for each face is: + // Bottom-Left, Bottom-Right, Top-Right, Top-Left + return { + // Back face (-Z) + {{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Front face (+Z) + {{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Left face (-X) + {{-0.5f, -0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{-0.5f, -0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{-0.5f, 0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Right face (+X) + {{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Bottom face (-Y) + {{-0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, -0.5f, 0.5f}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + + // Top face (+Y) + {{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}}, + }; +} + +std::vector generateUnitCubeIndices() +{ + // For each face, we define two triangles. + // The indices are arranged to have a CLOCKWISE winding order + // when viewed from outside the cube. This is necessary for + // back-face culling to work correctly. + // For a quad with vertices 0,1,2,3 (BL, BR, TR, TL), + // the triangles are (0, 1, 2) and (0, 2, 3). + return { + // Back + 0, 1, 2, 0, 2, 3, + // Front + 4, 5, 6, 4, 6, 7, + // Left + 8, 9, 10, 8, 10, 11, + // Right + 12, 13, 14, 12, 14, 15, + // Bottom + 16, 17, 18, 16, 18, 19, + // Top + 20, 21, 22, 20, 22, 23}; +} +} // namespace reactor diff --git a/src/vulkan/MeshGenerators.hpp b/src/vulkan/MeshGenerators.hpp new file mode 100644 index 0000000..d807b48 --- /dev/null +++ b/src/vulkan/MeshGenerators.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Vertex.hpp" + +namespace reactor +{ +std::vector generatePlaneVertices(int subdivisions, float size); +std::vector generatePlaneIndices(int subdivisions); +std::vector generateUnitCubeVertices(); +std::vector generateUnitCubeIndices(); + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.cpp b/src/vulkan/Pipeline.cpp index b244c19..1743582 100644 --- a/src/vulkan/Pipeline.cpp +++ b/src/vulkan/Pipeline.cpp @@ -5,185 +5,247 @@ #include "ShaderModule.hpp" #include "VulkanUtils.hpp" -namespace reactor { - -std::vector Pipeline::readFile(const std::string &filename) const { - std::ifstream file(filename, std::ios::ate | std::ios::binary); - if (!file.is_open()) - throw std::runtime_error("Failed to open shader file: " + filename); - size_t fileSize = (size_t)file.tellg(); - std::vector buffer(fileSize); - file.seekg(0); - file.read(buffer.data(), fileSize); - return buffer; -} - -Pipeline::Pipeline(vk::Device device, vk::Format colorAttachmentFormat, - const std::string &vertShaderPath, const std::string &fragShaderPath, - const std::vector &setLayouts, uint32_t samples, - vk::Format depthAttachmentFormat, bool depthWriteEnable) - : m_device(device) { - // 1. Read shader code from files - auto vertShaderCode = readFile(vertShaderPath); - auto vertShaderModule = ShaderModule(m_device, vertShaderCode); - - vk::PipelineShaderStageCreateInfo vertStageInfo{}; - vertStageInfo.stage = vk::ShaderStageFlagBits::eVertex; - vertStageInfo.module = vertShaderModule.getHandle(); - vertStageInfo.pName = "main"; - - std::vector shaderStages = {vertStageInfo}; - - std::unique_ptr fragShaderModule; - if (!fragShaderPath.empty()) { - auto fragShaderCode = readFile(fragShaderPath); - fragShaderModule = std::make_unique(m_device, fragShaderCode); - - vk::PipelineShaderStageCreateInfo fragStageInfo{}; - fragStageInfo.stage = vk::ShaderStageFlagBits::eFragment; - fragStageInfo.module = fragShaderModule->getHandle(); - fragStageInfo.pName = "main"; - shaderStages.push_back(fragStageInfo); - } - - // 3. Vertex input (empty, for a basic triangle with no vertex buffer) - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; - - // 4. Input assembly - vk::PipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - inputAssembly.primitiveRestartEnable = VK_FALSE; - - // 5. Viewport and scissor (dynamic for flexibility) - vk::PipelineViewportStateCreateInfo viewportState{}; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; - - // 6. Rasterizer - vk::PipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.depthClampEnable = VK_FALSE; - rasterizer.rasterizerDiscardEnable = VK_FALSE; - rasterizer.polygonMode = vk::PolygonMode::eFill; - rasterizer.lineWidth = 1.0f; - rasterizer.cullMode = vk::CullModeFlagBits::eBack; - rasterizer.frontFace = vk::FrontFace::eClockwise; - rasterizer.depthBiasEnable = VK_FALSE; - - // 7. Multisampling (disabled) - vk::PipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sampleShadingEnable = VK_FALSE; - multisampling.rasterizationSamples = utils::mapSampleCountFlag(samples); - - vk::PipelineDepthStencilStateCreateInfo depthStencil{}; - if (depthAttachmentFormat != vk::Format::eUndefined) { - depthStencil.depthTestEnable = VK_TRUE; - depthStencil.depthWriteEnable = depthWriteEnable ? VK_TRUE : VK_FALSE; - depthStencil.depthCompareOp = vk::CompareOp::eLessOrEqual; - depthStencil.depthBoundsTestEnable = VK_FALSE; - depthStencil.stencilTestEnable = VK_FALSE; - } - - // 8. Color blending - vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; - colorBlendAttachment.colorWriteMask = - vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | - vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; - colorBlendAttachment.blendEnable = VK_FALSE; - - vk::PipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = (colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - colorBlending.pAttachments = (colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; - - // 9. Pipeline layout - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.setLayoutCount = static_cast(setLayouts.size()); - pipelineLayoutInfo.pSetLayouts = setLayouts.data(); - - m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); - - // 10. Dynamic state (viewport and scissor set in command buffer) - std::vector dynamicStates = {vk::DynamicState::eViewport, - vk::DynamicState::eScissor}; - vk::PipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); - dynamicState.pDynamicStates = dynamicStates.data(); - - // 11. Dynamic rendering - vk::PipelineRenderingCreateInfo renderingInfo{}; - renderingInfo.colorAttachmentCount = (colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; - renderingInfo.pColorAttachmentFormats = (colorAttachmentFormat != vk::Format::eUndefined) ? &colorAttachmentFormat : nullptr; - renderingInfo.viewMask = 0; - renderingInfo.depthAttachmentFormat = depthAttachmentFormat; - renderingInfo.stencilAttachmentFormat = vk::Format::eUndefined; - - // 11. Create graphics pipeline - vk::GraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.stageCount = static_cast(shaderStages.size()); - pipelineInfo.pStages = shaderStages.data(); - pipelineInfo.pVertexInputState = &vertexInputInfo; - pipelineInfo.pInputAssemblyState = &inputAssembly; - pipelineInfo.pViewportState = &viewportState; - pipelineInfo.pRasterizationState = &rasterizer; - pipelineInfo.pMultisampleState = &multisampling; - pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = m_pipelineLayout; - pipelineInfo.renderPass = VK_NULL_HANDLE; - pipelineInfo.subpass = 0; - pipelineInfo.pDynamicState = &dynamicState; - pipelineInfo.pNext = &renderingInfo; - - // assign depth stencil state to the pipeline info if specified - if (depthAttachmentFormat != vk::Format::eUndefined) { - pipelineInfo.pDepthStencilState = &depthStencil; - } - - auto pipelineResult = m_device.createGraphicsPipeline({}, pipelineInfo); - if (pipelineResult.result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create graphics pipeline!"); - } - - m_pipeline = pipelineResult.value; - -} - -Pipeline::~Pipeline() { - if (m_pipeline) { - m_device.destroyPipeline(m_pipeline); -} - if (m_pipelineLayout) { - m_device.destroyPipelineLayout(m_pipelineLayout); -} -} - -Pipeline::Pipeline(Pipeline&& other) noexcept - : m_device(other.m_device), - m_pipelineLayout(other.m_pipelineLayout), - m_pipeline(other.m_pipeline) +namespace reactor { - other.m_pipelineLayout = nullptr; - other.m_pipeline = nullptr; -} -Pipeline& Pipeline::operator=(Pipeline&& other) noexcept -{ - if (this != &other) { - // Destroy our own Vulkan objects first - if (m_pipeline) { + static std::vector readFile(const std::string& filename) + { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + if (!file.is_open()) + throw std::runtime_error("Failed to open shader file: " + filename); + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + return buffer; + } + + Pipeline::Builder::Builder(vk::Device device) + : m_device(device) + {} + + Pipeline::Builder& Pipeline::Builder::setVertexShader(const std::string& shaderPath) + { + m_vertShaderPath = shaderPath; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setFragmentShader(const std::string& shaderPath) + { + m_fragShaderPath = shaderPath; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setColorAttachment(vk::Format format) + { + m_colorAttachmentFormat = format; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setDepthAttachment(vk::Format format, bool depthWriteEnable) + { + m_depthAttachmentFormat = format; + m_depthWriteEnable = depthWriteEnable; + return *this; + } + + Pipeline::Builder& + Pipeline::Builder::setDescriptorSetLayouts(const std::vector& layouts) + { + m_setLayouts = layouts; + return *this; + } + Pipeline::Builder& Pipeline::Builder::setVertexInputFromVertex() + { + m_bindings.push_back(Vertex::getBindingDescription()); + auto attrs = Vertex::getAttributeDescriptions(); + m_attributes.insert(m_attributes.end(), attrs.begin(), attrs.end()); + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setMultisample(uint32_t samples) + { + m_samples = samples; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setCullMode(vk::CullModeFlags cullMode) + { + m_cullMode = cullMode; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::setFrontFace(vk::FrontFace frontFace) + { + m_frontFace = frontFace; + return *this; + } + + Pipeline::Builder& Pipeline::Builder::addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size) + { + m_pushRanges.push_back({stages, offset, size}); + return *this; + } + + Pipeline::Builder& Pipeline::Builder::enableDepthBias(bool enable) + { + m_depthBiasEnable = enable; + return *this; + } + + + std::unique_ptr Pipeline::Builder::build() const + { + // 1. Shader Stages + auto vertShaderCode = readFile(m_vertShaderPath); + auto vertShaderModule = ShaderModule(m_device, vertShaderCode); + vk::PipelineShaderStageCreateInfo vertStageInfo({}, vk::ShaderStageFlagBits::eVertex, vertShaderModule.getHandle(), "main"); + + std::vector shaderStages = {vertStageInfo}; + std::unique_ptr fragShaderModule; + if (!m_fragShaderPath.empty()) + { + auto fragShaderCode = readFile(m_fragShaderPath); + fragShaderModule = std::make_unique(m_device, fragShaderCode); + vk::PipelineShaderStageCreateInfo fragStageInfo({}, vk::ShaderStageFlagBits::eFragment, fragShaderModule->getHandle(), "main"); + shaderStages.push_back(fragStageInfo); + } + + // 2. Vertex Input + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.vertexBindingDescriptionCount = static_cast(m_bindings.size()); + vertexInputInfo.pVertexBindingDescriptions = m_bindings.data(); + vertexInputInfo.vertexAttributeDescriptionCount = static_cast(m_attributes.size()); + vertexInputInfo.pVertexAttributeDescriptions = m_attributes.data(); + + // 3. Input Assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly({}, vk::PrimitiveTopology::eTriangleList, VK_FALSE); + + // 4. Viewport and Scissor (Dynamic) + vk::PipelineViewportStateCreateInfo viewportState({}, 1, nullptr, 1, nullptr); + + // 5. Rasterizer + vk::PipelineRasterizationStateCreateInfo rasterizer( + {}, VK_FALSE, VK_FALSE, vk::PolygonMode::eFill, m_cullMode, m_frontFace, m_depthBiasEnable? VK_TRUE : VK_FALSE, 0.0f, 0.0f, 0.0f, 1.0f); + + // 6. Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling({}, utils::mapSampleCountFlag(m_samples), VK_FALSE); + + // 7. Depth/Stencil + vk::PipelineDepthStencilStateCreateInfo depthStencil{}; + if (m_depthAttachmentFormat != vk::Format::eUndefined) + { + depthStencil.depthTestEnable = VK_TRUE; + depthStencil.depthWriteEnable = m_depthWriteEnable ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = vk::CompareOp::eLessOrEqual; + } + + // 8. Color Blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = VK_FALSE; + + vk::PipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.attachmentCount = (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + colorBlending.pAttachments = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? &colorBlendAttachment : nullptr; + + // 9. Dynamic State + std::vector dynamicStates = {vk::DynamicState::eViewport, + vk::DynamicState::eScissor}; + // conditionally add eDepthBias to the list of dynamic states + if (m_depthBiasEnable) + { + dynamicStates.push_back(vk::DynamicState::eDepthBias); + } + + vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStates); + + // 10. Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo({}, m_setLayouts); + pipelineLayoutInfo.pushConstantRangeCount = static_cast(m_pushRanges.size()); + pipelineLayoutInfo.pPushConstantRanges = m_pushRanges.data(); + vk::PipelineLayout pipelineLayout = m_device.createPipelineLayout(pipelineLayoutInfo); + + // 11. Dynamic Rendering Info + vk::PipelineRenderingCreateInfo renderingInfo{}; + renderingInfo.viewMask = 0; + renderingInfo.colorAttachmentCount = + (m_colorAttachmentFormat != vk::Format::eUndefined) ? 1 : 0; + renderingInfo.pColorAttachmentFormats = &m_colorAttachmentFormat; + renderingInfo.depthAttachmentFormat = m_depthAttachmentFormat; + + // 12. Graphics Pipeline Create Info + vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.pNext = &renderingInfo; + pipelineInfo.stageCount = static_cast(shaderStages.size()); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = + (m_depthAttachmentFormat != vk::Format::eUndefined) ? &depthStencil : nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + + auto result = m_device.createGraphicsPipeline({}, pipelineInfo); + if (result.result != vk::Result::eSuccess) + { + throw std::runtime_error("Failed to create graphics pipeline!"); + } + + // Using a private constructor to pass ownership of the created handles + return std::unique_ptr(new Pipeline(m_device, pipelineLayout, result.value)); + } + + // --- Pipeline Implementation --- + + Pipeline::Pipeline(vk::Device device, vk::PipelineLayout layout, vk::Pipeline pipeline) + : m_device(device), m_pipelineLayout(layout), m_pipeline(pipeline) + {} + + Pipeline::~Pipeline() + { + if (m_pipeline) + { m_device.destroyPipeline(m_pipeline); } - if (m_pipelineLayout) { + if (m_pipelineLayout) + { m_device.destroyPipelineLayout(m_pipelineLayout); } - m_device = other.m_device; - m_pipelineLayout = other.m_pipelineLayout; - m_pipeline = other.m_pipeline; + } + Pipeline::Pipeline(Pipeline&& other) noexcept + : m_device(other.m_device), m_pipelineLayout(other.m_pipelineLayout), + m_pipeline(other.m_pipeline) + { other.m_pipelineLayout = nullptr; other.m_pipeline = nullptr; } - return *this; -} + Pipeline& Pipeline::operator=(Pipeline&& other) noexcept + { + if (this != &other) + { + if (m_pipeline) + m_device.destroyPipeline(m_pipeline); + if (m_pipelineLayout) + m_device.destroyPipelineLayout(m_pipelineLayout); + + m_device = other.m_device; + m_pipelineLayout = other.m_pipelineLayout; + m_pipeline = other.m_pipeline; + + other.m_pipelineLayout = nullptr; + other.m_pipeline = nullptr; + } + return *this; + } } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Pipeline.hpp b/src/vulkan/Pipeline.hpp index 900b270..bbbf3c9 100644 --- a/src/vulkan/Pipeline.hpp +++ b/src/vulkan/Pipeline.hpp @@ -1,21 +1,64 @@ #pragma once -#include #include #include +#include + +#include "Vertex.hpp" -namespace reactor { +namespace reactor +{ - class Pipeline { + class Pipeline + { public: - Pipeline(vk::Device device, vk::Format colorAttachmentFormat, - const std::string& vertShaderPath, const std::string& fragShaderPath, - const std::vector& setLayouts, uint32_t samples, - vk::Format depthAttachmentFormat = vk::Format::eUndefined, bool depthWriteEnable = true); + class Builder + { + public: + Builder(vk::Device device); + + Builder& setVertexShader(const std::string& shaderPath); + Builder& setFragmentShader(const std::string& shaderPath); + Builder& setColorAttachment(vk::Format format); + Builder& setDepthAttachment(vk::Format format, bool depthWriteEnable = true); + Builder& setDescriptorSetLayouts(const std::vector& layouts); + Builder& setVertexInputFromVertex(); + Builder& setMultisample(uint32_t samples); + Builder& setCullMode(vk::CullModeFlags cullMode); + Builder& setFrontFace(vk::FrontFace frontFace); + Builder& addPushContantRange(vk::ShaderStageFlags stages, uint32_t offset, uint32_t size); + Builder& enableDepthBias(bool enable=true); + + [[nodiscard]] std::unique_ptr build() const; + + private: + vk::Device m_device; + std::string m_vertShaderPath; + std::string m_fragShaderPath; + vk::Format m_colorAttachmentFormat = vk::Format::eUndefined; + vk::Format m_depthAttachmentFormat = vk::Format::eUndefined; + bool m_depthWriteEnable = true; + std::vector m_setLayouts; + uint32_t m_samples = 1; + vk::CullModeFlags m_cullMode = vk::CullModeFlagBits::eBack; + vk::FrontFace m_frontFace = vk::FrontFace::eCounterClockwise; + bool m_depthBiasEnable = false; + + std::vector m_bindings; + std::vector m_attributes; + std::vector m_pushRanges; + }; + ~Pipeline(); - [[nodiscard]] vk::Pipeline get() const { return m_pipeline; } - [[nodiscard]] vk::PipelineLayout getLayout() const { return m_pipelineLayout; } + [[nodiscard]] vk::Pipeline get() const + { + return m_pipeline; + } + [[nodiscard]] vk::PipelineLayout getLayout() const + { + return m_pipelineLayout; + } // Delete copy operations Pipeline(const Pipeline&) = delete; @@ -26,13 +69,12 @@ namespace reactor { Pipeline& operator=(Pipeline&& other) noexcept; private: + friend class Builder; + Pipeline(vk::Device device, vk::PipelineLayout pipelineLayout, vk::Pipeline pipeline); + vk::Device m_device; vk::PipelineLayout m_pipelineLayout; vk::Pipeline m_pipeline; - - [[nodiscard]] std::vector readFile(const std::string& filename) const; - - }; } // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Sampler.cpp b/src/vulkan/Sampler.cpp index 3048180..f3052d8 100644 --- a/src/vulkan/Sampler.cpp +++ b/src/vulkan/Sampler.cpp @@ -1,17 +1,20 @@ #include "Sampler.hpp" -namespace reactor { +namespace reactor +{ - Sampler::Sampler(vk::Device device, const vk::SamplerCreateInfo &createInfo) -: m_device(device) { - m_sampler = m_device.createSampler(createInfo); - } +Sampler::Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo) + : m_device(device) +{ + m_sampler = m_device.createSampler(createInfo); +} - Sampler::~Sampler() { - if (m_sampler) { - m_device.destroySampler(m_sampler, nullptr); - } - } +Sampler::~Sampler() +{ + if (m_sampler) + { + m_device.destroySampler(m_sampler, nullptr); + } +} - -} // reactor \ No newline at end of file +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/Sampler.hpp b/src/vulkan/Sampler.hpp index 8507bd3..bbd4915 100644 --- a/src/vulkan/Sampler.hpp +++ b/src/vulkan/Sampler.hpp @@ -2,20 +2,23 @@ #include -namespace reactor { +namespace reactor +{ -class Sampler { +class Sampler +{ public: Sampler(vk::Device device, const vk::SamplerCreateInfo& createInfo); ~Sampler(); - [[nodiscard]] vk::Sampler get() const { return m_sampler; } + [[nodiscard]] vk::Sampler get() const + { + return m_sampler; + } private: vk::Device m_device; vk::Sampler m_sampler; - }; -} // reactor - +} // namespace reactor diff --git a/src/vulkan/ShaderModule.cpp b/src/vulkan/ShaderModule.cpp index af71220..2e15dd9 100644 --- a/src/vulkan/ShaderModule.cpp +++ b/src/vulkan/ShaderModule.cpp @@ -4,18 +4,22 @@ #include "ShaderModule.hpp" -namespace reactor { +namespace reactor +{ - ShaderModule::ShaderModule(vk::Device device, const std::vector &code) - : m_device(device) { - vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); - m_handle = m_device.createShaderModule(createInfo); - } +ShaderModule::ShaderModule(vk::Device device, const std::vector& code) + : m_device(device) +{ + vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast(code.data())); + m_handle = m_device.createShaderModule(createInfo); +} - ShaderModule::~ShaderModule() { - if (m_handle) m_device.destroyShaderModule(m_handle); - } +ShaderModule::~ShaderModule() +{ + if (m_handle) + { + m_device.destroyShaderModule(m_handle); + } +} - - -} // reactor \ No newline at end of file +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShaderModule.hpp b/src/vulkan/ShaderModule.hpp index a8c9c98..579de6f 100644 --- a/src/vulkan/ShaderModule.hpp +++ b/src/vulkan/ShaderModule.hpp @@ -1,25 +1,33 @@ #pragma once #include -namespace reactor { +namespace reactor +{ -class ShaderModule { +class ShaderModule +{ public: ShaderModule(vk::Device device, const std::vector& code); ~ShaderModule(); - [[nodiscard]] vk::ShaderModule getHandle() const { return m_handle; }; + [[nodiscard]] vk::ShaderModule getHandle() const + { + return m_handle; + }; // disallow copy, allow move ShaderModule(const ShaderModule&) = delete; ShaderModule& operator=(const ShaderModule&) = delete; - ShaderModule(ShaderModule&& other) noexcept : m_handle(other.m_handle) { other.m_handle = nullptr; } + ShaderModule(ShaderModule&& other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = nullptr; + } ShaderModule& operator=(ShaderModule&&) = delete; private: - vk::ShaderModule m_handle = {}; - vk::Device m_device = {}; + vk::ShaderModule m_handle; + vk::Device m_device; }; -} // reactor - +} // namespace reactor diff --git a/src/vulkan/ShadowMapping.cpp b/src/vulkan/ShadowMapping.cpp new file mode 100644 index 0000000..ccf41b8 --- /dev/null +++ b/src/vulkan/ShadowMapping.cpp @@ -0,0 +1,228 @@ +#include "ShadowMapping.hpp" + +#include "VulkanRenderer.hpp" +#include "../core/Uniforms.hpp" + +namespace reactor +{ + +ShadowMapping::ShadowMapping(VulkanRenderer& renderer, uint32_t resolution) + : m_renderer(renderer), m_resolution(resolution), m_shadowMapView(VK_NULL_HANDLE), + m_shadowMapSampler(VK_NULL_HANDLE) +{ + createResources(); + createDescriptors(); + createPipeline(); +} + +ShadowMapping::~ShadowMapping() +{ + // if you own any Vulkan handles directly, clean them up here + + auto device = m_renderer.device(); + device.destroyImageView(m_shadowMapView); + device.destroySampler(m_shadowMapSampler); +} + +void ShadowMapping::createResources() +{ + auto device = m_renderer.device(); + auto& allocator = m_renderer.allocator(); + + vk::ImageCreateInfo imageInfo = {}; + imageInfo.imageType = vk::ImageType::e2D; + imageInfo.format = vk::Format::eD32Sfloat; + imageInfo.extent = vk::Extent3D{m_resolution, m_resolution, 1}; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = 1; + imageInfo.samples = vk::SampleCountFlagBits::e1; + imageInfo.tiling = vk::ImageTiling::eOptimal; + imageInfo.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; + imageInfo.sharingMode = vk::SharingMode::eExclusive; + imageInfo.initialLayout = vk::ImageLayout::eUndefined; + + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_GPU_ONLY; + + m_shadowMap = std::make_unique(allocator, imageInfo, memoryUsage); + + // 2. Create image view + vk::ImageViewCreateInfo viewInfo{}; + viewInfo.image = m_shadowMap->get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = vk::Format::eD32Sfloat; + viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + m_shadowMapView = device.createImageView(viewInfo); + + // 3. Create sampler + vk::SamplerCreateInfo samplerInfo{}; + samplerInfo.magFilter = vk::Filter::eLinear; + samplerInfo.minFilter = vk::Filter::eLinear; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eNearest; + samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToBorder; + samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + samplerInfo.compareEnable = VK_TRUE; + samplerInfo.compareOp = vk::CompareOp::eLessOrEqual; + + m_shadowMapSampler = device.createSampler(samplerInfo); + + const size_t frameCount = 2; + m_mvpBuffer.clear(); + m_mvpBuffer.reserve(frameCount); + for (size_t i = 0; i < frameCount; ++i) + { + m_mvpBuffer.push_back(std::make_unique(m_renderer.allocator(), + sizeof(SceneUBO), + vk::BufferUsageFlagBits::eUniformBuffer, + VMA_MEMORY_USAGE_CPU_TO_GPU, + "MVP Buffer")); + } +} + +void ShadowMapping::createPipeline() +{ + spdlog::info("Creating shadow mapping pipeline"); + + auto device = m_renderer.device(); + + std::vector setLayouts; + if (m_descriptors) + { + setLayouts.push_back(m_descriptors->getLayout()); + } + + Pipeline::Builder builder(device); + + builder + .setVertexShader("../resources/shaders/triangle.vert.spv") + // No fragment shader, we only want depth output + .setVertexInputFromVertex() + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .enableDepthBias() + .setDescriptorSetLayouts(setLayouts) + .setMultisample(1) + .setCullMode(vk::CullModeFlagBits::eFront) + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)); + + m_depthPassPipeline = builder.build(); +} + +void ShadowMapping::createDescriptors() +{ + spdlog::info("Creating shadow mapping descriptors"); + vk::Device device = m_renderer.device(); + size_t framesInFlight = 2; + + // Descriptor set bindings: UBO for light's MVP + std::vector bindings = { + {0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex} + // Add more if you want—for example for a sampler or shadow map + }; + + // Use your DescriptorSet abstraction to handle layout & Vulkan allocation + m_descriptors = std::make_unique(device, m_renderer.descriptorPool(), framesInFlight, bindings); + + // Now update each descriptor set with the corresponding per-frame UBO + for (size_t i = 0; i < framesInFlight; ++i) + { + vk::DescriptorBufferInfo uboInfo{}; + uboInfo.buffer = m_mvpBuffer[i]->getHandle(); + uboInfo.offset = 0; + uboInfo.range = sizeof(SceneUBO); + + vk::WriteDescriptorSet writes{}; + writes.dstSet = m_descriptors->get(i); + writes.dstBinding = 0; + writes.dstArrayElement = 0; + writes.descriptorType = vk::DescriptorType::eUniformBuffer; + writes.descriptorCount = 1; + writes.pBufferInfo = &uboInfo; + + m_descriptors->updateSet({writes}); + } +} + +void ShadowMapping::recordShadowPass(vk::CommandBuffer cmd, + size_t frameIndex, + const std::function& drawCallback) +{ + vk::ClearValue clearDepth; + clearDepth.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + + vk::RenderingAttachmentInfo depthAttachment{}; + depthAttachment.imageView = m_shadowMapView; + depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; + depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; + depthAttachment.clearValue = clearDepth; + + vk::RenderingInfo renderingInfo{}; + renderingInfo.renderArea = vk::Rect2D({0, 0}, {m_resolution, m_resolution}); + renderingInfo.layerCount = 1; + renderingInfo.pDepthAttachment = &depthAttachment; + + cmd.setDepthBias(depthBiasConstant, 0.0f, depthBiasSlope); + + cmd.beginRendering(&renderingInfo); + + // set viewport/scissor + const vk::Viewport viewport = {0.0f, 0.0f, static_cast(m_resolution), static_cast(m_resolution), 0.0f, 1.0f}; + const vk::Rect2D scissor = {vk::Offset2D{0, 0}, vk::Extent2D{m_resolution, m_resolution}}; + cmd.setViewport(0, viewport); + cmd.setScissor(0, scissor); + + cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->get()); + + auto descriptor = m_descriptors->get(frameIndex); + + // bind descriptor set (with light MVP) + cmd.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, m_depthPassPipeline->getLayout(), 0, 1, &descriptor, 0, nullptr); + + // draw the shadow casters + drawCallback(cmd); + + cmd.endRendering(); +} + +vk::ImageView ShadowMapping::shadowMapView() const +{ + return m_shadowMapView; +} + +vk::Sampler ShadowMapping::shadowMapSampler() const +{ + return m_shadowMapSampler; +} + +vk::DescriptorSet ShadowMapping::shadowMapDescriptorSet(size_t frameIndex) const +{ + return m_descriptors->get(frameIndex); +} + +vk::Image ShadowMapping::shadowMapImage() const +{ + return m_shadowMap->get(); +} + +void ShadowMapping::setLightMatrix(const glm::mat4& lightSpaceMatrix, size_t frameIndex) +{ + SceneUBO ubo{}; + ubo.view = glm::mat4(1.0f); + ubo.projection = lightSpaceMatrix; + ubo.lightSpaceMatrix = glm::mat4(1.0f); + + // map buffer, copy matrix + void* data = m_mvpBuffer[frameIndex]->map(); + memcpy(data, &ubo, sizeof(SceneUBO)); + m_mvpBuffer[frameIndex]->unmap(); +} + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/ShadowMapping.hpp b/src/vulkan/ShadowMapping.hpp new file mode 100644 index 0000000..fe03e4d --- /dev/null +++ b/src/vulkan/ShadowMapping.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "Buffer.hpp" +#include "DescriptorSet.hpp" +#include "Image.hpp" +#include "Pipeline.hpp" +#include +#include + +namespace reactor +{ + +class VulkanRenderer; + +class ShadowMapping +{ +public: + explicit ShadowMapping(VulkanRenderer& renderer, uint32_t resolution = 2048); + ~ShadowMapping(); + + void recordShadowPass(vk::CommandBuffer cmd, size_t frameIndex, const std::function& drawCallback); + + vk::ImageView shadowMapView() const; + vk::Sampler shadowMapSampler() const; + vk::DescriptorSet shadowMapDescriptorSet(size_t frameIndex) const; + vk::Image shadowMapImage() const; + + void setLightMatrix(const glm::mat4& lightMVP, size_t frameIndex); + + uint32_t resolution() const + { + return m_resolution; + } + +private: + void createResources(); + void createPipeline(); + void createDescriptors(); + + const float depthBiasConstant = 1.25f; + const float depthBiasSlope = 1.75f; + + VulkanRenderer& m_renderer; + uint32_t m_resolution; + + // depth image, view, and sampler for shadow map + std::unique_ptr m_shadowMap; + vk::ImageView m_shadowMapView; + vk::Sampler m_shadowMapSampler; + + std::vector> m_mvpBuffer; + + std::unique_ptr m_descriptors; + + // Pipeline for depth pass + std::unique_ptr m_depthPassPipeline; +}; +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/UniformManager.hpp b/src/vulkan/UniformManager.hpp index 471cfdd..06fbcaa 100644 --- a/src/vulkan/UniformManager.hpp +++ b/src/vulkan/UniformManager.hpp @@ -25,9 +25,9 @@ class UniformManager { auto& buffer = m_uniformBuffers.at(name)[frameIndex]; void* mappedData = nullptr; - vmaMapMemory(m_allocator.get(), buffer->allocation(), &mappedData); + vmaMapMemory(m_allocator.getAllocator(), buffer->allocation(), &mappedData); memcpy(mappedData, &data, sizeof(T)); - vmaUnmapMemory(m_allocator.get(), buffer->allocation()); + vmaUnmapMemory(m_allocator.getAllocator(), buffer->allocation()); } // Get the descriptor info need to update a descriptor set. @@ -37,7 +37,7 @@ class UniformManager { const auto& buffer = m_uniformBuffers.at(name)[frameIndex]; vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = buffer->buffer(); + bufferInfo.buffer = buffer->getHandle(); bufferInfo.offset = 0; bufferInfo.range = buffer->size(); return bufferInfo; diff --git a/src/vulkan/Vertex.hpp b/src/vulkan/Vertex.hpp new file mode 100644 index 0000000..e7309d3 --- /dev/null +++ b/src/vulkan/Vertex.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include + +namespace reactor +{ + +struct Vertex +{ + glm::vec3 pos; + glm::vec3 normal; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() + { + return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + } + + static std::array getAttributeDescriptions() + { + std::array attributeDescriptions{}; + + attributeDescriptions[0].binding = 0; + attributeDescriptions[0].location = 0; + attributeDescriptions[0].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[0].offset = offsetof(Vertex, pos); + + attributeDescriptions[1].binding = 0; + attributeDescriptions[1].location = 1; + attributeDescriptions[1].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[1].offset = offsetof(Vertex, normal); + + attributeDescriptions[2].binding = 0; + attributeDescriptions[2].location = 2; + attributeDescriptions[2].format = vk::Format::eR32G32B32Sfloat; + attributeDescriptions[2].offset = offsetof(Vertex, color); + + attributeDescriptions[3].binding = 0; + attributeDescriptions[3].location = 3; + attributeDescriptions[3].format = vk::Format::eR32G32Sfloat; + attributeDescriptions[3].offset = offsetof(Vertex, texCoord); + + return attributeDescriptions; + } +}; + +} // namespace reactor \ No newline at end of file diff --git a/src/vulkan/VulkanRenderer.cpp b/src/vulkan/VulkanRenderer.cpp index 8c2dd80..dcd1521 100644 --- a/src/vulkan/VulkanRenderer.cpp +++ b/src/vulkan/VulkanRenderer.cpp @@ -1,15 +1,19 @@ #include "VulkanRenderer.hpp" +#include "../core/ModelIO.hpp" #include "../core/Uniforms.hpp" #include "../core/Window.hpp" +#include "ImageUtils.hpp" #include "VulkanUtils.hpp" #include #include -namespace reactor { +namespace reactor +{ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera) -: m_config(config), m_window(window), m_camera(camera) { + : m_config(config), m_window(window), m_camera(camera) +{ createCoreVulkanObjects(); createSwapchainAndFrameManager(); @@ -17,7 +21,9 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam m_uniformManager = std::make_unique(*m_allocator, m_frameManager->getFramesInFlightCount()); m_uniformManager->registerUBO("scene"); m_uniformManager->registerUBO("composite"); + m_uniformManager->registerUBO("lighting"); + createDescriptorPool(); createPipelineAndDescriptors(); setupUI(); createMSAAImage(); @@ -27,66 +33,115 @@ VulkanRenderer::VulkanRenderer(const RendererConfig& config, Window& window, Cam createSampler(); createDescriptorSets(); createDepthPipelineAndDescriptorSets(); + initScene(); + m_shadowMapping = std::make_unique(*this); + m_imageStateTracker.recordState(m_shadowMapping->shadowMapImage(), vk::ImageLayout::eUndefined); } -void VulkanRenderer::createCoreVulkanObjects() { - m_context = std::make_unique(m_window.getNativeWindow()); - m_allocator = std::make_unique(m_context->physicalDevice(), m_context->device(), - m_context->instance()); +Allocator& VulkanRenderer::allocator() +{ + return *m_allocator; } -void VulkanRenderer::createSwapchainAndFrameManager() { - m_swapchain = std::make_unique(m_context->device(), m_context->physicalDevice(), - m_context->surface(), m_window); +vk::Device VulkanRenderer::device() const +{ + return m_context->device(); +} + +vk::DescriptorPool VulkanRenderer::descriptorPool() const +{ + return m_descriptorPool; +} + +void VulkanRenderer::createCoreVulkanObjects() +{ + m_context = std::make_unique(m_window.getNativeWindow()); + m_allocator = std::make_unique(m_context->physicalDevice(), + m_context->device(), + m_context->instance(), + m_context->graphicsQueue(), + m_context->queueFamilies().graphicsFamily.value()); +} + +void VulkanRenderer::createSwapchainAndFrameManager() +{ + m_swapchain = + std::make_unique(m_context->device(), m_context->physicalDevice(), m_context->surface(), m_window); uint32_t swapchainImageCount = m_swapchain->getImageViews().size(); - m_frameManager = std::make_unique(m_context->device(), *m_allocator, 0, 2, - swapchainImageCount); + m_frameManager = std::make_unique(m_context->device(), *m_allocator, 0, 2, swapchainImageCount); - for (const auto& image : m_swapchain->getImages()) { + for (const auto& image : m_swapchain->getImages()) + { m_imageStateTracker.recordState(image, vk::ImageLayout::eUndefined); } } -void VulkanRenderer::createPipelineAndDescriptors() { +void VulkanRenderer::createDescriptorPool() +{ + std::vector poolSizes = {{vk::DescriptorType::eUniformBuffer, 32}, + {vk::DescriptorType::eCombinedImageSampler, 32}}; + + vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlags(), 128, poolSizes.size(), poolSizes.data()); + + m_descriptorPool = m_context->device().createDescriptorPool(poolInfo); +} + + +void VulkanRenderer::createPipelineAndDescriptors() +{ const std::string vertShaderPath = m_config.vertShaderPath; const std::string fragShaderPath = m_config.fragShaderPath; const std::vector bindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, - vk::ShaderStageFlagBits::eVertex), + vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex), + vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding(2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; - - m_descriptorSet = std::make_unique(m_context->device(), 2, bindings); - + m_descriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, bindings); const std::vector setLayouts = {m_descriptorSet->getLayout()}; - m_pipeline = std::make_unique(m_context->device(), vk::Format::eR16G16B16A16Sfloat, - vertShaderPath, fragShaderPath, setLayouts, 4, vk::Format::eD32Sfloat, true); + m_pipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + .setFragmentShader(m_config.fragShaderPath) + .setVertexInputFromVertex() + .setColorAttachment(vk::Format::eR16G16B16A16Sfloat) + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .build(); const std::vector compositeBindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding( + 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment), + vk::DescriptorSetLayoutBinding( + 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment), }; - m_compositeDescriptorSet = std::make_unique(m_context->device(), 2, compositeBindings); + m_compositeDescriptorSet = std::make_unique(m_context->device(), m_descriptorPool, 2, compositeBindings); std::vector compositeSetLayouts = {m_compositeDescriptorSet->getLayout()}; - vk::Format swapchainFormat = m_swapchain->getFormat(); - - m_compositePipeline = std::make_unique( - m_context->device(), - swapchainFormat, - m_config.compositeVertShaderPath, - m_config.compositeFragShaderPath, - compositeSetLayouts, 1); + m_compositePipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.compositeVertShaderPath) + .setFragmentShader(m_config.compositeFragShaderPath) + .setColorAttachment(m_swapchain->getFormat()) + .setDescriptorSetLayouts(compositeSetLayouts) + .setMultisample(1) // No MSAA for composite pass + .setFrontFace(vk::FrontFace::eClockwise) // Assuming standard winding order for cubes + .build(); } -void VulkanRenderer::handleSwapchainResizing() { - if (m_window.wasResized()) { +void VulkanRenderer::handleSwapchainResizing() +{ + if (m_window.wasResized()) + { vk::Extent2D size = m_window.getFramebufferSize(); - while (size.width == 0 || size.height == 0) { + while (size.width == 0 || size.height == 0) + { Window::waitEvents(); size = m_window.getFramebufferSize(); } @@ -96,107 +151,154 @@ void VulkanRenderer::handleSwapchainResizing() { } } -void VulkanRenderer::setupUI() { m_imgui = std::make_unique(*m_context, m_window); } +void VulkanRenderer::setupUI() +{ + m_imgui = std::make_unique(*m_context, m_window, m_window.getEventManager()); +} -VulkanRenderer::~VulkanRenderer() { +VulkanRenderer::~VulkanRenderer() +{ m_context->device().waitIdle(); - for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) { + for (auto i = 0; i < m_frameManager->getFramesInFlightCount(); ++i) + { m_context->device().destroyImageView(m_msaaColorViews[i]); m_context->device().destroyImageView(m_resolveViews[i]); m_context->device().destroyImageView(m_sceneViewViews[i]); m_context->device().destroyImageView(m_depthViews[i]); } + + m_context->device().destroyDescriptorPool(m_descriptorPool); } -void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) { +void VulkanRenderer::beginCommandBuffer(vk::CommandBuffer cmd) +{ cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); } -void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_pipeline->getLayout(), 0, +void VulkanRenderer::bindDescriptorSets(vk::CommandBuffer cmd) +{ + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_pipeline->getLayout(), + 0, m_descriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), nullptr); } -void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) { - //cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_pipeline->get()); - cmd.draw(36, 1, 0, 0); +void VulkanRenderer::drawGeometry(vk::CommandBuffer cmd) +{ + for (const auto& obj : m_objects) + { + cmd.pushConstants( + m_pipeline->getLayout(), vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &obj.transform[0][0]); + + vk::Buffer vbs[] = {obj.mesh->getVertexBuffer()}; + vk::DeviceSize offsets[] = {0}; + cmd.bindVertexBuffers(0, 1, vbs, offsets); + cmd.bindIndexBuffer(obj.mesh->getIndexBuffer(), 0, vk::IndexType::eUint32); + cmd.drawIndexed(obj.mesh->getIndexCount(), 1, 0, 0, 0); + } } -void VulkanRenderer::renderUI(const vk::CommandBuffer cmd) const { +void VulkanRenderer::renderUI(const vk::CommandBuffer cmd) const +{ m_imgui->createFrame(); m_imgui->drawFrame(cmd); } -void VulkanRenderer::endDynamicRendering(vk::CommandBuffer cmd) { cmd.endRendering(); } - +void VulkanRenderer::endDynamicRendering(vk::CommandBuffer cmd) +{ + cmd.endRendering(); +} -void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) { cmd.end(); } +void VulkanRenderer::endCommandBuffer(vk::CommandBuffer cmd) +{ + cmd.end(); +} -void VulkanRenderer::submitAndPresent(uint32_t imageIndex) { - m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), - m_swapchain->get(), imageIndex); +void VulkanRenderer::submitAndPresent(uint32_t imageIndex) +{ + m_frameManager->endFrame(m_context->graphicsQueue(), m_context->presentQueue(), m_swapchain->get(), imageIndex); } -void VulkanRenderer::beginDynamicRendering( - vk::CommandBuffer cmd, - vk::ImageView colorImageView, - vk::ImageView depthImageView, - vk::Extent2D extent, - bool clearColor=true, - bool clearDepth=false) +void VulkanRenderer::beginDynamicRendering(vk::CommandBuffer cmd, + vk::ImageView colorImageView, + vk::ImageView depthImageView, + vk::Extent2D extent, + bool clearColor = true, + bool clearDepth = false) { - constexpr vk::ClearValue clearColorValue = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); vk::RenderingAttachmentInfo colorAttachment{}; - colorAttachment.imageView = colorImageView; - colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; - colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachment.clearValue = clearColorValue; + vk::RenderingAttachmentInfo depthAttachment{}; + vk::RenderingInfo renderingInfo{}; + renderingInfo.renderArea.offset = vk::Offset2D{0, 0}; + renderingInfo.renderArea.extent = extent; + renderingInfo.layerCount = 1; + constexpr vk::ClearValue clearColorValue = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); vk::ClearValue depthClearValue{}; depthClearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); - vk::RenderingAttachmentInfo depthAttachment{}; - depthAttachment.imageView = depthImageView; - depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - depthAttachment.loadOp = clearDepth ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; - depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; - depthAttachment.clearValue = depthClearValue; + if (colorImageView) + { + colorAttachment.imageView = colorImageView; + colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; + colorAttachment.loadOp = clearColor ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; + colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; + colorAttachment.clearValue = clearColorValue; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachment; + } + else + { + renderingInfo.colorAttachmentCount = 0; + renderingInfo.pColorAttachments = nullptr; + } - vk::RenderingInfo renderingInfo{}; - renderingInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderingInfo.renderArea.extent = extent; - renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - renderingInfo.pDepthAttachment = &depthAttachment; + if (depthImageView) + { + depthAttachment.imageView = depthImageView; + depthAttachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; + depthAttachment.loadOp = clearDepth ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad; + depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; + depthAttachment.clearValue = depthClearValue; + renderingInfo.pDepthAttachment = &depthAttachment; + } + else + { + renderingInfo.pDepthAttachment = nullptr; + } cmd.beginRendering(renderingInfo); } -void VulkanRenderer::drawFrame() { +void VulkanRenderer::drawFrame() +{ handleSwapchainResizing(); uint32_t imageIndex; - if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) { + if (!m_frameManager->beginFrame(m_swapchain->get(), imageIndex)) + { return; // Swapchain out-of-date } - const auto ¤tFrame = m_frameManager->getCurrentFrame(); - const uint32_t frameIdx = m_frameManager->getCurrentFrameIndex(); - const vk::CommandBuffer cmd = currentFrame.commandBuffer; - const vk::Image swapchainImage = m_swapchain->getImages()[imageIndex]; - const vk::Extent2D extent = m_swapchain->getExtent(); + static auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + const auto& currentFrame = m_frameManager->getCurrentFrame(); + const uint32_t frameIdx = m_frameManager->getCurrentFrameIndex(); + const vk::CommandBuffer cmd = currentFrame.commandBuffer; + const vk::Image swapchainImage = m_swapchain->getImages()[imageIndex]; + const vk::Extent2D extent = m_swapchain->getExtent(); - const auto width = m_swapchain->getExtent().width; - const auto height = m_swapchain->getExtent().height; - const vk::Image msaaImage = m_msaaImages[frameIdx]->get(); - const vk::ImageView msaaView = m_msaaColorViews[frameIdx]; - const vk::Image resolveImage = m_resolveImages[frameIdx]->get(); - const vk::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); + const auto width = m_swapchain->getExtent().width; + const auto height = m_swapchain->getExtent().height; + const vk::Image msaaImage = m_msaaImages[frameIdx]->get(); + const vk::ImageView msaaView = m_msaaColorViews[frameIdx]; + const vk::Image resolveImage = m_resolveImages[frameIdx]->get(); + const vk::Image sceneViewImage = m_sceneViewImages[frameIdx]->get(); SceneUBO sceneData{}; const auto aspect = static_cast(width) / static_cast(height); @@ -208,15 +310,56 @@ void VulkanRenderer::drawFrame() { compositeData.uExposure = m_imgui->getExposure(); compositeData.uContrast = m_imgui->getContrast(); compositeData.uSaturation = m_imgui->getSaturation(); + compositeData.uFogDensity = m_imgui->getFogDensity(); m_uniformManager->update(frameIdx, compositeData); + m_light.lightDirection = glm::vec4(sin(time), -0.5f, cos(time), 0.0f); + m_light.lightDirection = glm::normalize(m_light.lightDirection); + + const float orthoSize = 10.0f; + const float nearPlane = 0.1f; + const float farPlane = 100.0f; + glm::mat4 lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); + + glm::vec3 lightTarget = glm::vec3(0.0f); + float lightDistance = 20.0f; + glm::vec3 lightPosition = lightTarget - glm::vec3(m_light.lightDirection) * lightDistance; + glm::mat4 lightView = glm::lookAt( + lightPosition, // Position of the light in world space + lightTarget, // The point the light is looking at (scene origin) + glm::vec3(0.0f, 1.0f, 0.0f) // Up vector + ); + + glm::mat4 clipCorrection = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f,-1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.5f, 0.0f, + 0.0f, 0.0f, 0.5f, 1.0f + }; + + glm::mat4 lightSpaceMatrix = clipCorrection * lightProjection * lightView; + + // 4. Set the matrix for the shadow mapping pass. + m_shadowMapping->setLightMatrix(lightSpaceMatrix, frameIdx); + SceneUBO ubo{}; - ubo.view = m_camera.getView(); - ubo.projection = m_camera.getProjection(); + ubo.view = m_camera.getViewMatrix(); + ubo.projection = m_camera.getProjectionMatrix(); + ubo.lightSpaceMatrix = lightSpaceMatrix; + m_uniformManager->update(frameIdx, m_light); m_uniformManager->update(frameIdx, ubo); + // Get descriptor info for both UBOs vk::DescriptorBufferInfo sceneBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + vk::DescriptorBufferInfo lightBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); + + vk::DescriptorImageInfo shadowMapImageInfo = {}; + shadowMapImageInfo.sampler = m_shadowMapping->shadowMapSampler(); + shadowMapImageInfo.imageView = m_shadowMapping->shadowMapView(); + shadowMapImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + + // Create write for Scene UBO at binding 0 vk::WriteDescriptorSet sceneWrite{}; sceneWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); sceneWrite.dstBinding = 0; @@ -224,13 +367,37 @@ void VulkanRenderer::drawFrame() { sceneWrite.descriptorCount = 1; sceneWrite.pBufferInfo = &sceneBufferInfo; - m_descriptorSet->updateSet({sceneWrite}); + // Create write for Light UBO at binding 1 + vk::WriteDescriptorSet lightWrite{}; + lightWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + lightWrite.dstBinding = 1; // Target binding 1 + lightWrite.descriptorType = vk::DescriptorType::eUniformBuffer; + lightWrite.descriptorCount = 1; + lightWrite.pBufferInfo = &lightBufferInfo; + + vk::WriteDescriptorSet shadowMapWrite{}; + shadowMapWrite.dstSet = m_descriptorSet->getCurrentSet(frameIdx); + shadowMapWrite.dstBinding = 2; // Target binding 2 + shadowMapWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; + shadowMapWrite.descriptorCount = 1; + shadowMapWrite.pImageInfo = &shadowMapImageInfo; + + m_descriptorSet->updateSet({sceneWrite, lightWrite, shadowMapWrite}); beginCommandBuffer(cmd); // get depth image view for this frame vk::ImageView depthView = m_depthViews[frameIdx]; + m_imageStateTracker.transition(cmd, + m_depthImages[frameIdx]->get(), + vk::ImageLayout::eDepthAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::AccessFlagBits::eNone, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::ImageAspectFlagBits::eDepth); + beginDynamicRendering(cmd, nullptr, depthView, extent, false, true); utils::setupViewportAndScissor(cmd, extent); bindDescriptorSets(cmd); @@ -238,17 +405,34 @@ void VulkanRenderer::drawFrame() { drawGeometry(cmd); endDynamicRendering(cmd); + + + //m_shadowMapping->setLightMatrix(lightMVP, frameIdx); + auto drawFunc = [this](vk::CommandBuffer cmd) { + this->drawGeometry(cmd); + }; + + m_shadowMapping->recordShadowPass(cmd, frameIdx, drawFunc); + + m_imageStateTracker.transition(cmd, + m_shadowMapping->shadowMapImage(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eDepthStencilAttachmentWrite, + vk::AccessFlagBits::eShaderRead, + vk::ImageAspectFlagBits::eDepth); + // --- 1. Geometry Pass --- // Transition the MSAA image so we can render the main scene into it. // Its layout was likely UNDEFINED (on first use) or TRANSFER_SRC (from previous frame's resolve). - m_imageStateTracker.transition( - cmd, - msaaImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage - vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage - {}, // Source Access - vk::AccessFlagBits::eColorAttachmentWrite // Destination Access + m_imageStateTracker.transition(cmd, + msaaImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, // Source Stage + vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination Stage + {}, // Source Access + vk::AccessFlagBits::eColorAttachmentWrite // Destination Access ); beginDynamicRendering(cmd, msaaView, depthView, extent, true, false); @@ -263,26 +447,22 @@ void VulkanRenderer::drawFrame() { // vkCmdResolveImage requires the source to be TRANSFER_SRC and destination to be TRANSFER_DST. // Transition MSAA image from COLOR_ATTACHMENT to TRANSFER_SRC_OPTIMAL to be read by the resolve command. - m_imageStateTracker.transition( - cmd, - msaaImage, - vk::ImageLayout::eTransferSrcOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eTransfer, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eTransferRead - ); + m_imageStateTracker.transition(cmd, + msaaImage, + vk::ImageLayout::eTransferSrcOptimal, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eColorAttachmentWrite, + vk::AccessFlagBits::eTransferRead); // Transition the Resolve image to TRANSFER_DST_OPTIMAL to be written to by the resolve command. - m_imageStateTracker.transition( - cmd, - resolveImage, - vk::ImageLayout::eTransferDstOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eTransfer, - {}, - vk::AccessFlagBits::eTransferWrite - ); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eTransferDstOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eTransfer, + {}, + vk::AccessFlagBits::eTransferWrite); utils::resolveMSAAImageTo(cmd, msaaImage, resolveImage, width, height); @@ -290,28 +470,43 @@ void VulkanRenderer::drawFrame() { // This pass reads from the resolved image and writes to the swapchain image. // Transition the Resolve image from TRANSFER_DST to SHADER_READ_ONLY so it can be used as a texture. - m_imageStateTracker.transition( - cmd, - resolveImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eTransfer, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eTransferWrite, - vk::AccessFlagBits::eShaderRead - ); + m_imageStateTracker.transition(cmd, + resolveImage, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eTransferWrite, + vk::AccessFlagBits::eShaderRead); // Transition the Swapchain image to COLOR_ATTACHMENT_OPTIMAL so we can render the composite result to it. + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); + m_imageStateTracker.transition( cmd, - swapchainImage, + sceneViewImage, vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - {}, - vk::AccessFlagBits::eColorAttachmentWrite + vk::PipelineStageFlagBits::eFragmentShader, // Source stage (previous shader read use) + vk::PipelineStageFlagBits::eColorAttachmentOutput, // Destination stage + vk::AccessFlagBits::eShaderRead, // Source access + vk::AccessFlagBits::eColorAttachmentWrite // Destination access ); - beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], depthView, extent, true); + m_imageStateTracker.transition(cmd, + m_depthImages[frameIdx]->get(), + vk::ImageLayout::eDepthStencilReadOnlyOptimal, + vk::PipelineStageFlagBits::eFragmentShader, + vk::PipelineStageFlagBits::eEarlyFragmentTests, + vk::AccessFlagBits::eShaderRead, + vk::AccessFlagBits::eDepthStencilAttachmentRead, + vk::ImageAspectFlagBits::eDepth); + + beginDynamicRendering(cmd, m_sceneViewViews[frameIdx], nullptr, extent, true); utils::setupViewportAndScissor(cmd, extent); vk::DescriptorBufferInfo compositeBufferInfo = m_uniformManager->getDescriptorInfo(frameIdx); @@ -321,55 +516,63 @@ void VulkanRenderer::drawFrame() { imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageInfo.sampler = m_sampler->get(); - std::vector writes = { - vk::WriteDescriptorSet{ - m_compositeDescriptorSet->getCurrentSet(frameIdx), - 0, - 0, - 1, - vk::DescriptorType::eCombinedImageSampler, - &imageInfo, - nullptr, - }, - vk::WriteDescriptorSet{ - m_compositeDescriptorSet->getCurrentSet(frameIdx), - 1, - 0, - 1, - vk::DescriptorType::eUniformBuffer, - nullptr, - &compositeBufferInfo, - nullptr - } - }; + vk::DescriptorImageInfo depthImageInfo = {}; + depthImageInfo.imageView = depthView; + depthImageInfo.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal; + depthImageInfo.sampler = m_sampler->get(); + + std::vector writes = {vk::WriteDescriptorSet{ + m_compositeDescriptorSet->getCurrentSet(frameIdx), + 0, + 0, + 1, + vk::DescriptorType::eCombinedImageSampler, + &imageInfo, + nullptr, + }, + vk::WriteDescriptorSet{m_compositeDescriptorSet->getCurrentSet(frameIdx), + 1, + 0, + 1, + vk::DescriptorType::eUniformBuffer, + nullptr, + &compositeBufferInfo, + nullptr}, + vk::WriteDescriptorSet{m_compositeDescriptorSet->getCurrentSet(frameIdx), + 2, + 0, + 1, + vk::DescriptorType::eCombinedImageSampler, + &depthImageInfo, + nullptr}}; m_compositeDescriptorSet->updateSet(writes); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, m_compositePipeline->get()); - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_compositePipeline->getLayout(), 0, m_compositeDescriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), nullptr); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_compositePipeline->getLayout(), + 0, + m_compositeDescriptorSet->getCurrentSet(m_frameManager->getFrameIndex()), + nullptr); cmd.draw(3, 1, 0, 0); endDynamicRendering(cmd); // -- Prepare sceneView for ImGui - m_imageStateTracker.transition( - cmd, - sceneViewImage, - vk::ImageLayout::eShaderReadOnlyOptimal, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, - vk::AccessFlagBits::eShaderRead - ); + m_imageStateTracker.transition(cmd, + sceneViewImage, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eColorAttachmentWrite, + vk::AccessFlagBits::eShaderRead); // UI pass - m_imageStateTracker.transition( - cmd, - swapchainImage, - vk::ImageLayout::eColorAttachmentOptimal, - vk::PipelineStageFlagBits::eTopOfPipe, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - {}, - vk::AccessFlagBits::eColorAttachmentWrite - ); + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + {}, + vk::AccessFlagBits::eColorAttachmentWrite); // --- 4. UI Pass --- // The UI is rendered on top of the composited scene. @@ -382,259 +585,187 @@ void VulkanRenderer::drawFrame() { // --- 5. Prepare for Presentation --- // Transition the swapchain image from COLOR_ATTACHMENT to PRESENT_SRC_KHR for the presentation engine. - m_imageStateTracker.transition( - cmd, - swapchainImage, - vk::ImageLayout::ePresentSrcKHR, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eBottomOfPipe, - vk::AccessFlagBits::eColorAttachmentWrite, - {} - ); + m_imageStateTracker.transition(cmd, + swapchainImage, + vk::ImageLayout::ePresentSrcKHR, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eBottomOfPipe, + vk::AccessFlagBits::eColorAttachmentWrite, + {}); endCommandBuffer(cmd); submitAndPresent(imageIndex); } -void VulkanRenderer::createMSAAImage() { - vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e4; - - // set format to HDR capable space +void VulkanRenderer::createMSAAImage() +{ vk::Format format = vk::Format::eR16G16B16A16Sfloat; - ; - constexpr vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eInputAttachment | - vk::ImageUsageFlagBits::eTransferSrc; - - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = m_swapchain->getExtent().width; - imageInfo.extent.height = m_swapchain->getExtent().height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = usage; - imageInfo.samples = msaaSamples; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment + | vk::ImageUsageFlagBits::eTransferSrc; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); m_msaaImages.resize(framesInFlight); m_msaaColorViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { - // Create the Image object - m_msaaImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).setSamples(vk::SampleCountFlagBits::e4).build(); + m_msaaImages[i] = std::move(built.image); + m_msaaColorViews[i] = built.view; m_imageStateTracker.recordState(m_msaaImages[i]->get(), vk::ImageLayout::eUndefined); - - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_msaaImages[i]->get(); // Get the VkImage from your new Image object - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_msaaColorViews[i] = m_context->device().createImageView(viewInfo); } } -void VulkanRenderer::createResolveImages() { - vk::Format format = vk::Format::eR16G16B16A16Sfloat; // Match your swapchain/attachment format - vk::Extent2D extent = m_swapchain->getExtent(); - +void VulkanRenderer::createResolveImages() +{ + vk::Format format = vk::Format::eR16G16B16A16Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eTransferDst; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); + + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); m_resolveImages.resize(framesInFlight); m_resolveViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst; - imageInfo.samples = vk::SampleCountFlagBits::e1; // Resolve is NOT multisampled! - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - // Create the Image (assume you have your own Image wrapper e.g. using VMA) - m_resolveImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples - m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); + m_resolveImages[i] = std::move(built.image); + m_resolveViews[i] = built.view; - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_resolveImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_resolveViews[i] = m_context->device().createImageView(viewInfo); + m_imageStateTracker.recordState(m_resolveImages[i]->get(), vk::ImageLayout::eUndefined); } } -void VulkanRenderer::createSceneViewImages() { - vk::Format format = m_swapchain->getFormat(); - vk::Extent2D extent = m_swapchain->getExtent(); - +void VulkanRenderer::createSceneViewImages() +{ + vk::Format format = m_swapchain->getFormat(); + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eTransferSrc; size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - // destroy old resources if recreating - for (size_t i = 0; i < m_sceneViewViews.size(); ++i) { + // Destroy old resources if recreating + for (size_t i = 0; i < m_sceneViewViews.size(); ++i) + { m_context->device().destroyImageView(m_sceneViewViews[i]); } m_sceneViewImages.clear(); m_sceneViewViews.clear(); + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); m_sceneViewImages.resize(framesInFlight); m_sceneViewViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment | - vk::ImageUsageFlagBits::eSampled | - vk::ImageUsageFlagBits::eTransferSrc; - imageInfo.samples = vk::SampleCountFlagBits::e1; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - m_sceneViewImages[i] = std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format).setUsage(usage).build(); // Defaults to e1 samples and color aspect - m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); + m_sceneViewImages[i] = std::move(built.image); + m_sceneViewViews[i] = built.view; - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_sceneViewImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_sceneViewViews[i] = m_context->device().createImageView(viewInfo); + m_imageStateTracker.recordState(m_sceneViewImages[i]->get(), vk::ImageLayout::eUndefined); } } -void VulkanRenderer::createSampler() { +void VulkanRenderer::createSampler() +{ vk::SamplerCreateInfo samplerInfo{}; - samplerInfo.magFilter = vk::Filter::eLinear; - samplerInfo.minFilter = vk::Filter::eLinear; - samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; - samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; - samplerInfo.mipLodBias = 0.0f; + samplerInfo.magFilter = vk::Filter::eLinear; + samplerInfo.minFilter = vk::Filter::eLinear; + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; + samplerInfo.mipLodBias = 0.0f; samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = vk::CompareOp::eAlways; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 0.0f; - samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = vk::CompareOp::eAlways; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; m_sampler = std::make_unique(m_context->device(), samplerInfo); } -void VulkanRenderer::createDescriptorSets() { +void VulkanRenderer::createDescriptorSets() +{ + + const auto framesInFlight = m_frameManager->getFramesInFlightCount(); m_sceneViewImageDescriptorSets.resize(framesInFlight); - for (int i = 0; i < framesInFlight; ++i) { - m_sceneViewImageDescriptorSets[i] = - m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); + for (int i = 0; i < framesInFlight; ++i) + { + m_sceneViewImageDescriptorSets[i] = m_imgui->createDescriptorSet(m_sceneViewViews[i], m_sampler->get()); } } -void VulkanRenderer::createDepthImages() { - vk::Format format = vk::Format::eD32Sfloat; // Common depth format - vk::Extent2D extent = m_swapchain->getExtent(); - size_t framesInFlight = m_frameManager->getFramesInFlightCount(); +void VulkanRenderer::createDepthImages() +{ + vk::Format format = vk::Format::eD32Sfloat; + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; + size_t framesInFlight = m_frameManager->getFramesInFlightCount(); - for (size_t i = 0; i < m_depthImages.size(); ++i) { + // Destroy old resources if recreating + for (size_t i = 0; i < m_depthViews.size(); ++i) + { m_context->device().destroyImageView(m_depthViews[i]); } m_depthImages.clear(); m_depthViews.clear(); + utils::ImageBuilder builder(m_context->device(), *m_allocator, m_swapchain->getExtent()); m_depthImages.resize(framesInFlight); m_depthViews.resize(framesInFlight); - for (size_t i = 0; i < framesInFlight; ++i) { - vk::ImageCreateInfo imageInfo{}; - imageInfo.imageType = vk::ImageType::e2D; - imageInfo.extent.width = extent.width; - imageInfo.extent.height = extent.height; - imageInfo.extent.depth = 1; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.format = format; - imageInfo.tiling = vk::ImageTiling::eOptimal; - imageInfo.initialLayout = vk::ImageLayout::eUndefined; - imageInfo.usage = - vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled; - imageInfo.samples = vk::SampleCountFlagBits::e4; - imageInfo.sharingMode = vk::SharingMode::eExclusive; - - m_depthImages[i] = - std::make_unique(*m_allocator, imageInfo, VMA_MEMORY_USAGE_GPU_ONLY); - m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); - - vk::ImageViewCreateInfo viewInfo{}; - viewInfo.image = m_depthImages[i]->get(); - viewInfo.viewType = vk::ImageViewType::e2D; - viewInfo.format = format; - viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; - viewInfo.subresourceRange.baseMipLevel = 0; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.baseArrayLayer = 0; - viewInfo.subresourceRange.layerCount = 1; - - m_depthViews[i] = m_context->device().createImageView(viewInfo); - } -} -void VulkanRenderer::createDepthPipelineAndDescriptorSets() { - const std::string depthVertexShaderPath = m_config.vertShaderPath; - const std::string emptyFragShaderPath = ""; // no fragment shader + for (size_t i = 0; i < framesInFlight; ++i) + { + auto built = builder.setFormat(format) + .setUsage(usage) + .setSamples(vk::SampleCountFlagBits::e4) + .setAspectMask(vk::ImageAspectFlagBits::eDepth) + .build(); - std::vector setLayouts = { - m_descriptorSet->getLayout()}; + m_depthImages[i] = std::move(built.image); + m_depthViews[i] = built.view; - m_depthPipeline = std::make_unique( - m_context->device(), - vk::Format::eUndefined, - depthVertexShaderPath, - emptyFragShaderPath, - setLayouts, - 4, - vk::Format::eD32Sfloat, - true - ); + m_imageStateTracker.recordState(m_depthImages[i]->get(), vk::ImageLayout::eUndefined); + } +} +void VulkanRenderer::createDepthPipelineAndDescriptorSets() +{ + std::vector setLayouts = {m_descriptorSet->getLayout()}; + + m_depthPipeline = Pipeline::Builder(m_context->device()) + .setVertexShader(m_config.vertShaderPath) + // No fragment shader, we only want depth output + .setVertexInputFromVertex() + .setDepthAttachment(vk::Format::eD32Sfloat, true) // depth test and write enabled + .setDescriptorSetLayouts(setLayouts) + .setMultisample(4) + .setFrontFace(vk::FrontFace::eClockwise) // Match main geometry pipeline + .addPushContantRange(vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4)) + .build(); +} +void VulkanRenderer::initScene() +{ + auto planeVerts = generatePlaneVertices(10, 50.0f); + auto planeInds = generatePlaneIndices(10); + auto planeMesh = std::make_shared(*m_allocator, planeVerts, planeInds); + m_objects.push_back({planeMesh, glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -0.5f, 0.0f))}); + + auto meshDataVec = loadModelFromBinary("monkey.mesh"); + + if (!meshDataVec.empty()) + { + const auto& meshData = meshDataVec[0]; // Use the first mesh for monkey + auto monkeyMesh = std::make_shared(*m_allocator, meshData.vertices, meshData.indices); + m_objects.push_back(RenderObject{monkeyMesh}); + } } } // namespace reactor diff --git a/src/vulkan/VulkanRenderer.hpp b/src/vulkan/VulkanRenderer.hpp index d415968..e297c4a 100644 --- a/src/vulkan/VulkanRenderer.hpp +++ b/src/vulkan/VulkanRenderer.hpp @@ -3,6 +3,7 @@ #include #include "../core/Camera.hpp" +#include "../core/Uniforms.hpp" #include "../core/Window.hpp" #include "../imgui/Imgui.hpp" #include "Allocator.hpp" @@ -10,17 +11,22 @@ #include "FrameManager.hpp" #include "Image.hpp" #include "ImageStateTracker.h" +#include "Mesh.hpp" +#include "MeshGenerators.hpp" #include "Pipeline.hpp" #include "Sampler.hpp" +#include "ShadowMapping.hpp" #include "Swapchain.hpp" #include "UniformManager.hpp" #include "VulkanContext.hpp" -namespace reactor { +namespace reactor +{ -struct RendererConfig { - uint32_t windowWidth; - uint32_t windowHeight; +struct RendererConfig +{ + uint32_t windowWidth; + uint32_t windowHeight; std::string windowTitle; std::string vertShaderPath; std::string fragShaderPath; @@ -28,45 +34,62 @@ struct RendererConfig { std::string compositeFragShaderPath; }; -class VulkanRenderer { - public: - VulkanRenderer(const RendererConfig &config, Window &window, Camera &camera); +struct RenderObject +{ + std::shared_ptr mesh; + glm::mat4 transform = glm::mat4(1.0f); +}; + +class VulkanRenderer +{ +public: + VulkanRenderer(const RendererConfig& config, Window& window, Camera& camera); ~VulkanRenderer(); void drawFrame(); - private: - const RendererConfig &m_config; - Window &m_window; - Camera &m_camera; - - std::unique_ptr m_context; - std::unique_ptr m_swapchain; - std::unique_ptr m_allocator; - std::unique_ptr m_frameManager; - std::unique_ptr m_descriptorSet; - std::unique_ptr m_pipeline; - std::unique_ptr m_compositePipeline; - std::unique_ptr m_depthPipeline; - std::unique_ptr m_compositeDescriptorSet; - std::unique_ptr m_sampler; + vk::Device device() const; + Allocator& allocator(); + vk::DescriptorPool descriptorPool() const; + +private: + const RendererConfig& m_config; + Window& m_window; + Camera& m_camera; + + std::unique_ptr m_context; + std::unique_ptr m_swapchain; + std::unique_ptr m_allocator; + std::unique_ptr m_frameManager; + std::unique_ptr m_descriptorSet; + std::unique_ptr m_pipeline; + std::unique_ptr m_compositePipeline; + std::unique_ptr m_depthPipeline; + std::unique_ptr m_compositeDescriptorSet; + std::unique_ptr m_sampler; std::unique_ptr m_uniformManager; - std::unique_ptr m_imgui; + std::unique_ptr m_imgui; + std::unique_ptr m_shadowMapping; ImageStateTracker m_imageStateTracker; std::vector> m_msaaImages; - std::vector m_msaaColorViews; + std::vector m_msaaColorViews; std::vector> m_resolveImages; - std::vector m_resolveViews; + std::vector m_resolveViews; std::vector> m_sceneViewImages; - std::vector m_sceneViewViews; - std::vector m_sceneViewImageDescriptorSets; + std::vector m_sceneViewViews; + std::vector m_sceneViewImageDescriptorSets; std::vector> m_depthImages; - std::vector m_depthViews; - std::vector m_depthImageDescriptorSets; + std::vector m_depthViews; + std::vector m_depthImageDescriptorSets; + + DirectionalLightUBO m_light; + std::vector m_objects; + + vk::DescriptorPool m_descriptorPool; void createCoreVulkanObjects(); void createSwapchainAndFrameManager(); @@ -79,18 +102,18 @@ class VulkanRenderer { void createDescriptorSets(); void createDepthImages(); void createDepthPipelineAndDescriptorSets(); + void initScene(); + void createDescriptorPool(); - void handleSwapchainResizing(); - void beginCommandBuffer(vk::CommandBuffer cmd); - void beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView colorImageView, - vk::ImageView depthImageView, vk::Extent2D extent, - bool clearColor, bool clearDepth); - void bindDescriptorSets(vk::CommandBuffer cmd); - void drawGeometry(vk::CommandBuffer cmd); - void renderUI(vk::CommandBuffer cmd) const; + void handleSwapchainResizing(); + void beginCommandBuffer(vk::CommandBuffer cmd); + void beginDynamicRendering(vk::CommandBuffer cmd, vk::ImageView colorImageView, vk::ImageView depthImageView, vk::Extent2D extent, bool clearColor, bool clearDepth); + void bindDescriptorSets(vk::CommandBuffer cmd); + void drawGeometry(vk::CommandBuffer cmd); + void renderUI(vk::CommandBuffer cmd) const; static void endDynamicRendering(vk::CommandBuffer cmd); static void endCommandBuffer(vk::CommandBuffer cmd); - void submitAndPresent(uint32_t imageIndex); + void submitAndPresent(uint32_t imageIndex); }; } // namespace reactor diff --git a/vcpkg.json b/vcpkg.json index 43be7d4..9690106 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,6 +15,7 @@ "vulkan-binding", "docking-experimental" ] - } + }, + "assimp" ] } \ No newline at end of file