From 6c1852299da2fe53ed57682b0b58532f3c727571 Mon Sep 17 00:00:00 2001 From: Lynix Date: Sat, 21 Jun 2014 16:23:38 +0200 Subject: [PATCH] Added SkinningManager Former-commit-id: ca3648dc16f5b1654cb8e3f5480d04f7fd22a3bc --- include/Nazara/Graphics/SkinningManager.hpp | 36 +++ src/Nazara/Graphics/Graphics.cpp | 13 +- src/Nazara/Graphics/Scene.cpp | 3 + src/Nazara/Graphics/SkinningManager.cpp | 237 ++++++++++++++++++++ 4 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 include/Nazara/Graphics/SkinningManager.hpp create mode 100644 src/Nazara/Graphics/SkinningManager.cpp diff --git a/include/Nazara/Graphics/SkinningManager.hpp b/include/Nazara/Graphics/SkinningManager.hpp new file mode 100644 index 000000000..5b015c066 --- /dev/null +++ b/include/Nazara/Graphics/SkinningManager.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_SKINNINGMANAGER_HPP +#define NAZARA_SKINNINGMANAGER_HPP + +#include + +class NzSkeleton; +class NzSkeletalMesh; +class NzVertexBuffer; + +class NAZARA_API NzSkinningManager +{ + friend class NzGraphics; + + public: + using SkinFunction = void (*)(const NzSkeletalMesh* mesh, const NzSkeleton* skeleton, NzVertexBuffer* buffer); + + NzSkinningManager() = delete; + ~NzSkinningManager() = delete; + + static NzVertexBuffer* GetBuffer(const NzSkeletalMesh* mesh, const NzSkeleton* skeleton); + static void Skin(); + + private: + static bool Initialize(); + static void Uninitialize(); + + static SkinFunction s_skinFunc; +}; + +#endif // NAZARA_SKINNINGMANAGER_HPP diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index 333162fb1..cc23fa2a6 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,13 @@ bool NzGraphics::Initialize() if (!NzMaterial::Initialize()) { - NazaraError("Failed to create material"); + NazaraError("Failed to initialize materials"); + return false; + } + + if (!NzSkinningManager::Initialize()) + { + NazaraError("Failed to initialize skinning cache"); return false; } @@ -89,9 +96,9 @@ void NzGraphics::Uninitialize() NzLoaders_OBJ_Unregister(); NzLoaders_Texture_Unregister(); - NzMaterial::Uninitialize(); - NzDeferredRenderTechnique::Uninitialize(); + NzMaterial::Uninitialize(); + NzSkinningManager::Uninitialize(); NazaraNotice("Uninitialized: Graphics module"); diff --git a/src/Nazara/Graphics/Scene.cpp b/src/Nazara/Graphics/Scene.cpp index 1c9a438e2..15d7a5d4a 100644 --- a/src/Nazara/Graphics/Scene.cpp +++ b/src/Nazara/Graphics/Scene.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,8 @@ void NzScene::Update() void NzScene::UpdateVisible() { + NzSkinningManager::Skin(); + if (m_impl->update) { for (NzUpdatable* node : m_impl->visibleUpdateList) diff --git a/src/Nazara/Graphics/SkinningManager.cpp b/src/Nazara/Graphics/SkinningManager.cpp new file mode 100644 index 000000000..9a756ea03 --- /dev/null +++ b/src/Nazara/Graphics/SkinningManager.cpp @@ -0,0 +1,237 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + enum ResourceType + { + ResourceType_SkeletalMesh, + ResourceType_Skeleton, + }; + + struct BufferData + { + NzVertexBufferRef buffer; + bool updated; + }; + + struct SkinningData + { + const NzSkeletalMesh* mesh; + const NzSkeleton* skeleton; + NzVertexBuffer* buffer; + }; + + using MeshMap = std::unordered_map; + using SkeletonMap = std::unordered_map; + SkeletonMap s_cache; + std::vector s_skinningQueue; + + class ResourceListener : public NzResourceListener + { + public: + bool OnResourceDestroy(const NzResource* resource, int index) + { + switch (index) + { + case ResourceType_SkeletalMesh: + { + for (auto& pair : s_cache) + { + MeshMap& meshMap = pair.second; + meshMap.erase(static_cast(resource)); + } + break; + } + + case ResourceType_Skeleton: + s_cache.erase(static_cast(resource)); + break; + } + + return false; + } + + bool OnResourceModified(const NzResource* resource, int index, unsigned int code) + { + NazaraUnused(code); + + switch (index) + { + case ResourceType_SkeletalMesh: + { + for (auto& pair : s_cache) + { + MeshMap& meshMap = pair.second; + for (auto& pair2 : meshMap) + pair2.second.updated = false; + } + break; + } + + case ResourceType_Skeleton: + { + for (auto& pair : s_cache.at(static_cast(resource))) + pair.second.updated = false; + break; + } + } + + return true; + } + + void OnResourceReleased(const NzResource* resource, int index) + { + OnResourceDestroy(resource, index); + } + }; + + ResourceListener listener; + + void Skin_MonoCPU(const NzSkeletalMesh* mesh, const NzSkeleton* skeleton, NzVertexBuffer* buffer) + { + NzBufferMapper mapper(buffer, nzBufferAccess_DiscardAndWrite); + + NzSkinningData skinningData; + skinningData.inputVertex = mesh->GetBindPoseBuffer(); + skinningData.outputVertex = static_cast(mapper.GetPointer()); + skinningData.joints = skeleton->GetJoints(); + skinningData.vertexWeights = mesh->GetVertexWeight(0); + skinningData.weights = mesh->GetWeight(0); + + NzSkinPositionNormalTangent(skinningData, 0, mesh->GetVertexCount()); + } + + void Skin_MultiCPU(const NzSkeletalMesh* mesh, const NzSkeleton* skeleton, NzVertexBuffer* buffer) + { + NzBufferMapper mapper(buffer, nzBufferAccess_DiscardAndWrite); + + NzSkinningData skinningData; + skinningData.inputVertex = mesh->GetBindPoseBuffer(); + skinningData.outputVertex = static_cast(mapper.GetPointer()); + skinningData.joints = skeleton->GetJoints(); + skinningData.vertexWeights = mesh->GetVertexWeight(0); + skinningData.weights = mesh->GetWeight(0); + + // Afin d'empêcher les différents threads de vouloir mettre à jour la même matrice en même temps, + // on se charge de la mettre à jour avant de les lancer + unsigned int jointCount = skeleton->GetJointCount(); + for (unsigned int i = 0; i < jointCount; ++i) + skinningData.joints[i].EnsureSkinningMatrixUpdate(); + + unsigned int workerCount = NzTaskScheduler::GetWorkerCount(); + + std::ldiv_t div = std::ldiv(mesh->GetVertexCount(), workerCount); + for (unsigned int i = 0; i < workerCount; ++i) + NzTaskScheduler::AddTask(NzSkinPositionNormalTangent, skinningData, i*div.quot, (i == workerCount-1) ? div.quot + div.rem : div.quot); + + NzTaskScheduler::Run(); + NzTaskScheduler::WaitForTasks(); + } +} + +NzVertexBuffer* NzSkinningManager::GetBuffer(const NzSkeletalMesh* mesh, const NzSkeleton* skeleton) +{ + #if NAZARA_GRAPHICS_SAFE + if (!mesh) + { + NazaraError("Invalid mesh"); + return nullptr; + } + + if (!skeleton) + { + NazaraError("Invalid skeleton"); + return nullptr; + } + #endif + + NzErrorFlags flags(nzErrorFlag_ThrowException); + + SkeletonMap::iterator it = s_cache.find(skeleton); + if (it == s_cache.end()) + { + it = s_cache.insert(std::make_pair(skeleton, SkeletonMap::mapped_type())).first; + skeleton->AddResourceListener(&listener, ResourceType_Skeleton); + } + + NzVertexBuffer* buffer; + + MeshMap& meshMap = it->second; + MeshMap::iterator it2 = meshMap.find(mesh); + if (it2 == meshMap.end()) + { + std::unique_ptr vertexBuffer(new NzVertexBuffer); + vertexBuffer->SetPersistent(false); + vertexBuffer->Reset(NzVertexDeclaration::Get(nzVertexLayout_XYZ_Normal_UV_Tangent), mesh->GetVertexCount(), nzBufferStorage_Hardware, nzBufferUsage_Dynamic); + + BufferData data({vertexBuffer.get(), true}); + meshMap.insert(std::make_pair(mesh, data)); + + mesh->AddResourceListener(&listener, ResourceType_SkeletalMesh); + + s_skinningQueue.push_back(SkinningData{mesh, skeleton, vertexBuffer.get()}); + + buffer = vertexBuffer.release(); + } + else + { + BufferData& data = it2->second; + if (!data.updated) + { + s_skinningQueue.push_back(SkinningData{mesh, skeleton, data.buffer}); + data.updated = true; + } + + buffer = data.buffer; + } + + return buffer; +} + +void NzSkinningManager::Skin() +{ + for (SkinningData& data : s_skinningQueue) + s_skinFunc(data.mesh, data.skeleton, data.buffer); + + s_skinningQueue.clear(); +} + +bool NzSkinningManager::Initialize() +{ + ///TODO: GPU Skinning + if (NzTaskScheduler::Initialize()) + s_skinFunc = Skin_MultiCPU; + else + s_skinFunc = Skin_MonoCPU; + + return true; // Rien de particulier à faire +} + +void NzSkinningManager::Uninitialize() +{ + for (auto& pair : s_cache) + { + pair.first->RemoveResourceListener(&listener); + MeshMap& meshMap = pair.second; + for (auto& pair2 : meshMap) + pair2.first->RemoveResourceListener(&listener); + } + s_cache.clear(); + s_skinningQueue.clear(); +} + +NzSkinningManager::SkinFunction NzSkinningManager::s_skinFunc = nullptr;