This commit is contained in:
2026-03-21 23:10:48 +01:00
parent 8cbb794dba
commit 3153735d0c
18 changed files with 249 additions and 195 deletions

View File

@@ -3,6 +3,7 @@
#include <destrum/App.h>
#include <destrum/FS/AssetFS.h>
#include <destrum/Util/DeltaTime.h>
#include "glm/gtx/transform.hpp"
#include "spdlog/spdlog.h"
@@ -35,41 +36,33 @@ void App::init(const AppParams& params) {
gfxDevice.init(window, params.appName, false);
InputManager::GetInstance().Init();
Time::GetInstance().Update();
customInit();
}
void App::run() {
using clock = std::chrono::steady_clock;
Time::GetInstance().Update(); // initialize delta timing
const float targetHz = 60.0f;
const float dt = 1.0f / targetHz;
const float maxFrameTime = 0.25f; // clamp big hitches
const int maxSteps = 5; // prevent spiral of death
auto prevTime = clock::now();
const float fixedDt = static_cast<float>(Time::GetInstance().FixedDeltaTime());
const int maxSteps = 5; // prevent spiral of death
float accumulator = 0.0f;
isRunning = true;
while (isRunning) {
const auto frameStart = clock::now();
float frameTimeSec = std::chrono::duration<float>(frameStart - prevTime).count();
prevTime = frameStart;
// ---- Update timing ---
Time::GetInstance().Update();
float dt = static_cast<float>(Time::GetInstance().DeltaTime());
if (frameTimeSec > 0.07f && frameTimeSec < 5.f) {
spdlog::warn("Frame drop detected, time: {:.4f}s", frameTimeSec);
}
if (dt > 0.25f) dt = 0.25f;
if (frameTimeSec > maxFrameTime) frameTimeSec = maxFrameTime;
if (frameTimeSec < 0.0f) frameTimeSec = 0.0f;
accumulator += frameTimeSec;
if (frameTimeSec > 0.0f) {
const float newFPS = 1.0f / frameTimeSec;
if (dt > 0.0f) {
float newFPS = 1.0f / dt;
avgFPS = std::lerp(avgFPS, newFPS, 0.1f);
}
accumulator += dt;
InputManager::GetInstance().BeginFrame();
camera.Update(dt);
@@ -92,63 +85,43 @@ void App::run() {
}
}
if (!isRunning) break;
customUpdate(dt);
// ---- Swapchain resize check once per frame ----
int steps = 0;
while (accumulator >= fixedDt && steps < maxSteps) {
// physics.Update(fixedDt);
SDL_SetWindowTitle(
window,
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str()
);
accumulator -= fixedDt;
steps++;
}
if (steps == maxSteps) accumulator = 0.0f;
const float alpha = accumulator / fixedDt;
if (!gfxDevice.needsSwapchainRecreate()) {
customDraw();
}
if (gfxDevice.needsSwapchainRecreate()) {
spdlog::info("Recreating swapchain to size: {}x{}", m_params.windowSize.x, m_params.windowSize.y);
gfxDevice.recreateSwapchain(m_params.windowSize.x, m_params.windowSize.y);
onWindowResize(m_params.windowSize.x, m_params.windowSize.y);
}
// ---- Fixed updates (no event polling inside) ----
int steps = 0;
while (accumulator >= dt && steps < maxSteps) {
//Set window title to fps
SDL_SetWindowTitle(
window,
fmt::format("{} - FPS: {:.2f}", m_params.windowTitle, avgFPS).c_str());
accumulator -= dt;
steps++;
}
// If we hit the step cap, drop leftover time to recover smoothly
if (steps == maxSteps) accumulator = 0.0f;
// Optional interpolation factor for rendering
const float alpha = accumulator / dt;
// ---- Render ----
if (!gfxDevice.needsSwapchainRecreate()) {
// glm::mat4 objMatrix = glm::translate(glm::mat4(1.f), glm::vec3(0.f, -3.0f, 0.f));
//
// renderer.beginDrawing(gfxDevice);
// renderer.drawMesh(testMeshID, glm::mat4(1.f), testMaterialID);
// renderer.drawMesh(testMeshID, objMatrix, 0);
// renderer.endDrawing();
//
// const auto cmd = gfxDevice.beginFrame();
// const auto& drawImage = renderer.getDrawImage(gfxDevice);
//
// renderer.draw(cmd, gfxDevice, camera, GameRenderer::SceneData{
// camera, glm::vec3(0.1f), 0.5f, glm::vec3(0.5f), 0.01f
// });
//
// gfxDevice.endFrame(cmd, drawImage, {
// .clearColor = {{0.f, 0.f, 0.5f, 1.f}},
// .drawImageBlitRect = glm::ivec4{}
// });
customDraw();
}
// ---- Frame cap (if you still want it) ----
if (frameLimit) {
const auto targetEnd = frameStart + std::chrono::duration_cast<clock::duration>(
std::chrono::duration<float>(dt)
);
std::this_thread::sleep_until(targetEnd);
auto sleepTime = Time::GetInstance().SleepDuration();
if (sleepTime.count() > 0) {
std::this_thread::sleep_for(sleepTime);
}
}
}
gfxDevice.waitIdle();
}

