From 9688c091aff3da9cf72f7e1ea4b1366745c879a9 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 28 Apr 2022 17:50:05 -0400 Subject: [PATCH] Add bone editing to mdlviewer This is big, as it shows we are now correctly parsing the havok XML sidecard data and you can edit the scale of the bones in the viewport. This also pulls in a new libxiv version, which is required to fill out the used bones list on a Model. Right now the bone editing is incredibly basic, and the viewport suffers from a lack of depth testing still. --- mdlviewer/CMakeLists.txt | 4 +- mdlviewer/include/mainwindow.h | 5 ++ mdlviewer/include/vec3edit.h | 23 +++++++ mdlviewer/src/mainwindow.cpp | 89 +++++++++++++++++++++++++++ mdlviewer/src/vec3edit.cpp | 58 ++++++++++++++++++ renderer/include/renderer.hpp | 13 ++++ renderer/shaders/mesh.vert | 25 ++++++-- renderer/shaders/mesh.vert.spv | Bin 1392 -> 4520 bytes renderer/src/renderer.cpp | 108 +++++++++++++++++++++++++++++++-- 9 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 mdlviewer/include/vec3edit.h create mode 100644 mdlviewer/src/vec3edit.cpp diff --git a/mdlviewer/CMakeLists.txt b/mdlviewer/CMakeLists.txt index 9eb3820..520b665 100644 --- a/mdlviewer/CMakeLists.txt +++ b/mdlviewer/CMakeLists.txt @@ -2,7 +2,9 @@ find_package(assimp REQUIRED) add_executable(mdlviewer src/main.cpp - src/mainwindow.cpp) + src/mainwindow.cpp + src/vec3edit.cpp + include/vec3edit.h) target_include_directories(mdlviewer PUBLIC include) diff --git a/mdlviewer/include/mainwindow.h b/mdlviewer/include/mainwindow.h index cb8e8ae..73748ab 100644 --- a/mdlviewer/include/mainwindow.h +++ b/mdlviewer/include/mainwindow.h @@ -7,6 +7,7 @@ #include "renderer.hpp" #include "types/slot.h" #include "types/race.h" +#include "havokxmlparser.h" struct ModelInfo { int primaryID; @@ -47,10 +48,14 @@ private: Race currentRace = Race::HyurMidlanderMale; int currentLod = 0; + glm::vec3 currentScale = glm::vec3(1); + Bone* currentEditedBone = nullptr; GameData& data; Renderer* renderer; VulkanWindow* vkWindow; StandaloneWindow* standaloneWindow; + + Skeleton skeleton; }; \ No newline at end of file diff --git a/mdlviewer/include/vec3edit.h b/mdlviewer/include/vec3edit.h new file mode 100644 index 0000000..4bf3cc1 --- /dev/null +++ b/mdlviewer/include/vec3edit.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +class Vector3Edit : public QWidget { +Q_OBJECT +public: + explicit Vector3Edit(glm::vec3& vec, QWidget* parent = nullptr); + ~Vector3Edit(); + +signals: + void onValueChanged(); + +private: + struct { + QDoubleSpinBox *x, *y, *z; + } spinBoxes; + + glm::vec3& vec; + QTimer* updateTimer; +}; diff --git a/mdlviewer/src/mainwindow.cpp b/mdlviewer/src/mainwindow.cpp index 89517d3..3d14fdb 100644 --- a/mdlviewer/src/mainwindow.cpp +++ b/mdlviewer/src/mainwindow.cpp @@ -17,11 +17,16 @@ #include #include #include +#include +#include #include "gamedata.h" #include "exhparser.h" #include "exdparser.h" #include "mdlparser.h" +#include "equipment.h" +#include "glm/glm.hpp" +#include "vec3edit.h" #ifndef USE_STANDALONE_WINDOW class VulkanWindow : public QWindow @@ -78,6 +83,22 @@ private: #endif +void calculate_bone_inverse_pose(Skeleton& skeleton, Bone& bone, Bone* parent_bone) { + const glm::mat4 parentMatrix = parent_bone == nullptr ? glm::mat4(1.0f) : parent_bone->inversePose; + + glm::mat4 local(1.0f); + local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); + local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); + local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); + + bone.inversePose = parentMatrix * local; + + for(auto& b : skeleton.bones) { + if(b.parent != nullptr && b.parent->name == bone.name) + calculate_bone_inverse_pose(skeleton, b, &bone); + } +} + MainWindow::MainWindow(GameData& data) : data(data) { setWindowTitle("mdlviewer"); setMinimumSize(QSize(640, 480)); @@ -208,6 +229,36 @@ MainWindow::MainWindow(GameData& data) : data(data) { } } }); + + skeleton = parseHavokXML("/home/josh/test.xml"); + calculate_bone_inverse_pose(skeleton, *skeleton.root_bone, nullptr); + + auto boneListWidget = new QListWidget(); + for(auto& bone : skeleton.bones) { + bone.inversePose = glm::inverse(bone.inversePose); + + boneListWidget->addItem(bone.name.c_str()); + } + + boneListWidget->setMaximumWidth(200); + + connect(boneListWidget, &QListWidget::itemClicked, [this](QListWidgetItem* item) { + for(auto& bone : skeleton.bones) { + if(bone.name == item->text().toStdString()) { + currentScale = glm::make_vec3(bone.scale.data()); + currentEditedBone = &bone; + } + } + }); + + layout->addWidget(boneListWidget); + + Vector3Edit* scaleEdit = new Vector3Edit(currentScale); + connect(scaleEdit, &Vector3Edit::onValueChanged, [this] { + memcpy(currentEditedBone->scale.data(), glm::value_ptr(currentScale), sizeof(float) * 3); + reloadGearAppearance(); + }); + layout->addWidget(scaleEdit); } void MainWindow::exportModel(Model& model, QString fileName) { @@ -292,9 +343,47 @@ void MainWindow::reloadGearModel() { reloadGearAppearance(); } +void calculate_bone(Skeleton& skeleton, Bone& bone, const Bone* parent_bone) { + glm::mat4 parent_matrix = glm::mat4(1.0f); + if(parent_bone != nullptr) + parent_matrix = parent_bone->localTransform; + + glm::mat4 local = glm::mat4(1.0f); + local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); + local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); + local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); + + bone.localTransform = parent_matrix * local; + bone.finalTransform = bone.localTransform * bone.inversePose; + + for(auto& b : skeleton.bones) { + if(b.parent != nullptr && b.parent->name == bone.name) + calculate_bone(skeleton, b, &bone); + } +} + void MainWindow::reloadGearAppearance() { loadedGear.renderModel = renderer->addModel(loadedGear.model, currentLod); + calculate_bone(skeleton, *skeleton.root_bone, nullptr); + + for(int i = 0; i < 128; i++) { + loadedGear.renderModel.boneData[i] = glm::mat4(1.0f); + } + + // we want to map the actual affected bones to bone ids + std::map boneMapping; + for(int i = 0; i < loadedGear.model.affectedBoneNames.size(); i++) { + for(int k = 0; k < skeleton.bones.size(); k++) { + if(skeleton.bones[k].name == loadedGear.model.affectedBoneNames[i]) + boneMapping[i] = k; + } + } + + for(int i = 0; i < loadedGear.model.affectedBoneNames.size(); i++) { + loadedGear.renderModel.boneData[i] = skeleton.bones[boneMapping[i]].finalTransform; + } + #ifndef USE_STANDALONE_WINDOW vkWindow->models = {loadedGear.renderModel}; #else diff --git a/mdlviewer/src/vec3edit.cpp b/mdlviewer/src/vec3edit.cpp new file mode 100644 index 0000000..05fa378 --- /dev/null +++ b/mdlviewer/src/vec3edit.cpp @@ -0,0 +1,58 @@ +#include "vec3edit.h" + +#include +#include + +Vector3Edit::Vector3Edit(glm::vec3& vec, QWidget* parent) : QWidget(parent), vec(vec) { + QHBoxLayout* itemsLayout = new QHBoxLayout(this); + + spinBoxes.x = new QDoubleSpinBox(); + spinBoxes.y = new QDoubleSpinBox(); + spinBoxes.z = new QDoubleSpinBox(); + + spinBoxes.x->setMinimum(-10000.0); + spinBoxes.x->setMaximum(10000.0); + + spinBoxes.y->setMinimum(-10000.0); + spinBoxes.y->setMaximum(10000.0); + + spinBoxes.z->setMinimum(-10000.0); + spinBoxes.z->setMaximum(10000.0); + + itemsLayout->addWidget(spinBoxes.x); + itemsLayout->addWidget(spinBoxes.y); + itemsLayout->addWidget(spinBoxes.z); + + spinBoxes.x->setValue(vec.x); + spinBoxes.y->setValue(vec.y); + spinBoxes.z->setValue(vec.z); + + connect(spinBoxes.x, static_cast(&QDoubleSpinBox::valueChanged), [this, &vec](double d) { + vec.x = d; + emit onValueChanged(); + }); + connect(spinBoxes.y, static_cast(&QDoubleSpinBox::valueChanged), [this, &vec](double d) { + vec.y = d; + emit onValueChanged(); + }); + connect(spinBoxes.z, static_cast(&QDoubleSpinBox::valueChanged), [this, &vec](double d) { + vec.z = d; + emit onValueChanged(); + }); + + // TODO: find a better way to do this + updateTimer = new QTimer(); + connect(updateTimer, &QTimer::timeout, [this, &vec]() { + if (vec.x != spinBoxes.x->value() || vec.y != spinBoxes.y->value() || vec.z != spinBoxes.z->value()) { + spinBoxes.x->setValue(vec.x); + spinBoxes.y->setValue(vec.y); + spinBoxes.z->setValue(vec.z); + } + }); + + updateTimer->start(1); +} + +Vector3Edit::~Vector3Edit() { + updateTimer->stop(); +} diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp index 62e15e5..92d45af 100644 --- a/renderer/include/renderer.hpp +++ b/renderer/include/renderer.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "mdlparser.h" @@ -11,11 +12,14 @@ struct RenderPart { VkBuffer vertexBuffer, indexBuffer; VkDeviceMemory vertexMemory, indexMemory; + + std::vector submeshes; }; struct RenderModel { Model model; std::vector parts; + std::array boneData; }; class Renderer { @@ -23,6 +27,7 @@ public: Renderer(); void initPipeline(); + void initDescriptors(); bool initSwapchain(VkSurfaceKHR surface, int width, int height); void resize(VkSurfaceKHR surface, int width, int height); @@ -45,6 +50,14 @@ public: std::array inFlightFences; std::array imageAvailableSemaphores, renderFinishedSemaphores; uint32_t currentFrame = 0; + + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + + VkBuffer boneInfoBuffer = VK_NULL_HANDLE; + VkDeviceMemory boneInfoMemory = VK_NULL_HANDLE; + VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; + VkDescriptorSet set = VK_NULL_HANDLE; + VkPipeline pipeline; VkPipelineLayout pipelineLayout; diff --git a/renderer/shaders/mesh.vert b/renderer/shaders/mesh.vert index dbad9f4..bbf192a 100644 --- a/renderer/shaders/mesh.vert +++ b/renderer/shaders/mesh.vert @@ -2,16 +2,33 @@ layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec4 inBoneWeights; +layout(location = 3) in uvec4 inBoneIds; layout(location = 0) out vec3 outNormal; layout(location = 1) out vec3 outFragPos; layout(push_constant) uniform PushConstant { - mat4 mvp; + mat4 vp, model; + int boneOffset; +}; + +layout(std430, binding = 2) buffer readonly BoneInformation { + mat4 bones[128]; }; void main() { - gl_Position = mvp * vec4(inPosition, 1.0); - outNormal = inNormal; - outFragPos = inNormal; + mat4 BoneTransform = bones[boneOffset + inBoneIds[0]] * inBoneWeights[0]; + BoneTransform += bones[boneOffset + inBoneIds[1]] * inBoneWeights[1]; + BoneTransform += bones[boneOffset + inBoneIds[2]] * inBoneWeights[2]; + BoneTransform += bones[boneOffset + inBoneIds[3]] * inBoneWeights[3]; + + BoneTransform = model * BoneTransform; + + vec4 bPos = BoneTransform * vec4(inPosition, 1.0); + vec4 bNor = BoneTransform * vec4(inNormal, 0.0); + + gl_Position = vp * bPos; + outNormal = bNor.xyz; + outFragPos = vec3(model * vec4(inPosition, 1.0)); } diff --git a/renderer/shaders/mesh.vert.spv b/renderer/shaders/mesh.vert.spv index b2a2cd0911800df103051033fdaa5f7eb1885429..77595fd3a937c2072ad177e32f5a1e005c4bbde0 100644 GIT binary patch literal 4520 zcmZ9NX?Ijb5QZ;g0YOAS78Nmo3W_2sAc{a(0~$#X1os${35-r=VlqKCH4zavL~zFi zMRDJMg1^cyp5yb}dn;ViIW_&(TU%FG&!l1E;z>y|A(@g)Oa4l-H6xh_lO$7<=5%js z-`&1^sNTDL<*F5WT$MDWj`~bbu1s+wpZ(qCs>%(>667Ru204#hL@pr>{7q#4o8U&O zwzYS)b#%0~Z|&+C80Z?RAE}hOD&=aatG7H@>Zz9xmGqlj@GDn)D#N`+hbBCV^_w{N z6l!d$RZDvYyQ@QewZVRL`Kxk%M^&=BUanPPn^GQAvY(it)VCqIT7qTVEcVXfp@W-i z)uDQKwJzUGRe9H;0rjR-_g?yIy`>6!a~^BY=I-e08!8dq#68T0b2ahjot3Mect>wm zzd60bjN4Z#A2?Xg_{L-u-~F96)il<7E>|JSX*+O@$!UFd)&|4JIh3mq{oNi5pXTHf z_5+o!&eGuC(qO%GSl;u>yR=2p=44XJ+gr+)tM%RGqfEz`%w~_+Hdo36Tgu*2Pif5N zi_tb*sf^jY1K)&$j&LZbIt>Nq!A+FFDg6+{7G54%yHg|oCHNq!qM4We6j8iwR74dAw z$z6`iIBV-Sf_Ps2Z`5NB8~cE4P4J&Rx!2)7d?P+}d3CEy$UB273!K0Wr7wOvJSypPjjt z%(pSQ2J9J~mpRq_?#o4vIbdsehPk=Tc>BMeJ?>Wfe01x}n@`>O$C>nv`_}))+?L%- zWBP`y6j-5f==bbqh`VMq+hTa2qp3&b?aO67%w)dz%0=Bkd zzbDwOWxVxYV>edb^Qrru<;<_|DI`foHiX^xU9_J~?b-Z41{-4!3qZbAYs+a{()&?*Vp^;f0((|N7zTl z>HfxR-$;~iW**|Z@-3|+#`kkAB7Yp+`CpfF#`rE;kofH|MxTW_KZ1|?BE&fPjoi{= z_L~sD7ruS%#yZcN5pCx8-Q0>SL41$;c*nOP@@McdW*K6f{CaM}zU88?+ri`ex&ux= z`dSW_i@sKX?aLWQUw47!qp!Qc#>q!t_kg1>?a|jtu(s%H71+KazkRJn6WL@{#8eaB=3l(dArme|u7#Qe*e`D5}1hEqgC}(Ty$6d>^`Xi!*-=UEj=>#XgR1 ztn-b!Po((&Gk+3J-^`Z9K80>Temp#XVCT47BlYx+lyy1w>~j*Z87s_!Q*G% z4JRKn-w&3HnfHR7c@GjZFM;J_=6zt}4Axg$#2x}0>wJTI5p1kn#2yCgt1V`J1Z*#!$=v$H%(Z#H$Jmb}&ip76clT1x zhyF6UcX^JO$?UHn`p8F4x$u7#tiSpR_Scb8^Vz)vwzfW=Ym{9s&h;+1c&_)*&meKGb6`1FaPNcVis$+u=Zfe05YBl0 zwVzET`|f@OwzfW=>omJu%-}PyGnk3QSw9EMtK0V%i2X#bUxM|KH_rw3uaNVId90(Z zU*z}(Z0!!F=v=-<#yDZFR{!2OY|D# EKhE!8^Z)<= literal 1392 zcmYk4-EPxB5QVo%ofb-4O6d_GNNb|`D>*V6u5hZc3l9|ZpCsXzD>jAyeT%fClq z5ZO2kCN>SnxxEU}EjM&F9!K*@Q{YL_yw_8`E4t$%3Wj0&HB9sHo1oZ|$0wF#Hn&ME zcB#%<)e5U)-k3)Y#v2*%u}EA&!V0U?ZbI?d7Q+Y z&fz<%8lE-1nn~9-no%4mfi7%E7nN zm9fC|+Qjq;rcUDY=6R z^l)8y^y_jE!+O%)(C%jQroy~E9QUjhx-u{KfY)WLU14THUh3-V6f=P%_bu(*4@Yh= z-v}J>+m(-=9(S};Gj|i;)xIEu=RBA>!%+*`dop8wzDK_=kF(SR?pN5I2lFmyTt41~ zd|={-GUBiF=DY)avEcrQ;T!p&e`?>7(GT-@DdsjxyXF5Z@dljdx=I&P< zbvPa024|^ZsE+9AfsFI$iPQTpHyr6x!NAfr+;G11?*x^ O8Zl-9{;Ns7mi+@{OKSK4 diff --git a/renderer/src/renderer.cpp b/renderer/src/renderer.cpp index 9a423cf..021aa63 100644 --- a/renderer/src/renderer.cpp +++ b/renderer/src/renderer.cpp @@ -306,6 +306,7 @@ bool Renderer::initSwapchain(VkSurfaceKHR surface, int width, int height) { vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass); + initDescriptors(); initPipeline(); swapchainFramebuffers.resize(swapchainViews.size()); @@ -402,6 +403,25 @@ void Renderer::render(std::vector models) { vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); for(auto model : models) { + // copy bone data + { + const size_t bufferSize = sizeof(glm::mat4) * 128; + void *mapped_data = nullptr; + vkMapMemory(device, boneInfoMemory, 0, bufferSize, 0, &mapped_data); + + memcpy(mapped_data, model.boneData.data(), bufferSize); + + VkMappedMemoryRange range = {}; + range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range.memory = boneInfoMemory; + range.size = bufferSize; + vkFlushMappedMemoryRanges(device, 1, &range); + + vkUnmapMemory(device, boneInfoMemory); + } + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, 0, nullptr); + for(auto part : model.parts) { VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(commandBuffer, 0, 1, &part.vertexBuffer, offsets); @@ -410,10 +430,17 @@ void Renderer::render(std::vector models) { glm::mat4 p = glm::perspective(glm::radians(45.0f), swapchainExtent.width / (float) swapchainExtent.height, 0.1f, 100.0f); p[1][1] *= -1; - glm::mat4 v = glm::lookAt(glm::vec3(0, 1, -2.5), glm::vec3(0, 1, 0), glm::vec3(0, 1, 0)); - glm::mat4 mvp = p * v; + glm::mat4 v = glm::lookAt(glm::vec3(3), glm::vec3(0, 1, 0), glm::vec3(0, 1, 0)); + glm::mat4 vp = p * v; - vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &mvp); + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &vp); + + glm::mat4 m = glm::mat4(1.0f); + + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), sizeof(glm::mat4), &m); + + int test = 0; + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4) * 2, sizeof(int), &test); vkCmdDrawIndexed(commandBuffer, part.numIndices, 1, 0, 0, 0); } @@ -517,6 +544,7 @@ RenderModel Renderer::addModel(const Model& model, int lod) { for(auto part : model.lods[lod].parts) { RenderPart renderPart; + renderPart.submeshes = part.submeshes; size_t vertexSize = part.vertices.size() * sizeof(Vertex); auto[vertexBuffer, vertexMemory] = createBuffer(vertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); @@ -590,6 +618,7 @@ void Renderer::initPipeline() { VkVertexInputAttributeDescription positionAttribute = {}; positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; + positionAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; positionAttribute.offset = offsetof(Vertex, position); VkVertexInputAttributeDescription normalAttribute = {}; @@ -597,7 +626,17 @@ void Renderer::initPipeline() { normalAttribute.location = 1; normalAttribute.offset = offsetof(Vertex, normal); - std::array attributes = {positionAttribute, normalAttribute}; + VkVertexInputAttributeDescription boneWeightAttribute = {}; + boneWeightAttribute.format = VK_FORMAT_R32G32B32_SFLOAT; + boneWeightAttribute.location = 2; + boneWeightAttribute.offset = offsetof(Vertex, boneWeights); + + VkVertexInputAttributeDescription boneIdAttribute = {}; + boneIdAttribute.format = VK_FORMAT_R8G8B8A8_UINT; + boneIdAttribute.location = 3; + boneIdAttribute.offset = offsetof(Vertex, boneIds); + + std::array attributes = {positionAttribute, normalAttribute, boneWeightAttribute, boneIdAttribute}; VkPipelineVertexInputStateCreateInfo vertexInputState = {}; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; @@ -647,13 +686,15 @@ void Renderer::initPipeline() { dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; VkPushConstantRange pushConstantRange = {}; - pushConstantRange.size = sizeof(glm::mat4); + pushConstantRange.size = (sizeof(glm::mat4) * 2) + sizeof(int); pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.pushConstantRangeCount = 1; pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &setLayout; vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout); @@ -700,4 +741,59 @@ VkShaderModule Renderer::loadShaderFromDisk(const std::string_view path) { file.read(buffer.data(), fileSize); return createShaderModule(reinterpret_cast(buffer.data()), fileSize); -} \ No newline at end of file +} + +void Renderer::initDescriptors() { + VkDescriptorPoolSize poolSize = {}; + poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + poolSize.descriptorCount = 1; + + VkDescriptorPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolCreateInfo.poolSizeCount = 1; + poolCreateInfo.pPoolSizes = &poolSize; + poolCreateInfo.maxSets = 1; + + vkCreateDescriptorPool(device, &poolCreateInfo, nullptr, &descriptorPool); + + VkDescriptorSetLayoutBinding boneInfoBufferBinding = {}; + boneInfoBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + boneInfoBufferBinding.descriptorCount = 1; + boneInfoBufferBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + boneInfoBufferBinding.binding = 2; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &boneInfoBufferBinding; + + vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &setLayout); + + const size_t bufferSize = sizeof(glm::mat4) * 128; + auto [buffer, memory] = createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + boneInfoBuffer = buffer; + boneInfoMemory = memory; + + VkDescriptorSetAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocateInfo.descriptorPool = descriptorPool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &setLayout; + + vkAllocateDescriptorSets(device, &allocateInfo, &set); + + VkDescriptorBufferInfo bufferInfo = {}; + bufferInfo.buffer = boneInfoBuffer; + bufferInfo.range = bufferSize; + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = set; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + descriptorWrite.dstBinding = 2; + + vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr); +}