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

Submodule TheChef updated: 3a7b137165...df0da96c38

View File

@@ -47,6 +47,8 @@ set(SRC_FILES
"src/FS/AssetFS.cpp"
"src/FS/Manifest.cpp"
"src/Util/DeltaTime.cpp"
)
add_library(destrum ${SRC_FILES})

Binary file not shown.

Binary file not shown.

View File

@@ -4,26 +4,33 @@
#include "bindless.glsl"
layout(location = 0) in vec2 uv; // from fullscreen triangle: 0..2 range
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 outColor;
layout(push_constant) uniform SkyboxPC {
mat4 invViewProj; // inverse(Proj * View) (your current setup)
vec4 cameraPos; // xyz = camera world position
uint skyboxTextureId; // index into textureCubes[]
mat4 invViewProj;
vec4 skyboxRot[3];
vec3 cameraPos;
uint skyboxTextureId;
} pcs;
void main()
{
vec2 ndcXY = uv * 2.0 - 1.0;
vec4 ndc = vec4(ndcXY, 1.0, 1.0);
vec4 world = pcs.invViewProj * ndc;
vec3 worldPos = world.xyz / world.w;
vec3 dir = normalize(worldPos - pcs.cameraPos.xyz);
vec3 dir = normalize(worldPos - pcs.cameraPos);
dir.y *= -1.0;
mat3 skyboxRot = mat3(
pcs.skyboxRot[0].xyz,
pcs.skyboxRot[1].xyz,
pcs.skyboxRot[2].xyz
);
dir = skyboxRot * dir;
outColor = sampleTextureCubeLinear(pcs.skyboxTextureId, dir);
}
}

View File

@@ -14,27 +14,22 @@
class SkinningPipeline;
class Animator : public Component {
class Animator final: public Component {
public:
explicit Animator(GameObject& parent);
// Component interface
void Update() override;
void ImGuiInspector() override;
// Animation control
void addClip(std::shared_ptr<SkeletalAnimation> clip);
void play(const std::string& name, float blendTime = 0.f);
void stop();
bool isPlaying() const { return m_current.clip != nullptr; }
const std::string& currentClipName() const { return m_currentClipName; }
float currentTime() const { return m_current.time; }
[[nodiscard]] bool isPlaying() const { return m_current.clip != nullptr; }
[[nodiscard]] const std::string& currentClipName() const { return m_currentClipName; }
[[nodiscard]] float currentTime() const { return m_current.time; }
// Called during draw command building
std::size_t uploadJointMatrices(SkinningPipeline& pipeline,
const Skeleton& skeleton,
std::size_t frameIndex);
std::size_t uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex);
Skeleton* getSkeleton() {
return &m_skeleton;
}

View File

@@ -32,12 +32,15 @@ private:
std::unique_ptr<Pipeline> pipeline;
ImageID skyboxTextureId{NULL_IMAGE_ID};
glm::mat4 skyboxRotation{1.f};
struct SkyboxPushConstants {
glm::mat4 invViewProj;
glm::vec4 cameraPos;
glm::mat4 invViewProj;
glm::vec4 skyboxRot[3];
glm::vec3 cameraPos;
std::uint32_t skyboxTextureId;
};
};
#endif //SKYBOXPIPELINE_H

View File