View File

@@ -4,21 +4,17 @@
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <destrum/Util/DeltaTime.h>
#include "spdlog/spdlog.h"
Animator::Animator(GameObject& parent)
: Component(parent, "Animator") {}
// ─── Component interface ──────────────────────────────────────────────────────
void Animator::Update() {
if (!m_current.clip) return;
// Time delta comes from your engine's time system
// const float dt = Time::GetDeltaTime();
const float dt = 0.016f; // ~60 FPS
const float dt = Time::GetInstance().DeltaTime();
m_current.time += dt * m_current.speed;
if (m_current.clip->looped)
@@ -37,11 +33,6 @@ void Animator::Update() {
m_previous = {};
}
}
spdlog::info("Playing '{}': time = {:.2f}s, blend = {:.2f}",
m_currentClipName.empty() ? "(none)" : m_currentClipName.c_str(),
m_current.time,
m_blendT);
}
void Animator::ImGuiInspector() {
@@ -65,8 +56,6 @@ void Animator::ImGuiInspector() {
// }
}
// ─── Animation control ────────────────────────────────────────────────────────
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
m_clips[clip->name] = std::move(clip);
}
@@ -94,24 +83,18 @@ void Animator::stop() {
m_currentClipName = {};
}
// ─── Joint matrix upload ──────────────────────────────────────────────────────
std::size_t Animator::uploadJointMatrices(SkinningPipeline& pipeline,
const Skeleton& skeleton,
std::size_t frameIndex) {
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
auto matrices = computeJointMatrices(skeleton);
return pipeline.appendJointMatrices(matrices, frameIndex);
return ctx.renderer.getSkinningPipeline().appendJointMatrices(matrices, frameIndex);
}
// ─── Private: pose evaluation ─────────────────────────────────────────────────
std::vector<glm::mat4> Animator::computeJointMatrices(const Skeleton& skeleton) {
const std::size_t numJoints = skeleton.joints.size();
std::vector<glm::mat4> global(numJoints, glm::mat4{1.f});
std::vector<glm::mat4> result(numJoints, glm::mat4{1.f});
auto findTrack = [](const SkeletalAnimation* clip,
std::uint32_t idx) -> const SkeletalAnimation::Track* {
std::uint32_t idx) -> const SkeletalAnimation::Track* {
if (!clip) return nullptr;
for (auto& t : clip->tracks)
if (t.jointIndex == idx) return &t;
@@ -123,26 +106,31 @@ std::vector<glm::mat4> Animator::computeJointMatrices(const Skeleton& skeleton)
glm::quat rot = glm::identity<glm::quat>();
glm::vec3 sc = {1.f, 1.f, 1.f};
if (const auto* track = findTrack(m_current.clip, i)) {
tr = sampleTranslation(*track, m_current.time);
rot = sampleRotation (*track, m_current.time);
sc = sampleScale (*track, m_current.time);
}
// Sample current clip if one is playing
if (m_current.clip) {
if (const auto* track = findTrack(m_current.clip, i)) {
tr = sampleTranslation(*track, m_current.time);
rot = sampleRotation (*track, m_current.time);
sc = sampleScale (*track, m_current.time);
}
if (m_previous.clip && m_blendT < 1.f) {
if (const auto* prev = findTrack(m_previous.clip, i)) {
tr = glm::mix (sampleTranslation(*prev, m_previous.time), tr, m_blendT);
rot = glm::slerp(sampleRotation (*prev, m_previous.time), rot, m_blendT);
sc = glm::mix (sampleScale (*prev, m_previous.time), sc, m_blendT);
if (m_previous.clip && m_blendT < 1.f) {
if (const auto* prev = findTrack(m_previous.clip, i)) {
tr = glm::mix (sampleTranslation(*prev, m_previous.time), tr, m_blendT);
rot = glm::slerp(sampleRotation (*prev, m_previous.time), rot, m_blendT);
sc = glm::mix (sampleScale (*prev, m_previous.time), sc, m_blendT);
}
}
}
// If no clip: tr/rot/sc stay as identity → rest pose
glm::mat4 local = glm::translate(glm::mat4{1.f}, tr)
* glm::mat4_cast(rot)
* glm::scale(glm::mat4{1.f}, sc);
const int parent = skeleton.parentIndex[i];
global[i] = (parent < 0) ? local : global[parent] * local;
global[i] = (parent < 0) ? skeleton.rootPreTransform * local
: global[parent] * local;
result[i] = global[i] * skeleton.inverseBindMatrices[i];
}

View File

@@ -38,8 +38,8 @@ void MeshRendererComponent::Render(const RenderContext& ctx) {
std::uint32_t frameIdx = GameState::GetInstance().Gfx().getCurrentFrameIndex();
const std::size_t jointMatricesStartIndex = animator->uploadJointMatrices(
ctx.renderer.getSkinningPipeline(),
*skeleton, // skeleton stored on GPUMesh (or pass from CPUMesh)
ctx,
*skeleton,
frameIdx
);

View File

@@ -10,15 +10,13 @@
Camera::Camera(const glm::vec3& position, const glm::vec3& up)
: m_position{position}, m_up{up} {
// Initialize yaw to -90 degrees so the camera faces -Z by default
m_yaw = -glm::half_pi<float>();
m_pitch = 0.0f;
}
void Camera::Update(float deltaTime) {
auto& input = InputManager::GetInstance();
const auto& input = InputManager::GetInstance();
// --- tuning ---
float moveSpeed = m_movementSpeed;
if (input.IsKeyDown(SDL_SCANCODE_LSHIFT) || input.IsKeyDown(SDL_SCANCODE_RSHIFT)) {
moveSpeed *= 2.0f;
@@ -29,10 +27,7 @@ void Camera::Update(float deltaTime) {
moveSpeed *= 3.0f;
}
// =========================
// Look Input (Keyboard & Controller)
// =========================
const float keyLookSpeed = glm::radians(120.0f);
constexpr float keyLookSpeed = glm::radians(120.0f);
if (input.IsKeyDown(SDL_SCANCODE_UP)) m_pitch += keyLookSpeed * deltaTime;
if (input.IsKeyDown(SDL_SCANCODE_DOWN)) m_pitch -= keyLookSpeed * deltaTime;
if (input.IsKeyDown(SDL_SCANCODE_LEFT)) m_yaw -= keyLookSpeed * deltaTime;
@@ -46,13 +41,8 @@ void Camera::Update(float deltaTime) {
m_pitch -= ry * padLookSpeed * deltaTime; // Inverted to match stick convention
}
// Clamp pitch to prevent flipping over the top
m_pitch = glm::clamp(m_pitch, -glm::half_pi<float>() + 0.01f, glm::half_pi<float>() - 0.01f);
// =========================
// Update Basis Vectors
// =========================
// Standard Spherical to Cartesian coordinates (Y-Up, Right-Handed)
glm::vec3 front;
front.x = cos(m_yaw) * cos(m_pitch);
front.y = sin(m_pitch);
@@ -62,9 +52,6 @@ void Camera::Update(float deltaTime) {
m_right = glm::normalize(glm::cross(m_forward, glm::vec3(0, 1, 0))); // World Up
m_up = glm::normalize(glm::cross(m_right, m_forward));
// =========================
// Movement Input
// =========================
glm::vec3 move(0.0f);
if (input.IsKeyDown(SDL_SCANCODE_W)) move += m_forward;
if (input.IsKeyDown(SDL_SCANCODE_S)) move -= m_forward;
@@ -105,18 +92,11 @@ void Camera::CalculateViewMatrix() {
}
void Camera::CalculateProjectionMatrix() {
// RH_ZO: Right-Handed, Zero-to-One depth (Vulkan/D3D standard)
m_projectionMatrix = glm::perspectiveRH_ZO(glm::radians(fovAngle), m_aspectRatio, m_zNear, m_zFar);
// CRITICAL VULKAN FIX: Flip Y-axis
// This keeps the world upright and fixes winding order issues
m_projectionMatrix[1][1] *= -1;
}
// ---------------------------------------------------------
// Helpers to keep orientation consistent
// ---------------------------------------------------------
void Camera::SetRotation(float yawRadians, float pitchRadians) {
m_yaw = yawRadians;
m_pitch = glm::clamp(pitchRadians, -glm::half_pi<float>() + 0.001f, glm::half_pi<float>() - 0.001f);

View File

@@ -1,7 +1,10 @@
#include <destrum/Graphics/Pipelines/SkyboxPipeline.h>
#include <destrum/FS/AssetFS.h>
#include <glm/glm.hpp>
#include <destrum/Util/DeltaTime.h>
#include "glm/ext/matrix_transform.hpp"
#include "spdlog/spdlog.h"
SkyboxPipeline::SkyboxPipeline(): pipelineLayout{nullptr} {
@@ -72,12 +75,19 @@ void SkyboxPipeline::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camer
return;
}
//rotate skybox slowly for visual interest
const float rotationSpeed = 0.01f; // radians per second
//Rotate over the Y axis
skyboxRotation = glm::rotate(skyboxRotation, rotationSpeed * static_cast<float>(Time::GetInstance().DeltaTime()), glm::vec3(0.f, 1.f, 0.f));
pipeline->bind(cmd);
gfxDevice.bindBindlessDescSet(cmd, pipelineLayout);
const glm::mat3 r = glm::mat3(skyboxRotation);
const auto pcs = SkyboxPushConstants{
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
.cameraPos = glm::vec4{camera.GetPosition(), 1.f},
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
.skyboxRot = { glm::vec4(r[0], 0.f), glm::vec4(r[1], 0.f), glm::vec4(r[2], 0.f) },
.cameraPos = camera.GetPosition(),
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
};
vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(SkyboxPushConstants), &pcs);

View File

@@ -5,18 +5,16 @@
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Init.h>
#include <vulkan/vulkan.h>
#include <vulkan/vk_enum_string_helper.h>
#include "volk.h"
void Swapchain::initSync(VkDevice device)
{
const auto fenceCreateInfo = VkFenceCreateInfo{
void Swapchain::initSync(VkDevice device) {
constexpr auto fenceCreateInfo = VkFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
const auto semaphoreCreateInfo = VkSemaphoreCreateInfo{
constexpr auto semaphoreCreateInfo = VkSemaphoreCreateInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
@@ -57,7 +55,7 @@ void Swapchain::createSwapchain(GfxDevice* gfxDevice, VkFormat format, std::uint
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
};
for (auto& sem : imageRenderSemaphores) {
for (auto& sem: imageRenderSemaphores) {
VK_CHECK(vkCreateSemaphore(m_gfxDevice->getDevice(), &sci, nullptr, &sem));
}
@@ -150,8 +148,8 @@ std::pair<VkImage, int> Swapchain::acquireNextImage(int index) {
void Swapchain::submitAndPresent(
VkCommandBuffer cmd,
VkQueue graphicsQueue,
uint32_t imageIndex, // from vkAcquireNextImageKHR
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
uint32_t imageIndex, // from vkAcquireNextImageKHR
uint32_t frameIndex) // 0..FRAMES_IN_FLIGHT-1
{
auto& frame = frames[frameIndex]; // ✅ per-frame
@@ -164,14 +162,14 @@ void Swapchain::submitAndPresent(
};
VkSemaphoreSubmitInfo waitInfo =
vkinit::semaphoreSubmitInfo(
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
vkinit::semaphoreSubmitInfo(
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,
frame.swapchainSemaphore); // ✅ acquire semaphore (per-frame)
VkSemaphoreSubmitInfo signalInfo =
vkinit::semaphoreSubmitInfo(
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
renderFinished); // ✅ signal semaphore (per-image)
vkinit::semaphoreSubmitInfo(
VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT,
renderFinished); // ✅ signal semaphore (per-image)
VkSubmitInfo2 submit = vkinit::submitInfo(&cmdInfo, &waitInfo, &signalInfo);
VK_CHECK(vkQueueSubmit2(graphicsQueue, 1, &submit, frame.renderFence)); // ✅ fence (per-frame)
@@ -189,4 +187,4 @@ void Swapchain::submitAndPresent(
VkResult res = vkQueuePresentKHR(graphicsQueue, &presentInfo);
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) dirty = true;
else if (res != VK_SUCCESS) dirty = true;
}
}

View File

@@ -0,0 +1,24 @@
#include <destrum/Util/DeltaTime.h>
#include <iostream>
double Time::FixedDeltaTime() const {
return m_FixedDeltaTime;
}
double Time::DeltaTime() const {
return m_DeltaTime;
}
std::chrono::nanoseconds Time::SleepDuration() const {
constexpr auto msPerFrame = std::chrono::milliseconds(static_cast<int>(1000.f / FPS));
const std::chrono::nanoseconds sleepTime = (m_PrevTime + msPerFrame - std::chrono::high_resolution_clock::now());
return sleepTime;
}
void Time::Update() {
const auto currentTime = std::chrono::high_resolution_clock::now();
m_DeltaTime = std::chrono::duration<double>(currentTime - m_PrevTime).count();
m_PrevTime = currentTime;
}