Added skeletal animations and other fixes
This commit is contained in:
189
destrum/src/Components/Animator.cpp
Normal file
189
destrum/src/Components/Animator.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#include <destrum/Components/Animator.h>
|
||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||
#include <destrum/ObjectModel/GameObject.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#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
|
||||
|
||||
m_current.time += dt * m_current.speed;
|
||||
if (m_current.clip->looped)
|
||||
m_current.time = std::fmod(m_current.time, m_current.clip->duration);
|
||||
else
|
||||
m_current.time = std::min(m_current.time, m_current.clip->duration);
|
||||
|
||||
if (m_previous.clip) {
|
||||
m_previous.time += dt * m_previous.speed;
|
||||
if (m_previous.clip->looped)
|
||||
m_previous.time = std::fmod(m_previous.time, m_previous.clip->duration);
|
||||
|
||||
m_blendT += dt / m_blendDuration;
|
||||
if (m_blendT >= 1.f) {
|
||||
m_blendT = 1.f;
|
||||
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() {
|
||||
// ImGui::Text("Clip: %s", m_currentClipName.empty() ? "(none)" : m_currentClipName.c_str());
|
||||
// ImGui::Text("Time: %.2f", m_current.time);
|
||||
// ImGui::Text("Blend: %.2f", m_blendT);
|
||||
//
|
||||
// if (!isPlaying()) {
|
||||
// ImGui::BeginDisabled();
|
||||
// ImGui::Button("Stop");
|
||||
// ImGui::EndDisabled();
|
||||
// } else if (ImGui::Button("Stop")) {
|
||||
// stop();
|
||||
// }
|
||||
//
|
||||
// ImGui::Separator();
|
||||
// ImGui::Text("Clips:");
|
||||
// for (auto& [name, _] : m_clips) {
|
||||
// if (ImGui::Selectable(name.c_str(), name == m_currentClipName))
|
||||
// play(name);
|
||||
// }
|
||||
}
|
||||
|
||||
// ─── Animation control ────────────────────────────────────────────────────────
|
||||
|
||||
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
||||
m_clips[clip->name] = std::move(clip);
|
||||
}
|
||||
|
||||
void Animator::play(const std::string& name, float blendTime) {
|
||||
auto it = m_clips.find(name);
|
||||
if (it == m_clips.end()) return;
|
||||
|
||||
if (m_current.clip && blendTime > 0.f) {
|
||||
m_previous = m_current;
|
||||
m_blendT = 0.f;
|
||||
m_blendDuration = blendTime;
|
||||
} else {
|
||||
m_previous = {};
|
||||
m_blendT = 1.f;
|
||||
}
|
||||
|
||||
m_current = { it->second.get(), 0.f, 1.f };
|
||||
m_currentClipName = name;
|
||||
}
|
||||
|
||||
void Animator::stop() {
|
||||
m_current = {};
|
||||
m_previous = {};
|
||||
m_currentClipName = {};
|
||||
}
|
||||
|
||||
// ─── Joint matrix upload ──────────────────────────────────────────────────────
|
||||
|
||||
std::size_t Animator::uploadJointMatrices(SkinningPipeline& pipeline,
|
||||
const Skeleton& skeleton,
|
||||
std::size_t frameIndex) {
|
||||
auto matrices = computeJointMatrices(skeleton);
|
||||
return pipeline.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* {
|
||||
if (!clip) return nullptr;
|
||||
for (auto& t : clip->tracks)
|
||||
if (t.jointIndex == idx) return &t;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
for (std::uint32_t i = 0; i < numJoints; ++i) {
|
||||
glm::vec3 tr = {0.f, 0.f, 0.f};
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
result[i] = global[i] * skeleton.inverseBindMatrices[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─── Keyframe sampling ────────────────────────────────────────────────────────
|
||||
|
||||
glm::vec3 Animator::sampleTranslation(const SkeletalAnimation::Track& track, float t) {
|
||||
const auto& kf = track.keyframes;
|
||||
if (kf.size() == 1) return kf[0].translation;
|
||||
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
||||
if (t <= kf[i + 1].time) {
|
||||
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
||||
return glm::mix(kf[i].translation, kf[i + 1].translation, f);
|
||||
}
|
||||
}
|
||||
return kf.back().translation;
|
||||
}
|
||||
|
||||
glm::quat Animator::sampleRotation(const SkeletalAnimation::Track& track, float t) {
|
||||
const auto& kf = track.keyframes;
|
||||
if (kf.size() == 1) return kf[0].rotation;
|
||||
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
||||
if (t <= kf[i + 1].time) {
|
||||
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
||||
return glm::slerp(kf[i].rotation, kf[i + 1].rotation, f);
|
||||
}
|
||||
}
|
||||
return kf.back().rotation;
|
||||
}
|
||||
|
||||
glm::vec3 Animator::sampleScale(const SkeletalAnimation::Track& track, float t) {
|
||||
const auto& kf = track.keyframes;
|
||||
if (kf.size() == 1) return kf[0].scale;
|
||||
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
||||
if (t <= kf[i + 1].time) {
|
||||
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
||||
return glm::mix(kf[i].scale, kf[i + 1].scale, f);
|
||||
}
|
||||
}
|
||||
return kf.back().scale;
|
||||
}
|
||||
@@ -1,19 +1,56 @@
|
||||
#include <destrum/Components/MeshRendererComponent.h>
|
||||
#include <destrum/ObjectModel/Transform.h>
|
||||
|
||||
#include "destrum/Components/Animator.h"
|
||||
#include "destrum/ObjectModel/GameObject.h"
|
||||
#include "destrum/Util/GameState.h"
|
||||
|
||||
|
||||
MeshRendererComponent::MeshRendererComponent(GameObject& parent): Component(parent, "MeshRendererComponent") {
|
||||
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Start() {
|
||||
Component::Start();
|
||||
if (auto* animator = GetGameObject()->GetComponent<Animator>()) {
|
||||
const auto& gfxDevice = GameState::GetInstance().Gfx();
|
||||
const auto& mesh = GameState::GetInstance().Renderer().GetMeshCache().getMesh(meshID);
|
||||
|
||||
m_skinnedMesh = std::make_unique<SkinnedMesh>();
|
||||
m_skinnedMesh->skinnedVertexBuffer = gfxDevice.createBuffer(
|
||||
mesh.numVertices * sizeof(CPUMesh::Vertex),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Update() {
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Render(const RenderContext& ctx) {
|
||||
if (meshID != NULL_MESH_ID && materialID != NULL_MATERIAL_ID) {
|
||||
if (meshID == NULL_MESH_ID || materialID == NULL_MATERIAL_ID) return;
|
||||
|
||||
if (auto* animator = GetGameObject()->GetComponent<Animator>(); animator && m_skinnedMesh) {
|
||||
const auto& mesh = ctx.renderer.GetMeshCache().getCPUMesh(meshID);
|
||||
const auto skeleton = GetGameObject()->GetComponent<Animator>()->getSkeleton();
|
||||
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)
|
||||
frameIdx
|
||||
);
|
||||
|
||||
ctx.renderer.drawSkinnedMesh(
|
||||
meshID,
|
||||
GetTransform().GetWorldMatrix(),
|
||||
materialID,
|
||||
m_skinnedMesh.get(),
|
||||
jointMatricesStartIndex
|
||||
);
|
||||
} else {
|
||||
ctx.renderer.drawMesh(meshID, GetTransform().GetWorldMatrix(), materialID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,13 +98,13 @@ void OrbitAndSpin::Update()
|
||||
// GetTransform().SetLocalScale(glm::vec3(std::sin(m_GrowPhase)));
|
||||
|
||||
// material color
|
||||
auto& mat = GameState::GetInstance().Renderer().getMaterialMutable(m_MaterialID);
|
||||
mat.baseColor = glm::vec3(
|
||||
0.5f + 0.5f * std::sin(m_OrbitAngle * 2.0f),
|
||||
0.5f + 0.5f * std::sin(m_OrbitAngle * 3.0f + 2.0f),
|
||||
0.5f + 0.5f * std::sin(m_OrbitAngle * 4.0f + 4.0f)
|
||||
);
|
||||
GameState::GetInstance().Renderer().updateMaterialGPU(m_MaterialID);
|
||||
// auto& mat = GameState::GetInstance().Renderer().getMaterialMutable(m_MaterialID);
|
||||
// mat.baseColor = glm::vec3(
|
||||
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 2.0f),
|
||||
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 3.0f + 2.0f),
|
||||
// 0.5f + 0.5f * std::sin(m_OrbitAngle * 4.0f + 4.0f)
|
||||
// );
|
||||
// GameState::GetInstance().Renderer().updateMaterialGPU(m_MaterialID);
|
||||
}
|
||||
|
||||
void OrbitAndSpin::Start() {
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
static const std::uint32_t maxBindlessResources = 16536;
|
||||
static const std::uint32_t maxSamplers = 32;
|
||||
constexpr std::uint32_t maxBindlessResources = 16536;
|
||||
constexpr std::uint32_t maxSamplers = 32;
|
||||
|
||||
static const std::uint32_t texturesBinding = 0;
|
||||
static const std::uint32_t samplersBinding = 1;
|
||||
constexpr std::uint32_t texturesBinding = 0;
|
||||
constexpr std::uint32_t samplersBinding = 1;
|
||||
}
|
||||
|
||||
void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
@@ -26,7 +26,7 @@ void BindlessSetManager::init(VkDevice device, float maxAnisotropy)
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT,
|
||||
.maxSets = 10,
|
||||
.poolSizeCount = (std::uint32_t)poolSizesBindless.size(),
|
||||
.poolSizeCount = static_cast<std::uint32_t>(poolSizesBindless.size()),
|
||||
.pPoolSizes = poolSizesBindless.data(),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <destrum/Graphics/ComputePipeline.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "destrum/Util/GameState.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
ComputePipeline::ComputePipeline(GfxDevice& device,
|
||||
const std::string& compPath,
|
||||
const ComputePipelineConfigInfo& configInfo)
|
||||
: m_device(device) {
|
||||
CreateComputePipeline(compPath, configInfo);
|
||||
}
|
||||
|
||||
ComputePipeline::~ComputePipeline() {
|
||||
if (m_compShaderModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device.getDevice(), m_compShaderModule, nullptr);
|
||||
}
|
||||
if (m_computePipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(m_device.getDevice(), m_computePipeline, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputePipeline::bind(VkCommandBuffer buffer) const {
|
||||
vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_COMPUTE, m_computePipeline);
|
||||
}
|
||||
|
||||
void ComputePipeline::DefaultPipelineConfigInfo(ComputePipelineConfigInfo& configInfo) {
|
||||
configInfo.name = "DefaultComputePipelineConfigInfo";
|
||||
configInfo.specializationInfo = nullptr;
|
||||
configInfo.pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
std::vector<char> ComputePipeline::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 file: " + filename);
|
||||
}
|
||||
|
||||
const size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
std::vector<char> buffer(fileSize);
|
||||
|
||||
file.seekg(0);
|
||||
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
|
||||
file.close();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ComputePipeline::CreateComputePipeline(const std::string& compPath,
|
||||
const ComputePipelineConfigInfo& configInfo) {
|
||||
assert(configInfo.pipelineLayout != VK_NULL_HANDLE && "no pipelineLayout provided in configInfo");
|
||||
assert(!compPath.empty() && "Compute shader path is empty");
|
||||
|
||||
const std::string compFileName = std::filesystem::path(compPath).filename().string();
|
||||
auto compCode = readFile(compPath);
|
||||
|
||||
spdlog::debug("Compute shader code size: {}", compCode.size());
|
||||
spdlog::debug("Compute shader file: {}", compFileName);
|
||||
|
||||
CreateShaderModule(compCode, &m_compShaderModule);
|
||||
|
||||
VkPipelineShaderStageCreateInfo compStageInfo{};
|
||||
compStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
compStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
compStageInfo.module = m_compShaderModule;
|
||||
compStageInfo.pName = "main";
|
||||
compStageInfo.pSpecializationInfo = configInfo.specializationInfo;
|
||||
|
||||
VkComputePipelineCreateInfo pipelineInfo{};
|
||||
pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
pipelineInfo.stage = compStageInfo;
|
||||
pipelineInfo.layout = configInfo.pipelineLayout;
|
||||
|
||||
pipelineInfo.basePipelineIndex = -1;
|
||||
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||
|
||||
if (vkCreateComputePipelines(m_device.getDevice(),
|
||||
VK_NULL_HANDLE,
|
||||
1,
|
||||
&pipelineInfo,
|
||||
nullptr,
|
||||
&m_computePipeline) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Can't make compute pipeline!");
|
||||
}
|
||||
|
||||
if (!configInfo.name.empty()) {
|
||||
vkutil::addDebugLabel(GameState::GetInstance().Gfx().getDevice(), m_computePipeline, configInfo.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ComputePipeline::CreateShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) const {
|
||||
VkShaderModuleCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.codeSize = code.size();
|
||||
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||
|
||||
if (vkCreateShaderModule(m_device.getDevice(), &createInfo, nullptr, shaderModule) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create shader module!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
|
||||
Frustum edge::createFrustumFromCamera(const Camera& camera) {
|
||||
// TODO: write a non-horrible version of this, lol
|
||||
Frustum frustum;
|
||||
// if (camera.isOrthographic()) {
|
||||
// const auto points = calculateFrustumCornersWorldSpace(camera);
|
||||
//
|
||||
// /*
|
||||
// 5────────6
|
||||
// ╱┊ ╱│
|
||||
// ╱ ┊ ╱ │
|
||||
// 1──┼─────2 │
|
||||
// │ ┊ (C) │ │ Y ╿ . Z
|
||||
// │ 4┈┈┈┈┈│┈┈7 │ ╱
|
||||
// │ ╱ │ ╱ X │ ╱
|
||||
// │╱ │╱ ╾────┼
|
||||
// 0--------3
|
||||
//
|
||||
// */
|
||||
// // from bottom-left and moving CW...
|
||||
// static const std::array<int, 4> near{0, 1, 2, 3};
|
||||
// static const std::array<int, 4> far{7, 6, 5, 4};
|
||||
// static const std::array<int, 4> left{4, 5, 1, 0};
|
||||
// static const std::array<int, 4> right{3, 2, 6, 7};
|
||||
// static const std::array<int, 4> bottom{4, 0, 3, 7};
|
||||
// static const std::array<int, 4> top{5, 6, 2, 1};
|
||||
//
|
||||
// frustum.nearFace = {findCenter(points, near), findNormal(points, near)};
|
||||
// frustum.farFace = {findCenter(points, far), findNormal(points, far)};
|
||||
// frustum.leftFace = {findCenter(points, left), findNormal(points, left)};
|
||||
// frustum.rightFace = {findCenter(points, right), findNormal(points, right)};
|
||||
// frustum.bottomFace = {findCenter(points, bottom), findNormal(points, bottom)};
|
||||
// frustum.topFace = {findCenter(points, top), findNormal(points, top)};
|
||||
// } else {
|
||||
const auto camPos = camera.GetPosition();
|
||||
const auto camFront = camera.GetForward();
|
||||
const auto camUp = camera.GetUp();
|
||||
const auto camRight = camera.GetRight();
|
||||
|
||||
const auto zNear = camera.GetZNear();
|
||||
const auto zFar = camera.GetZFar();
|
||||
const auto halfVSide = zFar * tanf(camera.GetFOVY() * .5f);
|
||||
const auto halfHSide = halfVSide * camera.GetAspectRatio();
|
||||
const auto frontMultFar = zFar * camFront;
|
||||
|
||||
frustum.nearFace = {camPos + zNear * camFront, camFront};
|
||||
frustum.farFace = {camPos + frontMultFar, -camFront};
|
||||
frustum.leftFace = {camPos, glm::cross(camUp, frontMultFar + camRight * halfHSide)};
|
||||
frustum.rightFace = {camPos, glm::cross(frontMultFar - camRight * halfHSide, camUp)};
|
||||
frustum.bottomFace = {camPos, glm::cross(frontMultFar + camUp * halfVSide, camRight)};
|
||||
frustum.topFace = {camPos, glm::cross(camRight, frontMultFar - camUp * halfVSide)};
|
||||
// }
|
||||
|
||||
return frustum;
|
||||
}
|
||||
|
||||
bool isOnOrForwardPlane(const Frustum::Plane& plane, const Sphere& sphere)
|
||||
{
|
||||
return plane.getSignedDistanceToPlane(sphere.center) > -sphere.radius;
|
||||
}
|
||||
|
||||
bool edge::isInFrustum(const Frustum& frustum, const Sphere& s) {
|
||||
return (
|
||||
isOnOrForwardPlane(frustum.farFace, s) && isOnOrForwardPlane(frustum.nearFace, s) &&
|
||||
isOnOrForwardPlane(frustum.leftFace, s) && isOnOrForwardPlane(frustum.rightFace, s) &&
|
||||
isOnOrForwardPlane(frustum.topFace, s) && isOnOrForwardPlane(frustum.bottomFace, s));
|
||||
}
|
||||
|
||||
bool edge::isInFrustum(const Frustum& frustum, const AABB& aabb) {
|
||||
glm::vec3 vmin, vmax;
|
||||
bool ret = true;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
const auto& plane = frustum.getPlane(i);
|
||||
// X axis
|
||||
if (plane.normal.x < 0) {
|
||||
vmin.x = aabb.min.x;
|
||||
vmax.x = aabb.max.x;
|
||||
} else {
|
||||
vmin.x = aabb.max.x;
|
||||
vmax.x = aabb.min.x;
|
||||
}
|
||||
// Y axis
|
||||
if (plane.normal.y < 0) {
|
||||
vmin.y = aabb.min.y;
|
||||
vmax.y = aabb.max.y;
|
||||
} else {
|
||||
vmin.y = aabb.max.y;
|
||||
vmax.y = aabb.min.y;
|
||||
}
|
||||
// Z axis
|
||||
if (plane.normal.z < 0) {
|
||||
vmin.z = aabb.min.z;
|
||||
vmax.z = aabb.max.z;
|
||||
} else {
|
||||
vmin.z = aabb.max.z;
|
||||
vmax.z = aabb.min.z;
|
||||
}
|
||||
if (plane.getSignedDistanceToPlane(vmin) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (plane.getSignedDistanceToPlane(vmax) <= 0) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
glm::vec3 getTransformScale(const glm::mat4& transform)
|
||||
{
|
||||
float sx = glm::length(glm::vec3{transform[0][0], transform[0][1], transform[0][2]});
|
||||
float sy = glm::length(glm::vec3{transform[1][0], transform[1][1], transform[1][2]});
|
||||
float sz = glm::length(glm::vec3{transform[2][0], transform[2][1], transform[2][2]});
|
||||
return {sx, sy, sz};
|
||||
}
|
||||
|
||||
Sphere edge::calculateBoundingSphereWorld(const glm::mat4& transform, const Sphere& s, bool hasSkeleton) {
|
||||
const auto scale = getTransformScale(transform);
|
||||
float maxScale = std::max({scale.x, scale.y, scale.z});
|
||||
if (hasSkeleton) {
|
||||
maxScale = 5.f; // ignore scale for skeleton meshes (TODO: fix)
|
||||
// setting scale to 1.f causes prolems with frustum culling
|
||||
}
|
||||
auto sphereWorld = s;
|
||||
sphereWorld.radius *= maxScale;
|
||||
sphereWorld.center = glm::vec3(transform * glm::vec4(sphereWorld.center, 1.f));
|
||||
return sphereWorld;
|
||||
}
|
||||
|
||||
@@ -129,18 +129,36 @@ void GfxDevice::init(SDL_Window* window, const std::string& appName, bool vSync)
|
||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||
}
|
||||
//
|
||||
// { // create white texture
|
||||
// std::uint32_t pixel = 0xFFFFFFFF;
|
||||
// whiteImageId = createImage(
|
||||
// {
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
// .extent = VkExtent3D{1, 1, 1},
|
||||
// },
|
||||
// "white texture",
|
||||
// &pixel);
|
||||
// }
|
||||
|
||||
{ // create white texture
|
||||
std::uint32_t pixel = 0xFFFFFFFF;
|
||||
whiteImageId = createImage(
|
||||
{
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
.extent = VkExtent3D{1, 1, 1},
|
||||
},
|
||||
"white texture",
|
||||
&pixel);
|
||||
}
|
||||
|
||||
{ // create error texture (black/magenta checker)
|
||||
constexpr auto black = 0xFF000000;
|
||||
constexpr auto magenta = 0xFFFF00FF;
|
||||
|
||||
std::array<std::uint32_t, 4> pixels{black, magenta, magenta, black};
|
||||
errorImageId = createImage(
|
||||
{
|
||||
.format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
||||
.extent = VkExtent3D{2, 2, 1},
|
||||
},
|
||||
"error texture",
|
||||
pixels.data());
|
||||
imageCache.setErrorImageId(errorImageId);
|
||||
}
|
||||
|
||||
GameState::GetInstance().SetGfxDevice(this);
|
||||
}
|
||||
|
||||
@@ -495,25 +513,14 @@ GPUImage GfxDevice::createImageRaw(
|
||||
return image;
|
||||
}
|
||||
|
||||
GPUImage GfxDevice::loadImageFromFileRaw(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent) const {
|
||||
// 1) Decode file to CPU pixels (stb for png/jpg/hdr, tinyexr for exr)
|
||||
// IMPORTANT: util::loadImage should fill:
|
||||
// - width/height/channels(=4)
|
||||
// - hdr + (pixels OR hdrPixels)
|
||||
// - vkFormat (RGBA8_SRGB or RGBA8_UNORM or RGBA32F)
|
||||
// - byteSize (exact bytes to upload)
|
||||
GPUImage GfxDevice::loadImageFromFileRaw(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap, TextureIntent intent) const {
|
||||
const auto data = util::loadImage(path, intent);
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0 ||
|
||||
(data.hdr ? (data.hdrPixels == nullptr) : (data.pixels == nullptr))) {
|
||||
|
||||
if (data.vkFormat == VK_FORMAT_UNDEFINED || data.byteSize == 0 || (data.hdr ? (data.hdrPixels == nullptr) : (data.pixels == nullptr))) {
|
||||
spdlog::error("Failed to load image '{}'", path.string());
|
||||
return getImage(errorImageId);
|
||||
}
|
||||
|
||||
// 2) Create GPU image using the format the loader chose (matches CPU memory layout)
|
||||
auto image = createImageRaw({
|
||||
.format = data.vkFormat,
|
||||
.usage = usage |
|
||||
@@ -527,15 +534,10 @@ GPUImage GfxDevice::loadImageFromFileRaw(
|
||||
.mipMap = mipMap,
|
||||
});
|
||||
|
||||
// 3) Upload *exactly* byteSize bytes from the correct pointer
|
||||
const void* src = data.hdr
|
||||
? static_cast<const void*>(data.hdrPixels)
|
||||
: static_cast<const void*>(data.pixels);
|
||||
const void* src = data.hdr ? static_cast<const void*>(data.hdrPixels) : static_cast<const void*>(data.pixels);
|
||||
|
||||
// Use the "sized" upload to avoid BytesPerTexel mismatches
|
||||
uploadImageDataSized(image, src, data.byteSize, 0);
|
||||
|
||||
// 4) Debug label
|
||||
image.debugName = path.string();
|
||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
||||
|
||||
|
||||
@@ -2,21 +2,12 @@
|
||||
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
|
||||
ImageCache::ImageCache(GfxDevice& gfxDevice) : gfxDevice(gfxDevice)
|
||||
{}
|
||||
ImageCache::ImageCache(GfxDevice& gfxDevice) : gfxDevice(gfxDevice) {
|
||||
}
|
||||
|
||||
ImageID ImageCache::loadImageFromFile(
|
||||
const std::filesystem::path& path,
|
||||
VkImageUsageFlags usage,
|
||||
bool mipMap,
|
||||
TextureIntent intent)
|
||||
{
|
||||
for (const auto& [id, info] : loadedImagesInfo) {
|
||||
if (info.path == path &&
|
||||
info.intent == intent &&
|
||||
info.usage == usage &&
|
||||
info.mipMap == mipMap)
|
||||
{
|
||||
ImageID ImageCache::loadImageFromFile(const std::filesystem::path& path, VkImageUsageFlags usage, bool mipMap, TextureIntent intent) {
|
||||
for (const auto& [id, info]: loadedImagesInfo) {
|
||||
if (info.path == path && info.intent == intent && info.usage == usage && info.mipMap == mipMap) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -41,13 +32,11 @@ ImageID ImageCache::loadImageFromFile(
|
||||
return id;
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(GPUImage image)
|
||||
{
|
||||
ImageID ImageCache::addImage(GPUImage image) {
|
||||
return addImage(getFreeImageId(), std::move(image));
|
||||
}
|
||||
|
||||
ImageID ImageCache::addImage(ImageID id, GPUImage image)
|
||||
{
|
||||
ImageID ImageCache::addImage(ImageID id, GPUImage image) {
|
||||
image.setBindlessId(static_cast<std::uint32_t>(id));
|
||||
if (id != images.size()) {
|
||||
images[id] = std::move(image); // replacing existing image
|
||||
@@ -59,20 +48,17 @@ ImageID ImageCache::addImage(ImageID id, GPUImage image)
|
||||
return id;
|
||||
}
|
||||
|
||||
const GPUImage& ImageCache::getImage(ImageID id) const
|
||||
{
|
||||
const GPUImage& ImageCache::getImage(ImageID id) const {
|
||||
assert(id != NULL_IMAGE_ID && id < images.size());
|
||||
return images.at(id);
|
||||
}
|
||||
|
||||
ImageID ImageCache::getFreeImageId() const
|
||||
{
|
||||
ImageID ImageCache::getFreeImageId() const {
|
||||
return images.size();
|
||||
}
|
||||
|
||||
void ImageCache::destroyImages()
|
||||
{
|
||||
for (const auto& image : images) {
|
||||
void ImageCache::destroyImages() {
|
||||
for (const auto& image: images) {
|
||||
gfxDevice.destroyImage(image);
|
||||
}
|
||||
images.clear();
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace util
|
||||
{
|
||||
ImageData data;
|
||||
|
||||
|
||||
|
||||
// ---------- EXR ----------
|
||||
if (isExrExt(p)) {
|
||||
float* out = nullptr;
|
||||
|
||||
@@ -24,7 +24,7 @@ void MaterialCache::init(GfxDevice& gfxDevice)
|
||||
&normal);
|
||||
}
|
||||
|
||||
Material placeholderMaterial{.name = "PLACEHOLDER_MATERIAL"};
|
||||
Material placeholderMaterial{.diffuseTexture = defaultNormalMapTextureID, .name = "PLACEHOLDER_MATERIAL"};
|
||||
placeholderMaterialId = addMaterial(gfxDevice, placeholderMaterial);
|
||||
}
|
||||
|
||||
@@ -41,13 +41,14 @@ MaterialID MaterialCache::addMaterial(GfxDevice& gfxDevice, Material material)
|
||||
};
|
||||
|
||||
// store on GPU
|
||||
MaterialData* data = (MaterialData*)materialDataBuffer.info.pMappedData;
|
||||
auto whiteTextureID = gfxDevice.getWhiteTextureID();
|
||||
auto id = getFreeMaterialId();
|
||||
MaterialData* data = static_cast<MaterialData*>(materialDataBuffer.info.pMappedData);
|
||||
const auto whiteTextureID = gfxDevice.getWhiteTextureID();
|
||||
const auto id = getFreeMaterialId();
|
||||
assert(id < MAX_MATERIALS);
|
||||
data[id] = MaterialData{
|
||||
.baseColor = glm::vec4(material.baseColor, 1.0f),
|
||||
.metalRoughnessEmissive = glm::vec4{material.metallicFactor, material.roughnessFactor, material.emissiveFactor, 0.f},
|
||||
.textureFilteringMode = static_cast<std::uint32_t>(material.textureFilteringMode),
|
||||
.diffuseTex = getTextureOrElse(material.diffuseTexture, whiteTextureID),
|
||||
.normalTex = whiteTextureID,
|
||||
.metallicRoughnessTex = whiteTextureID,
|
||||
@@ -60,6 +61,15 @@ MaterialID MaterialCache::addMaterial(GfxDevice& gfxDevice, Material material)
|
||||
return id;
|
||||
}
|
||||
|
||||
MaterialID MaterialCache::addSimpleTextureMaterial(GfxDevice& gfxDevice, ImageID textureID) {
|
||||
Material material{};
|
||||
material.name = "idk";
|
||||
material.diffuseTexture = textureID;
|
||||
material.metallicFactor = 0.0f;
|
||||
material.roughnessFactor = 1.0f;
|
||||
return addMaterial(gfxDevice, material);
|
||||
}
|
||||
|
||||
const Material& MaterialCache::getMaterial(MaterialID id) const
|
||||
{
|
||||
return materials.at(id);
|
||||
@@ -100,7 +110,7 @@ void MaterialCache::updateMaterialGPU(GfxDevice& gfxDevice, MaterialID id)
|
||||
.baseColor = glm::vec4(material.baseColor, 1.0f),
|
||||
.metalRoughnessEmissive = glm::vec4(material.metallicFactor, material.roughnessFactor, material.emissiveFactor, 0.f),
|
||||
.diffuseTex = getTextureOrElse(material.diffuseTexture, whiteTextureID),
|
||||
.normalTex = whiteTextureID, // if you have this field
|
||||
.normalTex = whiteTextureID,
|
||||
.metallicRoughnessTex = whiteTextureID,
|
||||
.emissiveTex = whiteTextureID,
|
||||
};
|
||||
|
||||
@@ -3,25 +3,30 @@
|
||||
#include <destrum/Graphics/Resources/Mesh.h>
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include <destrum/Util/MathUtils.h>
|
||||
// #include <destrum/Math/Util.h>
|
||||
|
||||
MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
||||
{
|
||||
auto gpuMesh = GPUMesh{
|
||||
.numVertices = (std::uint32_t)cpuMesh.vertices.size(),
|
||||
.numIndices = (std::uint32_t)cpuMesh.indices.size(),
|
||||
.minPos = cpuMesh.minPos,
|
||||
.maxPos = cpuMesh.maxPos,
|
||||
.numIndices = (std::uint32_t)cpuMesh.indices.size(),
|
||||
.minPos = cpuMesh.minPos,
|
||||
.maxPos = cpuMesh.maxPos,
|
||||
.hasSkeleton = cpuMesh.skeleton.has_value(),
|
||||
};
|
||||
|
||||
std::vector<glm::vec3> positions(cpuMesh.vertices.size());
|
||||
for (std::size_t i = 0; i < cpuMesh.vertices.size(); ++i) {
|
||||
for (std::size_t i = 0; i < cpuMesh.vertices.size(); ++i)
|
||||
positions[i] = cpuMesh.vertices[i].position;
|
||||
}
|
||||
gpuMesh.boundingSphere = calculateBoundingSphere(positions);
|
||||
|
||||
uploadMesh(gfxDevice, cpuMesh, gpuMesh);
|
||||
|
||||
const auto id = meshes.size();
|
||||
meshes.push_back(std::move(gpuMesh));
|
||||
cpuMeshes.push_back(cpuMesh); // store a copy of the CPU mesh
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -70,6 +75,33 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
||||
const auto idxBufferName = cpuMesh.name + " (idx)";
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.vertexBuffer.buffer, vtxBufferName.c_str());
|
||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.indexBuffer.buffer, idxBufferName.c_str());
|
||||
|
||||
if (gpuMesh.hasSkeleton) {
|
||||
// create skinning data buffer
|
||||
const auto skinningDataSize = cpuMesh.vertices.size() * sizeof(CPUMesh::SkinningData);
|
||||
gpuMesh.skinningDataBuffer = gfxDevice.createBuffer(
|
||||
skinningDataSize,
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
|
||||
const auto staging =
|
||||
gfxDevice.createBuffer(skinningDataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
|
||||
// copy data
|
||||
void* data = staging.info.pMappedData;
|
||||
memcpy(data, cpuMesh.skinningData.data(), skinningDataSize);
|
||||
|
||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||
const auto vertexCopy = VkBufferCopy{
|
||||
.srcOffset = 0,
|
||||
.dstOffset = 0,
|
||||
.size = skinningDataSize,
|
||||
};
|
||||
vkCmdCopyBuffer(cmd, staging.buffer, gpuMesh.skinningDataBuffer.buffer, 1, &vertexCopy);
|
||||
});
|
||||
|
||||
gfxDevice.destroyBuffer(staging);
|
||||
}
|
||||
}
|
||||
|
||||
const GPUMesh& MeshCache::getMesh(MeshID id) const
|
||||
@@ -77,6 +109,11 @@ const GPUMesh& MeshCache::getMesh(MeshID id) const
|
||||
return meshes.at(id);
|
||||
}
|
||||
|
||||
const CPUMesh& MeshCache::getCPUMesh(MeshID id) const
|
||||
{
|
||||
return cpuMeshes.at(id);
|
||||
}
|
||||
|
||||
void MeshCache::cleanup(const GfxDevice& gfxDevice)
|
||||
{
|
||||
for (const auto& mesh : meshes) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
||||
#include <destrum/FS/AssetFS.h>
|
||||
|
||||
#include "destrum/Graphics/Frustum.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
MeshPipeline::MeshPipeline(): m_pipelineLayout{nullptr} {
|
||||
}
|
||||
|
||||
@@ -68,6 +71,8 @@ void MeshPipeline::draw(VkCommandBuffer cmd,
|
||||
m_pipeline->bind(cmd);
|
||||
gfxDevice.bindBindlessDescSet(cmd, m_pipelineLayout);
|
||||
|
||||
int ActualDrawCalls = 0;
|
||||
|
||||
const auto viewport = VkViewport{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@@ -86,16 +91,17 @@ void MeshPipeline::draw(VkCommandBuffer cmd,
|
||||
|
||||
auto prevMeshId = NULL_MESH_ID;
|
||||
|
||||
// const auto frustum = edge::createFrustumFromCamera(camera);
|
||||
const auto frustum = edge::createFrustumFromCamera(camera);
|
||||
|
||||
for (const auto& dcIdx : drawCommands) {
|
||||
// const auto& dc = drawCommands[dcIdx];
|
||||
const auto& dc = dcIdx;
|
||||
|
||||
// if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
|
||||
// continue;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
ActualDrawCalls++;
|
||||
|
||||
const auto& mesh = meshCache.getMesh(dc.meshId);
|
||||
if (dc.meshId != prevMeshId) {
|
||||
prevMeshId = dc.meshId;
|
||||
@@ -106,7 +112,7 @@ void MeshPipeline::draw(VkCommandBuffer cmd,
|
||||
const auto pushConstants = PushConstants{
|
||||
.transform = dc.transformMatrix,
|
||||
.sceneDataBuffer = sceneDataBuffer.address,
|
||||
.vertexBuffer = mesh.vertexBuffer.address,
|
||||
.vertexBuffer = dc.skinnedMesh != nullptr ? dc.skinnedMesh->skinnedVertexBuffer.address : mesh.vertexBuffer.address,
|
||||
.materialId = dc.materialId,
|
||||
};
|
||||
vkCmdPushConstants(
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||
|
||||
#include "destrum/FS/AssetFS.h"
|
||||
#include "destrum/Graphics/MeshCache.h"
|
||||
#include "destrum/Graphics/MeshDrawCommand.h"
|
||||
|
||||
void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
||||
const auto& device = gfxDevice.getDevice();
|
||||
|
||||
const auto pushConstant = VkPushConstantRange{
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.offset = 0,
|
||||
.size = sizeof(PushConstants),
|
||||
};
|
||||
|
||||
const auto pushConstants = std::array{pushConstant};
|
||||
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = pushConstants.data();
|
||||
|
||||
if (vkCreatePipelineLayout(gfxDevice.getDevice().device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Could not make pipleine layout");
|
||||
}
|
||||
|
||||
|
||||
ComputePipelineConfigInfo pipelineConfig{};
|
||||
pipelineConfig.name = "skinning compute pipeline";
|
||||
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||
|
||||
const auto SkinningShaderPath = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/skinning.comp");
|
||||
|
||||
skinningPipeline = std::make_unique<ComputePipeline>(
|
||||
gfxDevice,
|
||||
SkinningShaderPath.generic_string(),
|
||||
pipelineConfig
|
||||
);
|
||||
|
||||
|
||||
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||
auto& jointMatricesBuffer = framesData[i].jointMatricesBuffer;
|
||||
jointMatricesBuffer.capacity = MAX_JOINT_MATRICES;
|
||||
jointMatricesBuffer.buffer = gfxDevice.createBuffer(
|
||||
MAX_JOINT_MATRICES * sizeof(glm::mat4),
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void SkinningPipeline::cleanup(GfxDevice& gfxDevice) {
|
||||
for (auto& frame : framesData) {
|
||||
gfxDevice.destroyBuffer(frame.jointMatricesBuffer.buffer);
|
||||
}
|
||||
vkDestroyPipelineLayout(gfxDevice.getDevice().device, m_pipelineLayout, nullptr);
|
||||
skinningPipeline.reset();
|
||||
}
|
||||
|
||||
void SkinningPipeline::doSkinning(VkCommandBuffer cmd, std::size_t frameIndex, const MeshCache& meshCache, const MeshDrawCommand& dc) {
|
||||
|
||||
skinningPipeline->bind(cmd);
|
||||
|
||||
const auto& mesh = meshCache.getMesh(dc.meshId);
|
||||
assert(mesh.hasSkeleton);
|
||||
assert(dc.skinnedMesh);
|
||||
|
||||
const auto cs = PushConstants{
|
||||
.jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer.buffer.address,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(dc.jointMatricesStartIndex), // explicit cast
|
||||
.numVertices = mesh.numVertices,
|
||||
.inputBuffer = mesh.vertexBuffer.address,
|
||||
.skinningData = mesh.skinningDataBuffer.address,
|
||||
.outputBuffer = dc.skinnedMesh->skinnedVertexBuffer.address,
|
||||
};
|
||||
vkCmdPushConstants(cmd, m_pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(PushConstants), &cs);
|
||||
|
||||
static const auto workgroupSize = 256;
|
||||
// const auto groupSizeX = (std::uint32_t)std::ceil(mesh.numVertices / (float)workgroupSize);
|
||||
const auto groupSizeX = static_cast<std::uint32_t>(
|
||||
std::ceil(mesh.numVertices / (float)workgroupSize));
|
||||
vkCmdDispatch(cmd, groupSizeX, 1, 1);
|
||||
}
|
||||
|
||||
void SkinningPipeline::beginDrawing(std::size_t frameIndex) {
|
||||
getCurrentFrameData(frameIndex).jointMatricesBuffer.clear();
|
||||
}
|
||||
|
||||
std::size_t SkinningPipeline::appendJointMatrices(std::span<const glm::mat4> jointMatrices, std::size_t frameIndex) {
|
||||
|
||||
auto& jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer;
|
||||
const auto startIndex = jointMatricesBuffer.size;
|
||||
jointMatricesBuffer.append(jointMatrices);
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
SkinningPipeline::PerFrameData& SkinningPipeline::getCurrentFrameData(std::size_t frameIndex) {
|
||||
return framesData[frameIndex % FRAMES_IN_FLIGHT];
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ void GameRenderer::init(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize) {
|
||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||
skyboxPipeline->init(gfxDevice, drawImageFormat, depthImageFormat);
|
||||
|
||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||
skinningPipeline->init(gfxDevice);
|
||||
|
||||
|
||||
GameState::GetInstance().SetRenderer(this);
|
||||
}
|
||||
@@ -30,6 +33,7 @@ void GameRenderer::init(GfxDevice& gfxDevice, const glm::ivec2& drawImageSize) {
|
||||
void GameRenderer::beginDrawing(GfxDevice& gfxDevice) {
|
||||
flushMaterialUpdates(gfxDevice);
|
||||
meshDrawCommands.clear();
|
||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||
}
|
||||
|
||||
void GameRenderer::endDrawing() {
|
||||
@@ -37,6 +41,13 @@ void GameRenderer::endDrawing() {
|
||||
}
|
||||
|
||||
void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& camera, const SceneData& sceneData) {
|
||||
|
||||
for (const auto& dc : meshDrawCommands) {
|
||||
if (!dc.skinnedMesh) {
|
||||
continue;
|
||||
}
|
||||
skinningPipeline->doSkinning(cmd, gfxDevice.getCurrentFrameIndex(), meshCache, dc);
|
||||
}
|
||||
const auto gpuSceneData = GPUSceneData{
|
||||
.view = sceneData.camera.GetViewMatrix(),
|
||||
.proj = sceneData.camera.GetProjectionMatrix(),
|
||||
@@ -107,7 +118,7 @@ void GameRenderer::cleanup(VkDevice device) {
|
||||
|
||||
void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId) {
|
||||
const auto& mesh = meshCache.getMesh(id);
|
||||
// const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
assert(materialId != NULL_MATERIAL_ID);
|
||||
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
@@ -117,6 +128,25 @@ void GameRenderer::drawMesh(MeshID id, const glm::mat4& transform, MaterialID ma
|
||||
});
|
||||
}
|
||||
|
||||
void GameRenderer::drawSkinnedMesh(MeshID id,
|
||||
const glm::mat4& transform,
|
||||
MaterialID materialId,
|
||||
SkinnedMesh* skinnedMesh,
|
||||
std::size_t jointMatricesStartIndex) {
|
||||
const auto& mesh = meshCache.getMesh(id);
|
||||
const auto worldBoundingSphere = edge::calculateBoundingSphereWorld(transform, mesh.boundingSphere, false);
|
||||
assert(materialId != NULL_MATERIAL_ID);
|
||||
assert(skinnedMesh != nullptr);
|
||||
|
||||
meshDrawCommands.push_back(MeshDrawCommand{
|
||||
.meshId = id,
|
||||
.transformMatrix = transform,
|
||||
.materialId = materialId,
|
||||
.skinnedMesh = skinnedMesh,
|
||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(jointMatricesStartIndex),
|
||||
});
|
||||
}
|
||||
|
||||
const GPUImage& GameRenderer::getDrawImage(const GfxDevice& gfx_device) const {
|
||||
return gfx_device.getImage(drawImageId);
|
||||
}
|
||||
|
||||
@@ -122,6 +122,16 @@ void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const c
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_PIPELINE,
|
||||
.objectHandle = (std::uint64_t)pipeline,
|
||||
.pObjectName = label,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||
}
|
||||
|
||||
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
|
||||
Reference in New Issue
Block a user