@@ -7,6 +7,7 @@
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <destrum/Graphics/ids.h>
@@ -28,6 +29,8 @@ struct Skeleton {
std::vector<Joint> joints;
std::vector<std::string> jointNames;
std::vector<int> parentIndex; // -1 = root, built once after loading
glm::mat4 rootPreTransform{1.f};
};
inline void buildParentIndex(Skeleton& skeleton) {

View File

@@ -0,0 +1,31 @@
#ifndef DELTATIME_H
#define DELTATIME_H
#include <chrono>
#include <destrum/Singleton.h>
class Time final: public Singleton<Time> {
public:
Time(const Time& other) = delete;
Time(Time&& other) = delete;
Time& operator=(const Time& other) = delete;
Time& operator=(Time&& other) = delete;
[[nodiscard]] double FixedDeltaTime() const;
[[nodiscard]] double DeltaTime() const;
[[nodiscard]] std::chrono::nanoseconds SleepDuration() const;
void Update();
private:
friend class Singleton;
Time() = default;
static constexpr double m_FixedDeltaTime{1.0 / 60.0};
static constexpr double FPS{60};
double m_DeltaTime{};
std::chrono::high_resolution_clock::time_point m_PrevTime;
};
#endif //DELTATIME_H

View File

@@ -12,6 +12,10 @@
// - For skinned meshes, vertex positions are kept in local/bind-pose space
// (identity world transform) so the skinning shader can apply joint matrices
// correctly at runtime.
// - rootPreTransform on Skeleton captures any non-bone ancestor transforms
// (e.g. the Assimp-injected coordinate-system node for glTF/FBX) so that
// computeJointMatrices can prepend it to root joints. This fixes the
// 90-degree rotation + mirror that appears when those transforms are dropped.
//
// Link against: assimp (e.g. -lassimp)
// Supports any format Assimp understands (.gltf, .glb, .fbx, .obj, …).
@@ -198,19 +202,32 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
// registering only nodes that correspond to actual bones.
// This guarantees joints are ordered parent-before-child, which is required
// by Animator::computeJointMatrices.
//
// Non-bone ancestor nodes (e.g. the Assimp coordinate-system root, or an
// "Armature" node in Blender exports) are NOT registered as joints, but
// their accumulated transform is carried forward so that the first real
// bone joint inherits it via skeleton.rootPreTransform. Without this the
// 90° rotation / mirror baked into those nodes is silently dropped, causing
// skinned meshes to appear rotated and/or mirrored relative to static ones.
JointId nextId = 0;
struct StackItem { const aiNode* node; int parentIdx; };
std::vector<StackItem> stack{{ scene->mRootNode, -1 }};
struct StackItem {
const aiNode* node;
int parentIdx;
glm::mat4 accWorld; // accumulated transform of non-bone ancestors
};
std::vector<StackItem> stack{{ scene->mRootNode, -1, glm::mat4{1.f} }};
while (!stack.empty()) {
auto [node, parentIdx] = stack.back();
auto [node, parentIdx, accWorld] = stack.back();
stack.pop_back();
std::string name(node->mName.C_Str());
int myIdx = parentIdx; // default: pass parent through to children
int myIdx = parentIdx;
glm::mat4 myAccWorld = accWorld; // only used while still outside the bone hierarchy
if (boneOffsets.count(name)) {
// ── This node IS a bone ──────────────────────────────────────────
const JointId id = nextId++;
myIdx = static_cast<int>(id);
@@ -220,19 +237,37 @@ static Skeleton LoadSkeleton(const aiScene* scene) {
skeleton.jointNames.push_back(name);
skeleton.inverseBindMatrices.push_back(boneOffsets[name]);
// Grow hierarchy to accommodate this id
// Grow hierarchy arrays to accommodate this id
while (skeleton.hierarchy.size() <= id)
skeleton.hierarchy.push_back({});
skeleton.hierarchy[id].id = id;
skeleton.hierarchy[id].id = id; // record the node's own id
if (parentIdx >= 0)
if (parentIdx >= 0) {
skeleton.hierarchy[parentIdx].children.push_back(id);
} else {
// First bone encountered with no bone parent: capture any
// non-bone ancestor transforms accumulated so far.
// Animator::computeJointMatrices prepends this to root joints.
skeleton.rootPreTransform = accWorld;
glm::mat4& m = skeleton.rootPreTransform;
// Normalize each basis vector to remove scale
m[0] = glm::vec4(glm::normalize(glm::vec3(m[0])), 0.f);
m[1] = glm::vec4(glm::normalize(glm::vec3(m[1])), 0.f);
m[2] = glm::vec4(glm::normalize(glm::vec3(m[2])), 0.f);
}
// Once we are inside the bone hierarchy the pre-transform is fully
// absorbed — children carry identity as their accWorld.
myAccWorld = glm::mat4{1.f};
} else {
// ── Not a bone: accumulate this node's local transform ───────────
myAccWorld = accWorld * ToGLM(node->mTransformation);
}
// Push children in reverse so left-most child is processed first
for (int c = static_cast<int>(node->mNumChildren) - 1; c >= 0; --c)
stack.push_back({ node->mChildren[c], myIdx });
stack.push_back({ node->mChildren[c], myIdx, myAccWorld });
}
buildParentIndex(skeleton);
@@ -318,11 +353,11 @@ static std::vector<SkeletalAnimation> LoadAnimations(const aiScene* scene,
SkeletalAnimation::Track track;
track.jointIndex = jointIdx;
// FIX: position, rotation and scale channels can have different
// keyframe counts and independent time axes. Build the track from
// the position channel's timeline and pick the nearest rotation/
// scale key for each sample rather than assuming index alignment.
// This avoids corrupted keyframes when counts differ.
// Position, rotation and scale channels can have different keyframe
// counts and independent time axes. Build the track from the position
// channel's timeline and pick the nearest rotation/scale key for each
// sample rather than assuming index alignment. This avoids corrupted
// keyframes when counts differ.
const unsigned int numPos = ch->mNumPositionKeys;
const unsigned int numRot = ch->mNumRotationKeys;
const unsigned int numSca = ch->mNumScalingKeys;
@@ -464,6 +499,11 @@ struct SkinnedModel {
// so they remain in bind-pose space. The skinning shader applies joint matrices
// at runtime — baking the node transform into positions would break that.
//
// The skeleton carries a rootPreTransform that captures any non-bone ancestor
// node transforms (coordinate-system correction nodes injected by Assimp for
// glTF/FBX, Blender "Armature" nodes, etc.). Animator::computeJointMatrices
// must prepend this to the global matrix of every root joint (parentIndex < 0).
//
// Usage:
// auto model = ModelLoader::LoadSkinnedModel("character.glb");
// MeshId id = meshCache.uploadMesh(model.meshes[0]);
@@ -499,7 +539,7 @@ static SkinnedModel LoadSkinnedModel(const std::string& path) {
? std::string(aiMesh->mName.C_Str())
: ("mesh_" + std::to_string(node->mMeshes[m]));
// FIX: pass identity matrix for skinned meshes so vertices stay in
// Pass identity matrix for skinned meshes so vertices stay in
// bind-pose space. The inverse bind matrices and joint transforms
// are already expressed in that space — baking the node world
// transform into positions would offset everything incorrectly.

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;
}

View File

@@ -44,10 +44,11 @@ void LightKeeper::customInit() {
auto& scene = SceneManager::GetInstance().CreateScene("Main");
auto testCube = std::make_shared<GameObject>("TestCube");
auto meshComp = testCube->AddComponent<MeshRendererComponent>();
meshComp->SetMeshID(testMeshID);
meshComp->SetMaterialID(testMaterialID);const int count = 100;
// auto testCube = std::make_shared<GameObject>("TestCube");
// auto meshComp = testCube->AddComponent<MeshRendererComponent>();
// meshComp->SetMeshID(testMeshID);
// meshComp->SetMaterialID(testMaterialID);
const int count = 100;
const float radius = 5.0f;
const float orbitRadius = 5.0f;
@@ -67,9 +68,9 @@ void LightKeeper::customInit() {
//
// scene.Add(childCube);
}
testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
// testCube->AddComponent<Spinner>(glm::vec3(0, 1, 0), glm::radians(10.0f)); // spin around Y, rad/sec
//rotate 180 around X axis
testCube->GetTransform().SetLocalRotation(glm::quat(glm::vec3(glm::radians(180.0f), 0.0f, 0.0f)));
// testCube->GetTransform().SetLocalRotation(glm::quat(glm::vec3(glm::radians(180.0f), 0.0f, 0.0f)));
//
auto globeRoot = std::make_shared<GameObject>("GlobeRoot");
globeRoot->GetTransform().SetWorldPosition(glm::vec3(0.0f));
@@ -78,7 +79,7 @@ void LightKeeper::customInit() {
scene.Add(testCube);
// scene.Add(testCube);
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/skybox.jpg");
// const auto skyboxID = AssetFS::GetInstance().GetFullPath("engine://textures/mars.jpg");
@@ -96,7 +97,7 @@ void LightKeeper::customInit() {
renderer.setSkyboxTexture(skyboxCubemap->GetCubeMapImageID());
//
const auto planeObj = std::make_shared<GameObject>("GroundPlane");
const auto planeMeshComp = planeObj->AddComponent<MeshRendererComponent>();
const auto planeModel = ModelLoader::LoadGLTF_CPUMeshes_MergedPerMesh(AssetFS::GetInstance().GetFullPath("game://plane.glb").generic_string());
@@ -104,10 +105,10 @@ void LightKeeper::customInit() {
const auto planeTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("game://grass.png"));
const auto planeMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = glm::vec3(1.f),
.textureFilteringMode = TextureFilteringMode::Nearest,
.diffuseTexture = planeTextureID,
.name = "GroundPlaneMaterial",
.baseColor = glm::vec3(1.f),
.textureFilteringMode = TextureFilteringMode::Nearest,
.diffuseTexture = planeTextureID,
.name = "GroundPlaneMaterial",
});
planeMeshComp->SetMeshID(planeMeshID);
planeMeshComp->SetMaterialID(planeMaterialID);
@@ -121,13 +122,12 @@ void LightKeeper::customInit() {
const auto CharObj = std::make_shared<GameObject>("Character");
auto charModel = ModelLoader::LoadSkinnedModel(
AssetFS::GetInstance().GetFullPath("engine://char.fbx").generic_string()
AssetFS::GetInstance().GetFullPath("engine://char2.fbx").generic_string()
);
const auto charMeshID = meshCache.addMesh(gfxDevice, charModel.meshes[0]);
const auto charTextureID = gfxDevice.loadImageFromFile(
AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
const auto charMaterialID = materialCache.addMaterial(gfxDevice, {
.baseColor = glm::vec3(1.f),
.diffuseTexture = charTextureID,
@@ -146,16 +146,16 @@ void LightKeeper::customInit() {
}
for (const auto& clip : charModel.animations)
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
if (!charModel.animations.empty())
// animator->play(charModel.animations[0].name);
// animator->play("Armature|main");
animator->play("Armature|mixamo.com");
// animator->play("Armature|Armature|mixamo.com");
// // animator->play(charModel.animations[0].name);
animator->play("Armature|Armature|Armature|main");
// or: animator->play("Run", 0.2f); // 0.2s cross-fade
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
scene.Add(CharObj);
}