From b18564c1f53aceae3ce9059deba813dd3d0e34d1 Mon Sep 17 00:00:00 2001 From: InkSoul Date: Mon, 5 Jun 2023 21:13:17 +0800 Subject: [PATCH] Revert "save to revert" This reverts commit 58212f943ee914e44e14ab467bb1aaac6a5d5e53. --- src/render/glTFModel.cpp | 2396 ++++++++++++++++++++++---------------- src/render/glTFModel.h | 248 ++-- src/render/render.cpp | 78 +- src/render/render.h | 118 +- 4 files changed, 1555 insertions(+), 1285 deletions(-) diff --git a/src/render/glTFModel.cpp b/src/render/glTFModel.cpp index 838ddd5..2fe3648 100644 --- a/src/render/glTFModel.cpp +++ b/src/render/glTFModel.cpp @@ -1,108 +1,124 @@ - //#pragma once #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION -#define STBI_MSC_SECURE_CRT +#define TINYGLTF_NO_STB_IMAGE_WRITE -#include "VulkanglTFModel.h" +#include "glTFModel.h" -namespace glTFModel +VkDescriptorSetLayout glTFModel::descriptorSetLayoutImage = VK_NULL_HANDLE; +VkDescriptorSetLayout glTFModel::descriptorSetLayoutUbo = VK_NULL_HANDLE; +VkMemoryPropertyFlags glTFModel::memoryPropertyFlags = 0; +uint32_t glTFModel::descriptorBindingFlags = glTFModel::DescriptorBindingFlags::ImageBaseColor; + + +/* + glTF loading functions + + The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure +*/ + +// custom image loading function with tinyglTF + + +// ktx image as texture +bool loadImageDataFunc(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) { - // Bounding box - - BoundingBox::BoundingBox() { - }; - - BoundingBox::BoundingBox(glm::vec3 min, glm::vec3 max) : min(min), max(max) { - }; - - BoundingBox BoundingBox::getAABB(glm::mat4 m) { - glm::vec3 min = glm::vec3(m[3]); - glm::vec3 max = min; - glm::vec3 v0, v1; - - glm::vec3 right = glm::vec3(m[0]); - v0 = right * this->min.x; - v1 = right * this->max.x; - min += glm::min(v0, v1); - max += glm::max(v0, v1); - - glm::vec3 up = glm::vec3(m[1]); - v0 = up * this->min.y; - v1 = up * this->max.y; - min += glm::min(v0, v1); - max += glm::max(v0, v1); - - glm::vec3 back = glm::vec3(m[2]); - v0 = back * this->min.z; - v1 = back * this->max.z; - min += glm::min(v0, v1); - max += glm::max(v0, v1); - - return BoundingBox(min, max); + // KTX files will be handled by our own code + if (image->uri.find_last_of(".") != std::string::npos) { + if (image->uri.substr(image->uri.find_last_of(".") + 1) == "ktx") { + return true; + } } - // Texture - void Texture::updateDescriptor() - { - descriptor.sampler = sampler; - descriptor.imageView = view; - descriptor.imageLayout = imageLayout; - } + return tinygltf::LoadImageData(image, imageIndex, error, warning, req_width, req_height, bytes, size, userData); +} - void Texture::destroy() +//will be used for samples that don't require images to be loaded +bool loadImageDataFuncEmpty(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) +{ + // This function will be used for samples that don't require images to be loaded + return true; +} + +/* + glTF texture loader +*/ + +void glTFModel::Texture::updateDescriptor() +{ + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; +} + +void glTFModel::Texture::destroy() +{ + if (device) { vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImage(device->logicalDevice, image, nullptr); vkFreeMemory(device->logicalDevice, deviceMemory, nullptr); vkDestroySampler(device->logicalDevice, sampler, nullptr); } +} - void Texture::fromglTfImage(tinygltf::Image& gltfimage, TextureSampler textureSampler, vks::VulkanDevice* device, VkQueue copyQueue) +void glTFModel::Texture::fromglTfImage(tinygltf::Image& gltfImage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue) +{ + this->device = device; + + bool isKtx = false; + // Image points to an external ktx file + if (gltfImage.uri.find_last_of(".") != std::string::npos) { + if (gltfImage.uri.substr(gltfImage.uri.find_last_of(".") + 1) == "ktx") { + isKtx = true; + } + } + + VkFormat format; + + if (!isKtx) { - this->device = device; + // loaded using STB_Image + // Images can be stored inside the glTF (which is the case for the sample model), so instead of directly + // loading them from disk, we fetch them from the glTF loader and upload the buffers unsigned char* buffer = nullptr; VkDeviceSize bufferSize = 0; bool deleteBuffer = false; - if (gltfimage.component == 3) { - // Most devices don't support RGB only on Vulkan so convert if necessary - // TODO: Check actual format support and transform only if required - bufferSize = gltfimage.width * gltfimage.height * 4; + // We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan + if (gltfImage.component == 3) { + bufferSize = gltfImage.width * gltfImage.height * 4; buffer = new unsigned char[bufferSize]; unsigned char* rgba = buffer; - unsigned char* rgb = &gltfimage.image[0]; - for (int32_t i = 0; i < gltfimage.width * gltfimage.height; ++i) { - for (int32_t j = 0; j < 3; ++j) { - rgba[j] = rgb[j]; - } + unsigned char* rgb = &gltfImage.image[0]; + for (size_t i = 0; i < gltfImage.width * gltfImage.height; ++i) { + memcpy(rgba, rgb, sizeof(unsigned char) * 3); rgba += 4; rgb += 3; } deleteBuffer = true; } else { - buffer = &gltfimage.image[0]; - bufferSize = gltfimage.image.size(); + buffer = &gltfImage.image[0]; + bufferSize = gltfImage.image.size(); } - VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; + format = VK_FORMAT_R8G8B8A8_UNORM; VkFormatProperties formatProperties; - width = gltfimage.width; - height = gltfimage.height; + width = gltfImage.width; + height = gltfImage.height; mipLevels = static_cast(floor(log2(std::max(width, height))) + 1.0); vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT); assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT); - + // allocate memry for texture VkMemoryAllocateInfo memAllocInfo{}; memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; VkMemoryRequirements memReqs{}; - VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; @@ -172,7 +188,7 @@ namespace glTFModel bufferCopyRegion.imageExtent.depth = 1; vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion); - + { VkImageMemoryBarrier imageMemoryBarrier{}; imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -184,7 +200,6 @@ namespace glTFModel imageMemoryBarrier.subresourceRange = subresourceRange; vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } - device->flushCommandBuffer(copyCmd, copyQueue, true); vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); @@ -251,1022 +266,1365 @@ namespace glTFModel imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; imageMemoryBarrier.image = image; imageMemoryBarrier.subresourceRange = subresourceRange; vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); } - device->flushCommandBuffer(blitCmd, copyQueue, true); - - VkSamplerCreateInfo samplerInfo{}; - samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - samplerInfo.magFilter = textureSampler.magFilter; - samplerInfo.minFilter = textureSampler.minFilter; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - samplerInfo.addressModeU = textureSampler.addressModeU; - samplerInfo.addressModeV = textureSampler.addressModeV; - samplerInfo.addressModeW = textureSampler.addressModeW; - samplerInfo.compareOp = VK_COMPARE_OP_NEVER; - samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - samplerInfo.maxAnisotropy = 1.0; - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxLod = (float)mipLevels; - samplerInfo.maxAnisotropy = 8.0f; - samplerInfo.anisotropyEnable = VK_TRUE; - VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); - - VkImageViewCreateInfo viewInfo{}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = format; - viewInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.layerCount = 1; - viewInfo.subresourceRange.levelCount = mipLevels; - VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &view)); - - descriptor.sampler = sampler; - descriptor.imageView = view; - descriptor.imageLayout = imageLayout; - - if (deleteBuffer) + if (deleteBuffer) { delete[] buffer; + } + device->flushCommandBuffer(blitCmd, copyQueue, true); } + else { + // Texture is stored in an external ktx file + std::string filename = path + "/" + gltfImage.uri; - // Primitive - Primitive::Primitive(uint32_t firstIndex, uint32_t indexCount, uint32_t vertexCount, Material& material) : firstIndex(firstIndex), indexCount(indexCount), vertexCount(vertexCount), material(material) { - hasIndices = indexCount > 0; - }; + ktxTexture* ktxTexture; - void Primitive::setBoundingBox(glm::vec3 min, glm::vec3 max) { - bb.min = min; - bb.max = max; - bb.valid = true; - } + ktxResult result = KTX_SUCCESS; +#if defined(__ANDROID__) + AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); + if (!asset) { + vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1); + } + size_t size = AAsset_getLength(asset); + assert(size > 0); + ktx_uint8_t* textureData = new ktx_uint8_t[size]; + AAsset_read(asset, textureData, size); + AAsset_close(asset); + result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); + delete[] textureData; +#else + if (!vks::tools::fileExists(filename)) { + vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1); + } + result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); +#endif + assert(result == KTX_SUCCESS); - // Mesh - Mesh::Mesh(vks::VulkanDevice* device, glm::mat4 matrix) { this->device = device; - this->uniformBlock.matrix = matrix; - VK_CHECK_RESULT(device->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - sizeof(uniformBlock), - &uniformBuffer.buffer, - &uniformBuffer.memory, - &uniformBlock)); - VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, uniformBuffer.memory, 0, sizeof(uniformBlock), 0, &uniformBuffer.mapped)); - uniformBuffer.descriptor = { uniformBuffer.buffer, 0, sizeof(uniformBlock) }; - }; + width = ktxTexture->baseWidth; + height = ktxTexture->baseHeight; + mipLevels = ktxTexture->numLevels; - Mesh::~Mesh() { - vkDestroyBuffer(device->logicalDevice, uniformBuffer.buffer, nullptr); - vkFreeMemory(device->logicalDevice, uniformBuffer.memory, nullptr); - for (Primitive* p : primitives) - delete p; + ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture); + ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture); + // @todo: Use ktxTexture_GetVkFormat(ktxTexture) + format = VK_FORMAT_R8G8B8A8_UNORM; + + // Get device properties for the requested texture format + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties); + + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + + VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); + bufferCreateInfo.size = ktxTextureSize; + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + + VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); + + uint8_t* data; + VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); + memcpy(data, ktxTextureData, ktxTextureSize); + vkUnmapMemory(device->logicalDevice, stagingMemory); + + std::vector bufferCopyRegions; + for (uint32_t i = 0; i < mipLevels; i++) + { + ktx_size_t offset; + KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset); + assert(result == KTX_SUCCESS); + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = i; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i); + bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i); + bufferCopyRegion.imageExtent.depth = 1; + bufferCopyRegion.bufferOffset = offset; + bufferCopyRegions.push_back(bufferCopyRegion); + } + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = mipLevels; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image)); + + vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0)); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = mipLevels; + subresourceRange.layerCount = 1; + + vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); + vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(bufferCopyRegions.size()), bufferCopyRegions.data()); + vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange); + device->flushCommandBuffer(copyCmd, copyQueue); + this->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); + + ktxTexture_Destroy(ktxTexture); } + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.compareOp = VK_COMPARE_OP_NEVER; + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + samplerInfo.maxAnisotropy = 1.0; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxLod = (float)mipLevels; + samplerInfo.maxAnisotropy = 8.0f; + samplerInfo.anisotropyEnable = VK_TRUE; + VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); - void Mesh::setBoundingBox(glm::vec3 min, glm::vec3 max) { - bb.min = min; - bb.max = max; - bb.valid = true; + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.layerCount = 1; + viewInfo.subresourceRange.levelCount = mipLevels; + VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &view)); + + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; + +} + + +void glTFModel::Model::loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue) +{ + for (tinygltf::Image& image : gltfModel.images) { + glTFModel::Texture texture; + texture.fromglTfImage(image, path, device, transferQueue); + textures.push_back(texture); } + // Create an empty texture to be used for empty material images + createEmptyTexture(transferQueue); +} - // Node - glm::mat4 Node::localMatrix() { - return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix; - } - glm::mat4 Node::getMatrix() { - glm::mat4 m = localMatrix(); - vkglTF::Node* p = parent; - while (p) { - m = p->localMatrix() * m; - p = p->parent; +void glTFModel::Model::loadAnimations(tinygltf::Model& gltfModel) +{ + for (tinygltf::Animation& anim : gltfModel.animations) { + glTFModel::Animation animation{}; + animation.name = anim.name; + if (anim.name.empty()) { + animation.name = std::to_string(animations.size()); } - return m; - } - void Node::update() { - if (mesh) { - glm::mat4 m = getMatrix(); - if (skin) { - mesh->uniformBlock.matrix = m; - // Update join matrices - glm::mat4 inverseTransform = glm::inverse(m); - size_t numJoints = std::min((uint32_t)skin->joints.size(), MAX_NUM_JOINTS); - for (size_t i = 0; i < numJoints; i++) { - vkglTF::Node* jointNode = skin->joints[i]; - glm::mat4 jointMat = jointNode->getMatrix() * skin->inverseBindMatrices[i]; - jointMat = inverseTransform * jointMat; - mesh->uniformBlock.jointMatrix[i] = jointMat; - } - mesh->uniformBlock.jointcount = (float)numJoints; - memcpy(mesh->uniformBuffer.mapped, &mesh->uniformBlock, sizeof(mesh->uniformBlock)); + // Samplers + for (auto& samp : anim.samplers) { + glTFModel::AnimationSampler sampler{}; + + if (samp.interpolation == "LINEAR") { + sampler.interpolation = AnimationSampler::InterpolationType::LINEAR; } - else { - memcpy(mesh->uniformBuffer.mapped, &m, sizeof(glm::mat4)); + if (samp.interpolation == "STEP") { + sampler.interpolation = AnimationSampler::InterpolationType::STEP; } - } - - for (auto& child : children) { - child->update(); - } - } - - Node::~Node() { - if (mesh) { - delete mesh; - } - for (auto& child : children) { - delete child; - } - } - - // Model - - void Model::destroy(VkDevice device) - { - if (vertices.buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, vertices.buffer, nullptr); - vkFreeMemory(device, vertices.memory, nullptr); - vertices.buffer = VK_NULL_HANDLE; - } - if (indices.buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, indices.buffer, nullptr); - vkFreeMemory(device, indices.memory, nullptr); - indices.buffer = VK_NULL_HANDLE; - } - for (auto texture : textures) { - texture.destroy(); - } - textures.resize(0); - textureSamplers.resize(0); - for (auto node : nodes) { - delete node; - } - materials.resize(0); - animations.resize(0); - nodes.resize(0); - linearNodes.resize(0); - extensions.resize(0); - for (auto skin : skins) { - delete skin; - } - skins.resize(0); - }; - - void Model::loadNode(vkglTF::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, LoaderInfo& loaderInfo, float globalscale) - { - vkglTF::Node* newNode = new Node{}; - newNode->index = nodeIndex; - newNode->parent = parent; - newNode->name = node.name; - newNode->skinIndex = node.skin; - newNode->matrix = glm::mat4(1.0f); - - // Generate local node matrix - glm::vec3 translation = glm::vec3(0.0f); - if (node.translation.size() == 3) { - translation = glm::make_vec3(node.translation.data()); - newNode->translation = translation; - } - glm::mat4 rotation = glm::mat4(1.0f); - if (node.rotation.size() == 4) { - glm::quat q = glm::make_quat(node.rotation.data()); - newNode->rotation = glm::mat4(q); - } - glm::vec3 scale = glm::vec3(1.0f); - if (node.scale.size() == 3) { - scale = glm::make_vec3(node.scale.data()); - newNode->scale = scale; - } - if (node.matrix.size() == 16) { - newNode->matrix = glm::make_mat4x4(node.matrix.data()); - }; - - // Node with children - if (node.children.size() > 0) { - for (size_t i = 0; i < node.children.size(); i++) { - loadNode(newNode, model.nodes[node.children[i]], node.children[i], model, loaderInfo, globalscale); - } - } - - // Node contains mesh data - if (node.mesh > -1) { - const tinygltf::Mesh mesh = model.meshes[node.mesh]; - Mesh* newMesh = new Mesh(device, newNode->matrix); - for (size_t j = 0; j < mesh.primitives.size(); j++) { - const tinygltf::Primitive& primitive = mesh.primitives[j]; - uint32_t vertexStart = static_cast(loaderInfo.vertexPos); - uint32_t indexStart = static_cast(loaderInfo.indexPos); - uint32_t indexCount = 0; - uint32_t vertexCount = 0; - glm::vec3 posMin{}; - glm::vec3 posMax{}; - bool hasSkin = false; - bool hasIndices = primitive.indices > -1; - // Vertices - { - const float* bufferPos = nullptr; - const float* bufferNormals = nullptr; - const float* bufferTexCoordSet0 = nullptr; - const float* bufferTexCoordSet1 = nullptr; - const float* bufferColorSet0 = nullptr; - const void* bufferJoints = nullptr; - const float* bufferWeights = nullptr; - - int posByteStride; - int normByteStride; - int uv0ByteStride; - int uv1ByteStride; - int color0ByteStride; - int jointByteStride; - int weightByteStride; - - int jointComponentType; - - // Position attribute is required - assert(primitive.attributes.find("POSITION") != primitive.attributes.end()); - - const tinygltf::Accessor& posAccessor = model.accessors[primitive.attributes.find("POSITION")->second]; - const tinygltf::BufferView& posView = model.bufferViews[posAccessor.bufferView]; - bufferPos = reinterpret_cast(&(model.buffers[posView.buffer].data[posAccessor.byteOffset + posView.byteOffset])); - posMin = glm::vec3(posAccessor.minValues[0], posAccessor.minValues[1], posAccessor.minValues[2]); - posMax = glm::vec3(posAccessor.maxValues[0], posAccessor.maxValues[1], posAccessor.maxValues[2]); - vertexCount = static_cast(posAccessor.count); - posByteStride = posAccessor.ByteStride(posView) ? (posAccessor.ByteStride(posView) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC3); - - if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { - const tinygltf::Accessor& normAccessor = model.accessors[primitive.attributes.find("NORMAL")->second]; - const tinygltf::BufferView& normView = model.bufferViews[normAccessor.bufferView]; - bufferNormals = reinterpret_cast(&(model.buffers[normView.buffer].data[normAccessor.byteOffset + normView.byteOffset])); - normByteStride = normAccessor.ByteStride(normView) ? (normAccessor.ByteStride(normView) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC3); - } - - // UVs - if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) { - const tinygltf::Accessor& uvAccessor = model.accessors[primitive.attributes.find("TEXCOORD_0")->second]; - const tinygltf::BufferView& uvView = model.bufferViews[uvAccessor.bufferView]; - bufferTexCoordSet0 = reinterpret_cast(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset])); - uv0ByteStride = uvAccessor.ByteStride(uvView) ? (uvAccessor.ByteStride(uvView) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC2); - } - if (primitive.attributes.find("TEXCOORD_1") != primitive.attributes.end()) { - const tinygltf::Accessor& uvAccessor = model.accessors[primitive.attributes.find("TEXCOORD_1")->second]; - const tinygltf::BufferView& uvView = model.bufferViews[uvAccessor.bufferView]; - bufferTexCoordSet1 = reinterpret_cast(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset])); - uv1ByteStride = uvAccessor.ByteStride(uvView) ? (uvAccessor.ByteStride(uvView) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC2); - } - - // Vertex colors - if (primitive.attributes.find("COLOR_0") != primitive.attributes.end()) { - const tinygltf::Accessor& accessor = model.accessors[primitive.attributes.find("COLOR_0")->second]; - const tinygltf::BufferView& view = model.bufferViews[accessor.bufferView]; - bufferColorSet0 = reinterpret_cast(&(model.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - color0ByteStride = accessor.ByteStride(view) ? (accessor.ByteStride(view) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC3); - } - - // Skinning - // Joints - if (primitive.attributes.find("JOINTS_0") != primitive.attributes.end()) { - const tinygltf::Accessor& jointAccessor = model.accessors[primitive.attributes.find("JOINTS_0")->second]; - const tinygltf::BufferView& jointView = model.bufferViews[jointAccessor.bufferView]; - bufferJoints = &(model.buffers[jointView.buffer].data[jointAccessor.byteOffset + jointView.byteOffset]); - jointComponentType = jointAccessor.componentType; - jointByteStride = jointAccessor.ByteStride(jointView) ? (jointAccessor.ByteStride(jointView) / tinygltf::GetComponentSizeInBytes(jointComponentType)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC4); - } - - if (primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end()) { - const tinygltf::Accessor& weightAccessor = model.accessors[primitive.attributes.find("WEIGHTS_0")->second]; - const tinygltf::BufferView& weightView = model.bufferViews[weightAccessor.bufferView]; - bufferWeights = reinterpret_cast(&(model.buffers[weightView.buffer].data[weightAccessor.byteOffset + weightView.byteOffset])); - weightByteStride = weightAccessor.ByteStride(weightView) ? (weightAccessor.ByteStride(weightView) / sizeof(float)) : tinygltf::GetNumComponentsInType(TINYGLTF_TYPE_VEC4); - } - - hasSkin = (bufferJoints && bufferWeights); - - for (size_t v = 0; v < posAccessor.count; v++) { - Vertex& vert = loaderInfo.vertexBuffer[loaderInfo.vertexPos]; - vert.pos = glm::vec4(glm::make_vec3(&bufferPos[v * posByteStride]), 1.0f); - vert.normal = glm::normalize(glm::vec3(bufferNormals ? glm::make_vec3(&bufferNormals[v * normByteStride]) : glm::vec3(0.0f))); - vert.uv0 = bufferTexCoordSet0 ? glm::make_vec2(&bufferTexCoordSet0[v * uv0ByteStride]) : glm::vec3(0.0f); - vert.uv1 = bufferTexCoordSet1 ? glm::make_vec2(&bufferTexCoordSet1[v * uv1ByteStride]) : glm::vec3(0.0f); - vert.color = bufferColorSet0 ? glm::make_vec4(&bufferColorSet0[v * color0ByteStride]) : glm::vec4(1.0f); - - if (hasSkin) - { - switch (jointComponentType) { - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { - const uint16_t* buf = static_cast(bufferJoints); - vert.joint0 = glm::vec4(glm::make_vec4(&buf[v * jointByteStride])); - break; - } - case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { - const uint8_t* buf = static_cast(bufferJoints); - vert.joint0 = glm::vec4(glm::make_vec4(&buf[v * jointByteStride])); - break; - } - default: - // Not supported by spec - std::cerr << "Joint component type " << jointComponentType << " not supported!" << std::endl; - break; - } - } - else { - vert.joint0 = glm::vec4(0.0f); - } - vert.weight0 = hasSkin ? glm::make_vec4(&bufferWeights[v * weightByteStride]) : glm::vec4(0.0f); - // Fix for all zero weights - if (glm::length(vert.weight0) == 0.0f) { - vert.weight0 = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f); - } - loaderInfo.vertexPos++; - } - } - // Indices - if (hasIndices) - { - const tinygltf::Accessor& accessor = model.accessors[primitive.indices > -1 ? primitive.indices : 0]; - const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = model.buffers[bufferView.buffer]; - - indexCount = static_cast(accessor.count); - const void* dataPtr = &(buffer.data[accessor.byteOffset + bufferView.byteOffset]); - - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) { - loaderInfo.indexBuffer[loaderInfo.indexPos] = buf[index] + vertexStart; - loaderInfo.indexPos++; - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { - const uint16_t* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) { - loaderInfo.indexBuffer[loaderInfo.indexPos] = buf[index] + vertexStart; - loaderInfo.indexPos++; - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { - const uint8_t* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) { - loaderInfo.indexBuffer[loaderInfo.indexPos] = buf[index] + vertexStart; - loaderInfo.indexPos++; - } - break; - } - default: - std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; - return; - } - } - Primitive* newPrimitive = new Primitive(indexStart, indexCount, vertexCount, primitive.material > -1 ? materials[primitive.material] : materials.back()); - newPrimitive->setBoundingBox(posMin, posMax); - newMesh->primitives.push_back(newPrimitive); - } - // Mesh BB from BBs of primitives - for (auto p : newMesh->primitives) { - if (p->bb.valid && !newMesh->bb.valid) { - newMesh->bb = p->bb; - newMesh->bb.valid = true; - } - newMesh->bb.min = glm::min(newMesh->bb.min, p->bb.min); - newMesh->bb.max = glm::max(newMesh->bb.max, p->bb.max); - } - newNode->mesh = newMesh; - } - if (parent) { - parent->children.push_back(newNode); - } - else { - nodes.push_back(newNode); - } - linearNodes.push_back(newNode); - } - - void Model::getNodeProps(const tinygltf::Node& node, const tinygltf::Model& model, size_t& vertexCount, size_t& indexCount) - { - if (node.children.size() > 0) { - for (size_t i = 0; i < node.children.size(); i++) { - getNodeProps(model.nodes[node.children[i]], model, vertexCount, indexCount); - } - } - if (node.mesh > -1) { - const tinygltf::Mesh mesh = model.meshes[node.mesh]; - for (size_t i = 0; i < mesh.primitives.size(); i++) { - auto primitive = mesh.primitives[i]; - vertexCount += model.accessors[primitive.attributes.find("POSITION")->second].count; - if (primitive.indices > -1) { - indexCount += model.accessors[primitive.indices].count; - } - } - } - } - - void Model::loadSkins(tinygltf::Model& gltfModel) - { - for (tinygltf::Skin& source : gltfModel.skins) { - Skin* newSkin = new Skin{}; - newSkin->name = source.name; - - // Find skeleton root node - if (source.skeleton > -1) { - newSkin->skeletonRoot = nodeFromIndex(source.skeleton); + if (samp.interpolation == "CUBICSPLINE") { + sampler.interpolation = AnimationSampler::InterpolationType::CUBICSPLINE; } - // Find joint nodes - for (int jointIndex : source.joints) { - Node* node = nodeFromIndex(jointIndex); - if (node) { - newSkin->joints.push_back(nodeFromIndex(jointIndex)); - } - } - - // Get inverse bind matrices from buffer - if (source.inverseBindMatrices > -1) { - const tinygltf::Accessor& accessor = gltfModel.accessors[source.inverseBindMatrices]; + // Read sampler input time values + { + const tinygltf::Accessor& accessor = gltfModel.accessors[samp.input]; const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; - newSkin->inverseBindMatrices.resize(accessor.count); - memcpy(newSkin->inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::mat4)); - } - skins.push_back(newSkin); - } - } + assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - void Model::loadTextures(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue) - { - for (tinygltf::Texture& tex : gltfModel.textures) { - tinygltf::Image image = gltfModel.images[tex.source]; - vkglTF::TextureSampler textureSampler; - if (tex.sampler == -1) { - // No sampler specified, use a default one - textureSampler.magFilter = VK_FILTER_LINEAR; - textureSampler.minFilter = VK_FILTER_LINEAR; - textureSampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; - textureSampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - textureSampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - } - else { - textureSampler = textureSamplers[tex.sampler]; - } - vkglTF::Texture texture; - texture.fromglTfImage(image, textureSampler, device, transferQueue); - textures.push_back(texture); - } - } - - VkSamplerAddressMode Model::getVkWrapMode(int32_t wrapMode) - { - switch (wrapMode) { - case -1: - case 10497: - return VK_SAMPLER_ADDRESS_MODE_REPEAT; - case 33071: - return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - case 33648: - return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; - } - - std::cerr << "Unknown wrap mode for getVkWrapMode: " << wrapMode << std::endl; - return VK_SAMPLER_ADDRESS_MODE_REPEAT; - } - - VkFilter Model::getVkFilterMode(int32_t filterMode) - { - switch (filterMode) { - case -1: - case 9728: - return VK_FILTER_NEAREST; - case 9729: - return VK_FILTER_LINEAR; - case 9984: - return VK_FILTER_NEAREST; - case 9985: - return VK_FILTER_NEAREST; - case 9986: - return VK_FILTER_LINEAR; - case 9987: - return VK_FILTER_LINEAR; - } - - std::cerr << "Unknown filter mode for getVkFilterMode: " << filterMode << std::endl; - return VK_FILTER_NEAREST; - } - - void Model::loadTextureSamplers(tinygltf::Model& gltfModel) - { - for (tinygltf::Sampler smpl : gltfModel.samplers) { - vkglTF::TextureSampler sampler{}; - sampler.minFilter = getVkFilterMode(smpl.minFilter); - sampler.magFilter = getVkFilterMode(smpl.magFilter); - sampler.addressModeU = getVkWrapMode(smpl.wrapS); - sampler.addressModeV = getVkWrapMode(smpl.wrapT); - sampler.addressModeW = sampler.addressModeV; - textureSamplers.push_back(sampler); - } - } - - void Model::loadMaterials(tinygltf::Model& gltfModel) - { - for (tinygltf::Material& mat : gltfModel.materials) { - vkglTF::Material material{}; - material.doubleSided = mat.doubleSided; - if (mat.values.find("baseColorTexture") != mat.values.end()) { - material.baseColorTexture = &textures[mat.values["baseColorTexture"].TextureIndex()]; - material.texCoordSets.baseColor = mat.values["baseColorTexture"].TextureTexCoord(); - } - if (mat.values.find("metallicRoughnessTexture") != mat.values.end()) { - material.metallicRoughnessTexture = &textures[mat.values["metallicRoughnessTexture"].TextureIndex()]; - material.texCoordSets.metallicRoughness = mat.values["metallicRoughnessTexture"].TextureTexCoord(); - } - if (mat.values.find("roughnessFactor") != mat.values.end()) { - material.roughnessFactor = static_cast(mat.values["roughnessFactor"].Factor()); - } - if (mat.values.find("metallicFactor") != mat.values.end()) { - material.metallicFactor = static_cast(mat.values["metallicFactor"].Factor()); - } - if (mat.values.find("baseColorFactor") != mat.values.end()) { - material.baseColorFactor = glm::make_vec4(mat.values["baseColorFactor"].ColorFactor().data()); - } - if (mat.additionalValues.find("normalTexture") != mat.additionalValues.end()) { - material.normalTexture = &textures[mat.additionalValues["normalTexture"].TextureIndex()]; - material.texCoordSets.normal = mat.additionalValues["normalTexture"].TextureTexCoord(); - } - if (mat.additionalValues.find("emissiveTexture") != mat.additionalValues.end()) { - material.emissiveTexture = &textures[mat.additionalValues["emissiveTexture"].TextureIndex()]; - material.texCoordSets.emissive = mat.additionalValues["emissiveTexture"].TextureTexCoord(); - } - if (mat.additionalValues.find("occlusionTexture") != mat.additionalValues.end()) { - material.occlusionTexture = &textures[mat.additionalValues["occlusionTexture"].TextureIndex()]; - material.texCoordSets.occlusion = mat.additionalValues["occlusionTexture"].TextureTexCoord(); - } - if (mat.additionalValues.find("alphaMode") != mat.additionalValues.end()) { - tinygltf::Parameter param = mat.additionalValues["alphaMode"]; - if (param.string_value == "BLEND") { - material.alphaMode = Material::ALPHAMODE_BLEND; + float* buf = new float[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(float)); + for (size_t index = 0; index < accessor.count; index++) { + sampler.inputs.push_back(buf[index]); } - if (param.string_value == "MASK") { - material.alphaCutoff = 0.5f; - material.alphaMode = Material::ALPHAMODE_MASK; - } - } - if (mat.additionalValues.find("alphaCutoff") != mat.additionalValues.end()) { - material.alphaCutoff = static_cast(mat.additionalValues["alphaCutoff"].Factor()); - } - if (mat.additionalValues.find("emissiveFactor") != mat.additionalValues.end()) { - material.emissiveFactor = glm::vec4(glm::make_vec3(mat.additionalValues["emissiveFactor"].ColorFactor().data()), 1.0); - } - - // Extensions - // @TODO: Find out if there is a nicer way of reading these properties with recent tinygltf headers - if (mat.extensions.find("KHR_materials_pbrSpecularGlossiness") != mat.extensions.end()) { - auto ext = mat.extensions.find("KHR_materials_pbrSpecularGlossiness"); - if (ext->second.Has("specularGlossinessTexture")) { - auto index = ext->second.Get("specularGlossinessTexture").Get("index"); - material.extension.specularGlossinessTexture = &textures[index.Get()]; - auto texCoordSet = ext->second.Get("specularGlossinessTexture").Get("texCoord"); - material.texCoordSets.specularGlossiness = texCoordSet.Get(); - material.pbrWorkflows.specularGlossiness = true; - } - if (ext->second.Has("diffuseTexture")) { - auto index = ext->second.Get("diffuseTexture").Get("index"); - material.extension.diffuseTexture = &textures[index.Get()]; - } - if (ext->second.Has("diffuseFactor")) { - auto factor = ext->second.Get("diffuseFactor"); - for (uint32_t i = 0; i < factor.ArrayLen(); i++) { - auto val = factor.Get(i); - material.extension.diffuseFactor[i] = val.IsNumber() ? (float)val.Get() : (float)val.Get(); - } - } - if (ext->second.Has("specularFactor")) { - auto factor = ext->second.Get("specularFactor"); - for (uint32_t i = 0; i < factor.ArrayLen(); i++) { - auto val = factor.Get(i); - material.extension.specularFactor[i] = val.IsNumber() ? (float)val.Get() : (float)val.Get(); + delete[] buf; + for (auto input : sampler.inputs) { + if (input < animation.start) { + animation.start = input; + }; + if (input > animation.end) { + animation.end = input; } } } - materials.push_back(material); - } - // Push a default material at the end of the list for meshes with no material assigned - materials.push_back(Material()); - } + // Read sampler output T/R/S values + { + const tinygltf::Accessor& accessor = gltfModel.accessors[samp.output]; + const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; - void Model::loadAnimations(tinygltf::Model& gltfModel) - { - for (tinygltf::Animation& anim : gltfModel.animations) { - vkglTF::Animation animation{}; - animation.name = anim.name; - if (anim.name.empty()) { - animation.name = std::to_string(animations.size()); - } + assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - // Samplers - for (auto& samp : anim.samplers) { - vkglTF::AnimationSampler sampler{}; - - if (samp.interpolation == "LINEAR") { - sampler.interpolation = AnimationSampler::InterpolationType::LINEAR; - } - if (samp.interpolation == "STEP") { - sampler.interpolation = AnimationSampler::InterpolationType::STEP; - } - if (samp.interpolation == "CUBICSPLINE") { - sampler.interpolation = AnimationSampler::InterpolationType::CUBICSPLINE; - } - - // Read sampler input time values - { - const tinygltf::Accessor& accessor = gltfModel.accessors[samp.input]; - const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; - - assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - - const void* dataPtr = &buffer.data[accessor.byteOffset + bufferView.byteOffset]; - const float* buf = static_cast(dataPtr); + switch (accessor.type) { + case TINYGLTF_TYPE_VEC3: { + glm::vec3* buf = new glm::vec3[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec3)); for (size_t index = 0; index < accessor.count; index++) { - sampler.inputs.push_back(buf[index]); + sampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f)); } - - for (auto input : sampler.inputs) { - if (input < animation.start) { - animation.start = input; - }; - if (input > animation.end) { - animation.end = input; - } + delete[] buf; + break; + } + case TINYGLTF_TYPE_VEC4: { + glm::vec4* buf = new glm::vec4[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec4)); + for (size_t index = 0; index < accessor.count; index++) { + sampler.outputsVec4.push_back(buf[index]); } + delete[] buf; + break; } - - // Read sampler output T/R/S values - { - const tinygltf::Accessor& accessor = gltfModel.accessors[samp.output]; - const tinygltf::BufferView& bufferView = gltfModel.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = gltfModel.buffers[bufferView.buffer]; - - assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT); - - const void* dataPtr = &buffer.data[accessor.byteOffset + bufferView.byteOffset]; - - switch (accessor.type) { - case TINYGLTF_TYPE_VEC3: { - const glm::vec3* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) { - sampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f)); - } - break; - } - case TINYGLTF_TYPE_VEC4: { - const glm::vec4* buf = static_cast(dataPtr); - for (size_t index = 0; index < accessor.count; index++) { - sampler.outputsVec4.push_back(buf[index]); - } - break; - } - default: { - std::cout << "unknown type" << std::endl; - break; - } - } + default: { + std::cout << "unknown type" << std::endl; + break; } - - animation.samplers.push_back(sampler); - } - - // Channels - for (auto& source : anim.channels) { - vkglTF::AnimationChannel channel{}; - - if (source.target_path == "rotation") { - channel.path = AnimationChannel::PathType::ROTATION; - } - if (source.target_path == "translation") { - channel.path = AnimationChannel::PathType::TRANSLATION; - } - if (source.target_path == "scale") { - channel.path = AnimationChannel::PathType::SCALE; - } - if (source.target_path == "weights") { - std::cout << "weights not yet supported, skipping channel" << std::endl; - continue; - } - channel.samplerIndex = source.sampler; - channel.node = nodeFromIndex(source.target_node); - if (!channel.node) { - continue; - } - - animation.channels.push_back(channel); - } - - animations.push_back(animation); - } - } - - void Model::loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, float scale) - { - tinygltf::Model gltfModel; - tinygltf::TinyGLTF gltfContext; - - std::string error; - std::string warning; - - this->device = device; - - bool binary = false; - size_t extpos = filename.rfind('.', filename.length()); - if (extpos != std::string::npos) { - binary = (filename.substr(extpos + 1, filename.length() - extpos) == "glb"); - } - - bool fileLoaded = binary ? gltfContext.LoadBinaryFromFile(&gltfModel, &error, &warning, filename.c_str()) : gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename.c_str()); - - LoaderInfo loaderInfo{}; - size_t vertexCount = 0; - size_t indexCount = 0; - - if (fileLoaded) { - loadTextureSamplers(gltfModel); - loadTextures(gltfModel, device, transferQueue); - loadMaterials(gltfModel); - - const tinygltf::Scene& scene = gltfModel.scenes[gltfModel.defaultScene > -1 ? gltfModel.defaultScene : 0]; - - // Get vertex and index buffer sizes up-front - for (size_t i = 0; i < scene.nodes.size(); i++) { - getNodeProps(gltfModel.nodes[scene.nodes[i]], gltfModel, vertexCount, indexCount); - } - loaderInfo.vertexBuffer = new Vertex[vertexCount]; - loaderInfo.indexBuffer = new uint32_t[indexCount]; - - // TODO: scene handling with no default scene - for (size_t i = 0; i < scene.nodes.size(); i++) { - const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]]; - loadNode(nullptr, node, scene.nodes[i], gltfModel, loaderInfo, scale); - } - if (gltfModel.animations.size() > 0) { - loadAnimations(gltfModel); - } - loadSkins(gltfModel); - - for (auto node : linearNodes) { - // Assign skins - if (node->skinIndex > -1) { - node->skin = skins[node->skinIndex]; - } - // Initial pose - if (node->mesh) { - node->update(); } } - } - else { - // TODO: throw - std::cerr << "Could not load gltf file: " << error << std::endl; - return; + + animation.samplers.push_back(sampler); } - extensions = gltfModel.extensionsUsed; + // Channels + for (auto& source : anim.channels) { + glTFModel::AnimationChannel channel{}; - size_t vertexBufferSize = vertexCount * sizeof(Vertex); - size_t indexBufferSize = indexCount * sizeof(uint32_t); - - assert(vertexBufferSize > 0); - - struct StagingBuffer { - VkBuffer buffer; - VkDeviceMemory memory; - } vertexStaging, indexStaging; - - // Create staging buffers - // Vertex data - VK_CHECK_RESULT(device->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - vertexBufferSize, - &vertexStaging.buffer, - &vertexStaging.memory, - loaderInfo.vertexBuffer)); - // Index data - if (indexBufferSize > 0) { - VK_CHECK_RESULT(device->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - indexBufferSize, - &indexStaging.buffer, - &indexStaging.memory, - loaderInfo.indexBuffer)); - } - - // Create device local buffers - // Vertex buffer - VK_CHECK_RESULT(device->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - vertexBufferSize, - &vertices.buffer, - &vertices.memory)); - // Index buffer - if (indexBufferSize > 0) { - VK_CHECK_RESULT(device->createBuffer( - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - indexBufferSize, - &indices.buffer, - &indices.memory)); - } - - // Copy from staging buffers - VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - VkBufferCopy copyRegion = {}; - - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertices.buffer, 1, ©Region); - - if (indexBufferSize > 0) { - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, ©Region); - } - - device->flushCommandBuffer(copyCmd, transferQueue, true); - - vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr); - vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr); - if (indexBufferSize > 0) { - vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr); - vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr); - } - - delete[] loaderInfo.vertexBuffer; - delete[] loaderInfo.indexBuffer; - - getSceneDimensions(); - } - - void Model::drawNode(Node* node, VkCommandBuffer commandBuffer) - { - if (node->mesh) { - for (Primitive* primitive : node->mesh->primitives) { - vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0); + if (source.target_path == "rotation") { + channel.path = AnimationChannel::PathType::ROTATION; } - } - for (auto& child : node->children) { - drawNode(child, commandBuffer); - } - } - - void Model::draw(VkCommandBuffer commandBuffer) - { - const VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); - for (auto& node : nodes) { - drawNode(node, commandBuffer); - } - } - - void Model::calculateBoundingBox(Node* node, Node* parent) { - BoundingBox parentBvh = parent ? parent->bvh : BoundingBox(dimensions.min, dimensions.max); - - if (node->mesh) { - if (node->mesh->bb.valid) { - node->aabb = node->mesh->bb.getAABB(node->getMatrix()); - if (node->children.size() == 0) { - node->bvh.min = node->aabb.min; - node->bvh.max = node->aabb.max; - node->bvh.valid = true; - } + if (source.target_path == "translation") { + channel.path = AnimationChannel::PathType::TRANSLATION; } - } - - parentBvh.min = glm::min(parentBvh.min, node->bvh.min); - parentBvh.max = glm::min(parentBvh.max, node->bvh.max); - - for (auto& child : node->children) { - calculateBoundingBox(child, node); - } - } - - void Model::getSceneDimensions() - { - // Calculate binary volume hierarchy for all nodes in the scene - for (auto node : linearNodes) { - calculateBoundingBox(node, nullptr); - } - - dimensions.min = glm::vec3(FLT_MAX); - dimensions.max = glm::vec3(-FLT_MAX); - - for (auto node : linearNodes) { - if (node->bvh.valid) { - dimensions.min = glm::min(dimensions.min, node->bvh.min); - dimensions.max = glm::max(dimensions.max, node->bvh.max); + if (source.target_path == "scale") { + channel.path = AnimationChannel::PathType::SCALE; } - } - - // Calculate scene aabb - aabb = glm::scale(glm::mat4(1.0f), glm::vec3(dimensions.max[0] - dimensions.min[0], dimensions.max[1] - dimensions.min[1], dimensions.max[2] - dimensions.min[2])); - aabb[3][0] = dimensions.min[0]; - aabb[3][1] = dimensions.min[1]; - aabb[3][2] = dimensions.min[2]; - } - - void Model::updateAnimation(uint32_t index, float time) - { - if (animations.empty()) { - std::cout << ".glTF does not contain animation." << std::endl; - return; - } - if (index > static_cast(animations.size()) - 1) { - std::cout << "No animation with index " << index << std::endl; - return; - } - Animation& animation = animations[index]; - - bool updated = false; - for (auto& channel : animation.channels) { - vkglTF::AnimationSampler& sampler = animation.samplers[channel.samplerIndex]; - if (sampler.inputs.size() > sampler.outputsVec4.size()) { + if (source.target_path == "weights") { + std::cout << "weights not yet supported, skipping channel" << std::endl; + continue; + } + channel.samplerIndex = source.sampler; + channel.node = nodeFromIndex(source.target_node); + if (!channel.node) { continue; } - for (size_t i = 0; i < sampler.inputs.size() - 1; i++) { - if ((time >= sampler.inputs[i]) && (time <= sampler.inputs[i + 1])) { - float u = std::max(0.0f, time - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]); - if (u <= 1.0f) { - switch (channel.path) { - case vkglTF::AnimationChannel::PathType::TRANSLATION: { - glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); - channel.node->translation = glm::vec3(trans); - break; - } - case vkglTF::AnimationChannel::PathType::SCALE: { - glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); - channel.node->scale = glm::vec3(trans); - break; - } - case vkglTF::AnimationChannel::PathType::ROTATION: { - glm::quat q1; - q1.x = sampler.outputsVec4[i].x; - q1.y = sampler.outputsVec4[i].y; - q1.z = sampler.outputsVec4[i].z; - q1.w = sampler.outputsVec4[i].w; - glm::quat q2; - q2.x = sampler.outputsVec4[i + 1].x; - q2.y = sampler.outputsVec4[i + 1].y; - q2.z = sampler.outputsVec4[i + 1].z; - q2.w = sampler.outputsVec4[i + 1].w; - channel.node->rotation = glm::normalize(glm::slerp(q1, q2, u)); - break; - } - } - updated = true; - } - } + animation.channels.push_back(channel); + } + + animations.push_back(animation); + } +} + +/* + glTF material +*/ + +void glTFModel::Model::loadMaterials(tinygltf::Model& gltfModel) +{ + + for (tinygltf::Material& mat : gltfModel.materials) { + glTFModel::Material material(device); + if (mat.values.find("baseColorTexture") != mat.values.end()) { + material.baseColorTexture = getTexture(gltfModel.textures[mat.values["baseColorTexture"].TextureIndex()].source); + } + // Metallic roughness workflow + if (mat.values.find("metallicRoughnessTexture") != mat.values.end()) { + material.metallicRoughnessTexture = getTexture(gltfModel.textures[mat.values["metallicRoughnessTexture"].TextureIndex()].source); + } + if (mat.values.find("roughnessFactor") != mat.values.end()) { + material.roughnessFactor = static_cast(mat.values["roughnessFactor"].Factor()); + } + if (mat.values.find("metallicFactor") != mat.values.end()) { + material.metallicFactor = static_cast(mat.values["metallicFactor"].Factor()); + } + if (mat.values.find("baseColorFactor") != mat.values.end()) { + material.baseColorFactor = glm::make_vec4(mat.values["baseColorFactor"].ColorFactor().data()); + } + if (mat.additionalValues.find("normalTexture") != mat.additionalValues.end()) { + material.normalTexture = getTexture(gltfModel.textures[mat.additionalValues["normalTexture"].TextureIndex()].source); + } + else { + material.normalTexture = &emptyTexture; + } + if (mat.additionalValues.find("emissiveTexture") != mat.additionalValues.end()) { + material.emissiveTexture = getTexture(gltfModel.textures[mat.additionalValues["emissiveTexture"].TextureIndex()].source); + } + if (mat.additionalValues.find("occlusionTexture") != mat.additionalValues.end()) { + material.occlusionTexture = getTexture(gltfModel.textures[mat.additionalValues["occlusionTexture"].TextureIndex()].source); + } + if (mat.additionalValues.find("alphaMode") != mat.additionalValues.end()) { + tinygltf::Parameter param = mat.additionalValues["alphaMode"]; + if (param.string_value == "BLEND") { + material.alphaMode = Material::ALPHAMODE_BLEND; + } + if (param.string_value == "MASK") { + material.alphaMode = Material::ALPHAMODE_MASK; } } - if (updated) { - for (auto& node : nodes) { + if (mat.additionalValues.find("alphaCutoff") != mat.additionalValues.end()) { + material.alphaCutoff = static_cast(mat.additionalValues["alphaCutoff"].Factor()); + } + + materials.push_back(material); + } + // Push a default material at the end of the list for meshes with no material assigned + materials.push_back(Material(device)); +} + + + +void glTFModel::Material::createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags) +{ + VkDescriptorSetAllocateInfo descriptorSetAllocInfo{}; + descriptorSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocInfo.descriptorPool = descriptorPool; + descriptorSetAllocInfo.pSetLayouts = &descriptorSetLayout; + descriptorSetAllocInfo.descriptorSetCount = 1; + VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &descriptorSetAllocInfo, &descriptorSet)); + std::vector imageDescriptors{}; + std::vector writeDescriptorSets{}; + if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { + imageDescriptors.push_back(baseColorTexture->descriptor); + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.dstSet = descriptorSet; + writeDescriptorSet.dstBinding = static_cast(writeDescriptorSets.size()); + writeDescriptorSet.pImageInfo = &baseColorTexture->descriptor; + writeDescriptorSets.push_back(writeDescriptorSet); + } + if (normalTexture && descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { + imageDescriptors.push_back(normalTexture->descriptor); + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.dstSet = descriptorSet; + writeDescriptorSet.dstBinding = static_cast(writeDescriptorSets.size()); + writeDescriptorSet.pImageInfo = &normalTexture->descriptor; + writeDescriptorSets.push_back(writeDescriptorSet); + } + vkUpdateDescriptorSets(device->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); +} + +/* + glTF primitive +*/ +void glTFModel::Primitive::setDimensions(glm::vec3 min, glm::vec3 max) { + dimensions.min = min; + dimensions.max = max; + dimensions.size = max - min; + dimensions.center = (min + max) / 2.0f; + dimensions.radius = glm::distance(min, max) / 2.0f; +} + +/* + glTF mesh +*/ +glTFModel::Mesh::Mesh(vks::VulkanDevice* device, glm::mat4 matrix) { + this->device = device; + this->uniformBlock.matrix = matrix; + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + sizeof(uniformBlock), + &uniformBuffer.buffer, + &uniformBuffer.memory, + &uniformBlock)); + VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, uniformBuffer.memory, 0, sizeof(uniformBlock), 0, &uniformBuffer.mapped)); + uniformBuffer.descriptor = { uniformBuffer.buffer, 0, sizeof(uniformBlock) }; +}; + +glTFModel::Mesh::~Mesh() { + vkDestroyBuffer(device->logicalDevice, uniformBuffer.buffer, nullptr); + vkFreeMemory(device->logicalDevice, uniformBuffer.memory, nullptr); + for (auto primitive : primitives) + { + delete primitive; + } +} +/* + glTF node +*/ +glm::mat4 glTFModel::Node::localMatrix() { + return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix; +} + +glm::mat4 glTFModel::Node::getMatrix() { + glm::mat4 m = localMatrix(); + glTFModel::Node* p = parent; + while (p) { + m = p->localMatrix() * m; + p = p->parent; + } + return m; +} + +void glTFModel::Node::update() { + if (mesh) { + glm::mat4 m = getMatrix(); + if (skin) { + mesh->uniformBlock.matrix = m; + // Update join matrices + glm::mat4 inverseTransform = glm::inverse(m); + for (size_t i = 0; i < skin->joints.size(); i++) { + glTFModel::Node* jointNode = skin->joints[i]; + glm::mat4 jointMat = jointNode->getMatrix() * skin->inverseBindMatrices[i]; + jointMat = inverseTransform * jointMat; + mesh->uniformBlock.jointMatrix[i] = jointMat; + } + mesh->uniformBlock.jointCount = (float)skin->joints.size(); + memcpy(mesh->uniformBuffer.mapped, &mesh->uniformBlock, sizeof(mesh->uniformBlock)); + } + else { + memcpy(mesh->uniformBuffer.mapped, &m, sizeof(glm::mat4)); + } + } + + for (auto& child : children) { + child->update(); + } +} + +glTFModel::Node::~Node() { + if (mesh) { + delete mesh; + } + for (auto& child : children) { + delete child; + } +} + +/* + glTF default vertex layout with easy Vulkan mapping functions +*/ + +VkVertexInputBindingDescription glTFModel::Vertex::vertexInputBindingDescription; +std::vector glTFModel::Vertex::vertexInputAttributeDescriptions; +VkPipelineVertexInputStateCreateInfo glTFModel::Vertex::pipelineVertexInputStateCreateInfo; + +VkVertexInputBindingDescription glTFModel::Vertex::inputBindingDescription(uint32_t binding) { + return VkVertexInputBindingDescription({ binding, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX }); +} + +VkVertexInputAttributeDescription glTFModel::Vertex::inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component) { + switch (component) { + case VertexComponent::Position: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) }); + case VertexComponent::Normal: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) }); + case VertexComponent::UV: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) }); + case VertexComponent::Color: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, color) }); + case VertexComponent::Tangent: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tangent) }); + case VertexComponent::Joint0: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, joint0) }); + case VertexComponent::Weight0: + return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, weight0) }); + default: + return VkVertexInputAttributeDescription({}); + } +} + +std::vector glTFModel::Vertex::inputAttributeDescriptions(uint32_t binding, const std::vector components) { + std::vector result; + uint32_t location = 0; + for (VertexComponent component : components) { + result.push_back(Vertex::inputAttributeDescription(binding, location, component)); + location++; + } + return result; +} + +/* @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */ +VkPipelineVertexInputStateCreateInfo* glTFModel::Vertex::getPipelineVertexInputState(const std::vector components) { + vertexInputBindingDescription = Vertex::inputBindingDescription(0); + Vertex::vertexInputAttributeDescriptions = Vertex::inputAttributeDescriptions(0, components); + pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = 1; + pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = &Vertex::vertexInputBindingDescription; + pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast(Vertex::vertexInputAttributeDescriptions.size()); + pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = Vertex::vertexInputAttributeDescriptions.data(); + return &pipelineVertexInputStateCreateInfo; +} + +glTFModel::Texture* glTFModel::Model::getTexture(uint32_t index) +{ + + if (index < textures.size()) { + return &textures[index]; + } + return nullptr; +} + +void glTFModel::Model::createEmptyTexture(VkQueue transferQueue) +{ + emptyTexture.device = device; + emptyTexture.width = 1; + emptyTexture.height = 1; + emptyTexture.layerCount = 1; + emptyTexture.mipLevels = 1; + + size_t bufferSize = emptyTexture.width * emptyTexture.height * 4; + unsigned char* buffer = new unsigned char[bufferSize]; + memset(buffer, 0, bufferSize); + + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); + bufferCreateInfo.size = bufferSize; + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + + VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); + + // Copy texture data into staging buffer + uint8_t* data; + VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); + memcpy(data, buffer, bufferSize); + vkUnmapMemory(device->logicalDevice, stagingMemory); + + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = emptyTexture.width; + bufferCopyRegion.imageExtent.height = emptyTexture.height; + bufferCopyRegion.imageExtent.depth = 1; + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { emptyTexture.width, emptyTexture.height, 1 }; + imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &emptyTexture.image)); + + vkGetImageMemoryRequirements(device->logicalDevice, emptyTexture.image, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &emptyTexture.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, emptyTexture.image, emptyTexture.deviceMemory, 0)); + + VkImageSubresourceRange subresourceRange{}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = 1; + subresourceRange.layerCount = 1; + + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); + vkCmdCopyBufferToImage(copyCmd, stagingBuffer, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion); + vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange); + device->flushCommandBuffer(copyCmd, transferQueue); + emptyTexture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + // Clean up staging resources + vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); + + VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo(); + samplerCreateInfo.magFilter = VK_FILTER_LINEAR; + samplerCreateInfo.minFilter = VK_FILTER_LINEAR; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; + samplerCreateInfo.maxAnisotropy = 1.0f; + VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &emptyTexture.sampler)); + + VkImageViewCreateInfo viewCreateInfo = vks::initializers::imageViewCreateInfo(); + viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.image = emptyTexture.image; + VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &emptyTexture.view)); + + emptyTexture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + emptyTexture.descriptor.imageView = emptyTexture.view; + emptyTexture.descriptor.sampler = emptyTexture.sampler; +} + +/* + glTF model loading and rendering class +*/ +glTFModel::Model::~Model() +{ + vkDestroyBuffer(device->logicalDevice, vertices.buffer, nullptr); + vkFreeMemory(device->logicalDevice, vertices.memory, nullptr); + vkDestroyBuffer(device->logicalDevice, indices.buffer, nullptr); + vkFreeMemory(device->logicalDevice, indices.memory, nullptr); + for (auto texture : textures) { + texture.destroy(); + } + for (auto node : nodes) { + delete node; + } + for (auto skin : skins) { + delete skin; + } + if (descriptorSetLayoutUbo != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutUbo, nullptr); + descriptorSetLayoutUbo = VK_NULL_HANDLE; + } + if (descriptorSetLayoutImage != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutImage, nullptr); + descriptorSetLayoutImage = VK_NULL_HANDLE; + } + vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr); + emptyTexture.destroy(); +} + +void glTFModel::Model::loadNode(glTFModel::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, std::vector& indexBuffer, std::vector& vertexBuffer, float globalscale) +{ + glTFModel::Node* newNode = new Node{}; + newNode->index = nodeIndex; + newNode->name = node.name; + newNode->skinIndex = node.skin; + newNode->matrix = glm::mat4(1.0f); + newNode->parent = parent; + + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + glm::vec3 translation = glm::vec3(0.0f); + if (node.translation.size() == 3) { + translation = glm::make_vec3(node.translation.data()); + newNode->translation = translation; + } + glm::mat4 rotation = glm::mat4(1.0f); + if (node.rotation.size() == 4) { + glm::quat q = glm::make_quat(node.rotation.data()); + newNode->rotation = glm::mat4(q); + } + glm::vec3 scale = glm::vec3(1.0f); + if (node.scale.size() == 3) { + scale = glm::make_vec3(node.scale.data()); + newNode->scale = scale; + } + if (node.matrix.size() == 16) { + newNode->matrix = glm::make_mat4x4(node.matrix.data()); + if (globalscale != 1.0f) { + //newNode->matrix = glm::scale(newNode->matrix, glm::vec3(globalscale)); + } + }; + + // Load node's children + if (node.children.size() > 0) { + for (size_t i = 0; i < node.children.size(); i++) { + loadNode(newNode, model.nodes[node.children[i]],node.children[i],model,indexBuffer,vertexBuffer,globalscale); + } + } + + // If the node contains mesh data, we load vertices and indices from the buffers + // In glTF this is done via accessors and buffer views + if (node.mesh > -1) { + const tinygltf::Mesh mesh = model.meshes[node.mesh]; + + Mesh* newMesh = new Mesh(device, newNode->matrix); + newMesh->name = mesh.name; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& primitive = mesh.primitives[i]; + if (primitive.indices < 0) + { + continue; + } + uint32_t firstIndex = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; + uint32_t vertexCount = 0; + glm::vec3 posMin{}; + glm::vec3 posMax{}; + bool hasSkin = false; + // Vertices + { + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + const float* colorBuffer = nullptr; + const float* tangentsBuffer = nullptr; + uint32_t colorComponentsNum; + const uint16_t* jointsBuffer = nullptr; + const float* weightsBuffer = nullptr; + + + + // Get buffer data for vertex positions + if (primitive.attributes.find("POSITION") != primitive.attributes.end()) { + const tinygltf::Accessor& positionAccessor = model.accessors[primitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = model.bufferViews[positionAccessor.bufferView]; + positionBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[positionAccessor.byteOffset + view.byteOffset])); + posMin = glm::vec3(positionAccessor.minValues[0], positionAccessor.minValues[1], positionAccessor.minValues[2]); + posMax = glm::vec3(positionAccessor.maxValues[0], positionAccessor.maxValues[1], positionAccessor.maxValues[2]); + vertexCount = static_cast(positionAccessor.count); + } + // Get buffer data for vertex normals + if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { + const tinygltf::Accessor& normalAccessor = model.accessors[primitive.attributes.find("NORMAL")->second]; + const tinygltf::BufferView& view = model.bufferViews[normalAccessor.bufferView]; + normalsBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[normalAccessor.byteOffset + view.byteOffset])); + } + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) { + const tinygltf::Accessor& texcoordAccessor = model.accessors[primitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = model.bufferViews[texcoordAccessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[texcoordAccessor.byteOffset + view.byteOffset])); + } + // material tangent + if (primitive.attributes.find("TANGENT") != primitive.attributes.end()) + { + const tinygltf::Accessor& tangentAccessor = model.accessors[primitive.attributes.find("TANGENT")->second]; + const tinygltf::BufferView& view = model.bufferViews[tangentAccessor.bufferView]; + tangentsBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[tangentAccessor.byteOffset + view.byteOffset])); + } + //color + if (primitive.attributes.find("COLOR_0") != primitive.attributes.end()) + { + const tinygltf::Accessor& colorAccessor = model.accessors[primitive.attributes.find("COLOR_0")->second]; + const tinygltf::BufferView& view = model.bufferViews[colorAccessor.bufferView]; + colorBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[colorAccessor.byteOffset + view.byteOffset])); + } + // skin joints + if (primitive.attributes.find("JOINTS_0") != primitive.attributes.end()) + { + const tinygltf::Accessor& jointsAccessor = model.accessors[primitive.attributes.find("JOINTS_0")->second]; + const tinygltf::BufferView& view = model.bufferViews[jointsAccessor.bufferView]; + jointsBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[jointsAccessor.byteOffset + view.byteOffset])); + } + // skin weights + if (primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end()) + { + const tinygltf::Accessor& weightsAccessor = model.accessors[primitive.attributes.find("WEIGHTSS_0")->second]; + const tinygltf::BufferView& view = model.bufferViews[weightsAccessor.bufferView]; + weightsBuffer = reinterpret_cast(&(model.buffers[view.buffer].data[weightsAccessor.byteOffset + view.byteOffset])); + } + + hasSkin = (jointsBuffer && weightsBuffer); + + + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); + vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + if (colorBuffer) + { + switch (colorComponentsNum) + { + case 3 : + vert.color = glm::vec4(glm::make_vec3(&colorBuffer[v * 3]), 1.0f); + case 4 : + vert.color = glm::make_vec4(&colorBuffer[v * 4]); + + } + } + else + { + vert.color = glm::vec4(1.0f); + } + vert.tangent = tangentsBuffer ? glm::vec4(glm::make_vec4(&tangentsBuffer[v * 4])) : glm::vec4(0.0f); + vert.joint0 = hasSkin ? glm::vec4(glm::make_vec4(&jointsBuffer[v * 4])) : glm::vec4(0.0f); + vert.weight0 = hasSkin ? glm::make_vec4(&weightsBuffer[v * 4]) : glm::vec4(0.0f); + + vertexBuffer.push_back(vert); + } + } + // Indices + { + const tinygltf::Accessor& accessor = model.accessors[primitive.indices]; + const tinygltf::BufferView& bufferView = model.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = model.buffers[bufferView.buffer]; + + indexCount += static_cast(accessor.count); + + // glTF supports different component types of indices + switch (accessor.componentType) { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { + uint32_t* buf = new uint32_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + delete[] buf; + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + uint16_t* buf = new uint16_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + delete[] buf; + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { + uint8_t* buf = new uint8_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + delete[] buf; + break; + } + default: + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; + return; + } + } + Primitive* newPrimitive = new Primitive(firstIndex, indexCount, primitive.material > -1 ? materials[primitive.material] : materials.back()); + newPrimitive->firstVertex = vertexStart; + newPrimitive->vertexCount = vertexCount; + newPrimitive->setDimensions(posMin, posMax); + newMesh->primitives.push_back(newPrimitive); + } + newNode -> mesh = newMesh; + } + + if (parent) { + parent->children.push_back(newNode); + } + else { + nodes.push_back(newNode); + } + linearNodes.push_back(newNode); +} + +// file loader function +void glTFModel::Model::loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags, float scale) +{ + tinygltf::Model gltfModel; + tinygltf::TinyGLTF gltfContext; + if (fileLoadingFlags & FileLoadingFlags::DontLoadImages) { + gltfContext.SetImageLoader(loadImageDataFuncEmpty, nullptr); + } + else { + gltfContext.SetImageLoader(loadImageDataFunc, nullptr); + } +#if defined(__ANDROID__) + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager + // We let tinygltf handle this, by passing the asset manager of our app + tinygltf::asset_manager = androidApp->activity->assetManager; +#endif + size_t pos = filename.find_last_of('/'); + path = filename.substr(0, pos); + + std::string error, warning; + + this->device = device; + +#if defined(__ANDROID__) + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager + // We let tinygltf handle this, by passing the asset manager of our app + tinygltf::asset_manager = androidApp->activity->assetManager; +#endif + bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename); + + std::vector indexBuffer; + std::vector vertexBuffer; + + if (fileLoaded) { + if (!(fileLoadingFlags & FileLoadingFlags::DontLoadImages)) { + loadImages(gltfModel, device, transferQueue); + } + loadMaterials(gltfModel); + const tinygltf::Scene& scene = gltfModel.scenes[gltfModel.defaultScene > -1 ? gltfModel.defaultScene : 0]; + for (size_t i = 0; i < scene.nodes.size(); i++) { + const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]]; + loadNode(nullptr, node, scene.nodes[i], gltfModel, indexBuffer, vertexBuffer, scale); + } + if (gltfModel.animations.size() > 0) { + loadAnimations(gltfModel); + } + loadSkins(gltfModel); + + for (auto node : linearNodes) { + // Assign skins + if (node->skinIndex > -1) { + node->skin = skins[node->skinIndex]; + } + // Initial pose + if (node->mesh) { node->update(); } } } - - Node* Model::findNode(Node* parent, uint32_t index) { - Node* nodeFound = nullptr; - if (parent->index == index) { - return parent; - } - for (auto& child : parent->children) { - nodeFound = findNode(child, index); - if (nodeFound) { - break; - } - } - return nodeFound; + else { + // TODO: throw + vks::tools::exitFatal("Could not load glTF file \"" + filename + "\": " + error, -1); + return; } - Node* Model::nodeFromIndex(uint32_t index) { - Node* nodeFound = nullptr; - for (auto& node : nodes) { - nodeFound = findNode(node, index); - if (nodeFound) { - break; + // Pre-Calculations for requested features + if ((fileLoadingFlags & FileLoadingFlags::PreTransformVertices) || (fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors) || (fileLoadingFlags & FileLoadingFlags::FlipY)) { + const bool preTransform = fileLoadingFlags & FileLoadingFlags::PreTransformVertices; + const bool preMultiplyColor = fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors; + const bool flipY = fileLoadingFlags & FileLoadingFlags::FlipY; + for (Node* node : linearNodes) { + if (node->mesh) { + const glm::mat4 localMatrix = node->getMatrix(); + for (Primitive* primitive : node->mesh->primitives) { + for (uint32_t i = 0; i < primitive->vertexCount; i++) { + Vertex& vertex = vertexBuffer[primitive->firstVertex + i]; + // Pre-transform vertex positions by node-hierarchy + if (preTransform) { + vertex.pos = glm::vec3(localMatrix * glm::vec4(vertex.pos, 1.0f)); + vertex.normal = glm::normalize(glm::mat3(localMatrix) * vertex.normal); + } + // Flip Y-Axis of vertex positions + if (flipY) { + vertex.pos.y *= -1.0f; + vertex.normal.y *= -1.0f; + } + // Pre-Multiply vertex colors with material base color + if (preMultiplyColor) { + vertex.color = primitive->material.baseColorFactor * vertex.color; + } + } + } } } - return nodeFound; } + for (auto extension : gltfModel.extensionsUsed) { + if (extension == "KHR_materials_pbrSpecularGlossiness") { + std::cout << "Required extension: " << extension; + metallicRoughnessWorkflow = false; + } + } + + size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); + size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); + indices.count = static_cast(indexBuffer.size()); + vertices.count = static_cast(vertexBuffer.size()); + + assert((vertexBufferSize > 0) && (indexBufferSize > 0)); + + struct StagingBuffer { + VkBuffer buffer; + VkDeviceMemory memory; + } vertexStaging, indexStaging; + + // Create staging buffers + // Vertex data + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vertexBufferSize, + &vertexStaging.buffer, + &vertexStaging.memory, + vertexBuffer.data())); + // Index data + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + indexBufferSize, + &indexStaging.buffer, + &indexStaging.memory, + indexBuffer.data())); + + // Create device local buffers + // Vertex buffer + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + vertexBufferSize, + &vertices.buffer, + &vertices.memory)); + // Index buffer + VK_CHECK_RESULT(device->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + indexBufferSize, + &indices.buffer, + &indices.memory)); + + // Copy from staging buffers + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkBufferCopy copyRegion = {}; + + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertices.buffer, 1, ©Region); + + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, ©Region); + + device->flushCommandBuffer(copyCmd, transferQueue, true); + + vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr); + vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr); + vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr); + vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr); + + getSceneDimensions(); + + // Setup descriptors + uint32_t uboCount{ 0 }; + uint32_t imageCount{ 0 }; + for (auto node : linearNodes) { + if (node->mesh) { + uboCount++; + } + } + for (auto material : materials) { + if (material.baseColorTexture != nullptr) { + imageCount++; + } + } + std::vector poolSizes = { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uboCount }, + }; + if (imageCount > 0) { + if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount }); + } + if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { + poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount }); + } + } + VkDescriptorPoolCreateInfo descriptorPoolCI{}; + descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolCI.poolSizeCount = static_cast(poolSizes.size()); + descriptorPoolCI.pPoolSizes = poolSizes.data(); + descriptorPoolCI.maxSets = uboCount + imageCount; + VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolCI, nullptr, &descriptorPool)); + + // Descriptors for per-node uniform buffers + { + // Layout is global, so only create if it hasn't already been created before + if (descriptorSetLayoutUbo == VK_NULL_HANDLE) { + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), + }; + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; + descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorLayoutCI.bindingCount = static_cast(setLayoutBindings.size()); + descriptorLayoutCI.pBindings = setLayoutBindings.data(); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutUbo)); + } + for (auto node : nodes) { + prepareNodeDescriptor(node, descriptorSetLayoutUbo); + } + } + + // Descriptors for per-material images + { + // Layout is global, so only create if it hasn't already been created before + if (descriptorSetLayoutImage == VK_NULL_HANDLE) { + std::vector setLayoutBindings{}; + if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) { + setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast(setLayoutBindings.size()))); + } + if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) { + setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast(setLayoutBindings.size()))); + } + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{}; + descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptorLayoutCI.bindingCount = static_cast(setLayoutBindings.size()); + descriptorLayoutCI.pBindings = setLayoutBindings.data(); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutImage)); + } + for (auto& material : materials) { + if (material.baseColorTexture != nullptr) { + material.createDescriptorSet(descriptorPool, glTFModel::descriptorSetLayoutImage, descriptorBindingFlags); + } + } + } } + +void glTFModel::Model::bindBuffers(VkCommandBuffer commandBuffer) +{ + const VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); + buffersBound = true; +} + + +/* + helper function +*/ + +void glTFModel::Model::getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max) +{ + if (node->mesh) { + for (Primitive* primitive : node->mesh->primitives) { + glm::vec4 locMin = glm::vec4(primitive->dimensions.min, 1.0f) * node->getMatrix(); + glm::vec4 locMax = glm::vec4(primitive->dimensions.max, 1.0f) * node->getMatrix(); + if (locMin.x < min.x) { min.x = locMin.x; } + if (locMin.y < min.y) { min.y = locMin.y; } + if (locMin.z < min.z) { min.z = locMin.z; } + if (locMax.x > max.x) { max.x = locMax.x; } + if (locMax.y > max.y) { max.y = locMax.y; } + if (locMax.z > max.z) { max.z = locMax.z; } + } + } + for (auto child : node->children) { + getNodeDimensions(child, min, max); + } +} + +void glTFModel::Model::getSceneDimensions() +{ + dimensions.min = glm::vec3(FLT_MAX); + dimensions.max = glm::vec3(-FLT_MAX); + for (auto node : nodes) { + getNodeDimensions(node, dimensions.min, dimensions.max); + } + dimensions.size = dimensions.max - dimensions.min; + dimensions.center = (dimensions.min + dimensions.max) / 2.0f; + dimensions.radius = glm::distance(dimensions.min, dimensions.max) / 2.0f; +} + +glTFModel::Node* glTFModel::Model::findNode(Node* parent, uint32_t index) +{ + Node* nodeFound = nullptr; + if (parent->index == index) + { + return parent; + } + for (auto& child : parent->children) + { + nodeFound = findNode(child, index); + if (nodeFound) + { + break; + } + } + return nodeFound; +} + +glTFModel::Node* glTFModel::Model::nodeFromIndex(uint32_t index) +{ + Node* nodeFound = nullptr; + for (auto& node : nodes) + { + nodeFound = findNode(node, index); + if (nodeFound) + { + break; + } + } + return nodeFound; +} + +/* + + gltf update function + +*/ + + +void glTFModel::Model::updateAnimation(uint32_t index, float time) +{ + if (index > static_cast(animations.size()) - 1) { + std::cout << "No animation with index " << index << std::endl; + return; + } + Animation& animation = animations[index]; + + bool updated = false; + for (auto& channel : animation.channels) { + glTFModel::AnimationSampler& sampler = animation.samplers[channel.samplerIndex]; + if (sampler.inputs.size() > sampler.outputsVec4.size()) { + continue; + } + + for (auto i = 0; i < sampler.inputs.size() - 1; i++) { + if ((time >= sampler.inputs[i]) && (time <= sampler.inputs[i + 1])) { + float u = std::max(0.0f, time - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]); + if (u <= 1.0f) { + switch (channel.path) { + case glTFModel::AnimationChannel::PathType::TRANSLATION: { + glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->translation = glm::vec3(trans); + break; + } + case glTFModel::AnimationChannel::PathType::SCALE: { + glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u); + channel.node->scale = glm::vec3(trans); + break; + } + case glTFModel::AnimationChannel::PathType::ROTATION: { + glm::quat q1; + q1.x = sampler.outputsVec4[i].x; + q1.y = sampler.outputsVec4[i].y; + q1.z = sampler.outputsVec4[i].z; + q1.w = sampler.outputsVec4[i].w; + glm::quat q2; + q2.x = sampler.outputsVec4[i + 1].x; + q2.y = sampler.outputsVec4[i + 1].y; + q2.z = sampler.outputsVec4[i + 1].z; + q2.w = sampler.outputsVec4[i + 1].w; + channel.node->rotation = glm::normalize(glm::slerp(q1, q2, u)); + break; + } + } + updated = true; + } + } + } + } + if (updated) { + for (auto& node : nodes) { + node->update(); + } + } + +} + +/* +void VulkanglTFModel::updateNodeMatrix(Node* node, std::vector& nodeMatrics) +{ + nodeMatrics[node->index] = getNodeMatrix(node); + for (auto& child : node->children) + { + updateNodeMatrix(child, nodeMatrics); + } +} + +glm::mat4 VulkanglTFModel::getNodeMatrix(Node* node) +{ + glm::mat4 nodeMatrix = node->getLocalMatrix(); + Node* currentParent = node->parent; + while (currentParent) + { + nodeMatrix = currentParent->getLocalMatrix() * nodeMatrix; + currentParent = currentParent->parent; + } + return nodeMatrix; +} +*/ + +void glTFModel::Model::prepareNodeDescriptor(glTFModel::Node* node, VkDescriptorSetLayout descriptorSetLayout) { + if (node->mesh) { + VkDescriptorSetAllocateInfo descriptorSetAllocInfo{}; + descriptorSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptorSetAllocInfo.descriptorPool = descriptorPool; + descriptorSetAllocInfo.pSetLayouts = &descriptorSetLayout; + descriptorSetAllocInfo.descriptorSetCount = 1; + VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &descriptorSetAllocInfo, &node->mesh->uniformBuffer.descriptorSet)); + + VkWriteDescriptorSet writeDescriptorSet{}; + writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writeDescriptorSet.descriptorCount = 1; + writeDescriptorSet.dstSet = node->mesh->uniformBuffer.descriptorSet; + writeDescriptorSet.dstBinding = 0; + writeDescriptorSet.pBufferInfo = &node->mesh->uniformBuffer.descriptor; + + vkUpdateDescriptorSets(device->logicalDevice, 1, &writeDescriptorSet, 0, nullptr); + } + for (auto& child : node->children) { + prepareNodeDescriptor(child, descriptorSetLayout); + } +} + +/* + glTF rendering functions +*/ + +void glTFModel::Model::drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) +{ + if (node->mesh) { + for (Primitive* primitive : node->mesh->primitives) { + bool skip = false; + const glTFModel::Material& material = primitive->material; + if (renderFlags & RenderFlags::RenderOpaqueNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_OPAQUE); + } + if (renderFlags & RenderFlags::RenderAlphaMaskedNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_MASK); + } + if (renderFlags & RenderFlags::RenderAlphaBlendedNodes) { + skip = (material.alphaMode != Material::ALPHAMODE_BLEND); + } + if (!skip) { + if (renderFlags & RenderFlags::BindImages) { + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, bindImageSet, 1, &material.descriptorSet, 0, nullptr); + } + vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0); + } + } + } + for (auto& child : node->children) { + drawNode(child, commandBuffer, renderFlags, pipelineLayout, bindImageSet); + } +} + +// Draw the glTF scene starting at the top-level-nodes +void glTFModel::Model::draw(VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet) +{ + if (!buffersBound) { + const VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); + } + for (auto& node : nodes) { + drawNode(node, commandBuffer, renderFlags, pipelineLayout, bindImageSet); + } +} \ No newline at end of file diff --git a/src/render/glTFModel.h b/src/render/glTFModel.h index 1489e71..1fda6b7 100644 --- a/src/render/glTFModel.h +++ b/src/render/glTFModel.h @@ -34,27 +34,35 @@ namespace glTFModel { - struct Node; - - struct BoundingBox { - glm::vec3 min; - glm::vec3 max; - bool valid = false; - BoundingBox(); - BoundingBox(glm::vec3 min, glm::vec3 max); - BoundingBox getAABB(glm::mat4 m); + enum DescriptorBindingFlags { + ImageBaseColor = 0x00000001, + ImageNormalMap = 0x00000002 }; - struct TextureSampler { - VkFilter magFilter; - VkFilter minFilter; - VkSamplerAddressMode addressModeU; - VkSamplerAddressMode addressModeV; - VkSamplerAddressMode addressModeW; + enum FileLoadingFlags { + None = 0x00000000, + PreTransformVertices = 0x00000001, + PreMultiplyVertexColors = 0x00000002, + FlipY = 0x00000004, + DontLoadImages = 0x00000008 }; + enum RenderFlags { + BindImages = 0x00000001, + RenderOpaqueNodes = 0x00000002, + RenderAlphaMaskedNodes = 0x00000004, + RenderAlphaBlendedNodes = 0x00000008 + }; + + extern VkDescriptorSetLayout descriptorSetLayoutImage; + extern VkDescriptorSetLayout descriptorSetLayoutUbo; + extern VkMemoryPropertyFlags memoryPropertyFlags; + extern uint32_t descriptorBindingFlags; + + // A glTF texture stores a reference to the image and a sampler struct Texture { - vks::VulkanDevice* device; + int32_t imageIndex; + vks::VulkanDevice* device = nullptr; VkImage image; VkImageLayout imageLayout; VkDeviceMemory deviceMemory; @@ -66,85 +74,96 @@ namespace glTFModel VkSampler sampler; void updateDescriptor(); void destroy(); - // Load a texture from a glTF image (stored as vector of chars loaded via stb_image) and generate a full mip chaing for it - void fromglTfImage(tinygltf::Image& gltfimage, TextureSampler textureSampler, vks::VulkanDevice* device, VkQueue copyQueue); + void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue); + }; + // A glTF material stores information in e.g. the texture that is attached to it and colors struct Material { + vks::VulkanDevice* device = nullptr; enum AlphaMode { ALPHAMODE_OPAQUE, ALPHAMODE_MASK, ALPHAMODE_BLEND }; AlphaMode alphaMode = ALPHAMODE_OPAQUE; float alphaCutoff = 1.0f; float metallicFactor = 1.0f; float roughnessFactor = 1.0f; glm::vec4 baseColorFactor = glm::vec4(1.0f); - glm::vec4 emissiveFactor = glm::vec4(1.0f); - glTFModel::Texture* baseColorTexture; - glTFModel::Texture* metallicRoughnessTexture; - glTFModel::Texture* normalTexture; - glTFModel::Texture* occlusionTexture; - glTFModel::Texture* emissiveTexture; - bool doubleSided = false; - struct TexCoordSets { - uint8_t baseColor = 0; - uint8_t metallicRoughness = 0; - uint8_t specularGlossiness = 0; - uint8_t normal = 0; - uint8_t occlusion = 0; - uint8_t emissive = 0; - } texCoordSets; - struct Extension { - glTFModel::Texture* specularGlossinessTexture; - glTFModel::Texture* diffuseTexture; - glm::vec4 diffuseFactor = glm::vec4(1.0f); - glm::vec3 specularFactor = glm::vec3(0.0f); - } extension; - struct PbrWorkflows { - bool metallicRoughness = true; - bool specularGlossiness = false; - } pbrWorkflows; + glTFModel::Texture* baseColorTexture = nullptr; + glTFModel::Texture* metallicRoughnessTexture = nullptr; + glTFModel::Texture* normalTexture = nullptr; + glTFModel::Texture* occlusionTexture = nullptr; + glTFModel::Texture* emissiveTexture = nullptr; + + glTFModel::Texture* specularGlossinessTexture; + glTFModel::Texture* diffuseTexture; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + + Material(vks::VulkanDevice* device) : device(device) {}; + void createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags); + }; + // A primitive contains the data for a single draw call struct Primitive { uint32_t firstIndex; uint32_t indexCount; + uint32_t firstVertex; uint32_t vertexCount; Material& material; - bool hasIndices; - BoundingBox bb; - Primitive(uint32_t firstIndex, uint32_t indexCount, uint32_t vertexCount, Material& material); - void setBoundingBox(glm::vec3 min, glm::vec3 max); + + struct Dimensions { + glm::vec3 min = glm::vec3(FLT_MAX); + glm::vec3 max = glm::vec3(-FLT_MAX); + glm::vec3 size; + glm::vec3 center; + float radius; + } dimensions; + + void setDimensions(glm::vec3 min, glm::vec3 max); + Primitive(uint32_t firstIndex, uint32_t indexCount, Material& material) : firstIndex(firstIndex), indexCount(indexCount), material(material) {}; + + }; + // Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives struct Mesh { vks::VulkanDevice* device; + std::vector primitives; - BoundingBox bb; - BoundingBox aabb; + std::string name; + struct UniformBuffer { VkBuffer buffer; VkDeviceMemory memory; VkDescriptorBufferInfo descriptor; - VkDescriptorSet descriptorSet; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; void* mapped; } uniformBuffer; + struct UniformBlock { glm::mat4 matrix; - glm::mat4 jointMatrix[128]{}; - float jointcount{ 0 }; + glm::mat4 jointMatrix[64]{}; + float jointCount{ 0 }; } uniformBlock; + Mesh(vks::VulkanDevice* device, glm::mat4 matrix); ~Mesh(); - void setBoundingBox(glm::vec3 min, glm::vec3 max); - }; + + }; + + struct Node; + + /* + glTF skin + */ struct Skin { std::string name; Node* skeletonRoot = nullptr; std::vector inverseBindMatrices; std::vector joints; }; - + // A node represents an object in the glTF scene graph struct Node { Node* parent; uint32_t index; @@ -157,102 +176,133 @@ namespace glTFModel glm::vec3 translation{}; glm::vec3 scale{ 1.0f }; glm::quat rotation{}; - BoundingBox bvh; - BoundingBox aabb; glm::mat4 localMatrix(); glm::mat4 getMatrix(); void update(); ~Node(); + + + }; - struct AnimationChannel { - enum PathType { TRANSLATION, ROTATION, SCALE }; - PathType path; - Node* node; - uint32_t samplerIndex; + /* + glTF default vertex layout with easy Vulkan mapping functions + */ + enum class VertexComponent { Position, Normal, UV, Color, Tangent, Joint0, Weight0 }; + + // The vertex layout for the samples' model + struct Vertex { + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv; + glm::vec4 color; + glm::vec4 joint0; + glm::vec4 weight0; + glm::vec4 tangent; + static VkVertexInputBindingDescription vertexInputBindingDescription; + static std::vector vertexInputAttributeDescriptions; + static VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo; + static VkVertexInputBindingDescription inputBindingDescription(uint32_t binding); + static VkVertexInputAttributeDescription inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component); + static std::vector inputAttributeDescriptions(uint32_t binding, const std::vector components); + /** @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */ + static VkPipelineVertexInputStateCreateInfo* getPipelineVertexInputState(const std::vector components); + }; - struct AnimationSampler { + + + + struct AnimationSampler + { enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; InterpolationType interpolation; std::vector inputs; std::vector outputsVec4; + }; - struct Animation { + struct AnimationChannel + { + enum PathType { TRANSLATION, ROTATION, SCALE }; + PathType path; + Node* node; + uint32_t samplerIndex; + + }; + + struct Animation + { std::string name; std::vector samplers; std::vector channels; + float start = std::numeric_limits::max(); float end = std::numeric_limits::min(); + float currentTime = 0.0f; }; - struct Model { - + /* + glTF model loading and rendering class + */ + class Model { + private: + glTFModel::Texture* getTexture(uint32_t index); + glTFModel::Texture emptyTexture; + void createEmptyTexture(VkQueue transferQueue); + public: vks::VulkanDevice* device; - - struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv0; - glm::vec2 uv1; - glm::vec4 joint0; - glm::vec4 weight0; - glm::vec4 color; - }; + VkDescriptorPool descriptorPool; struct Vertices { - VkBuffer buffer = VK_NULL_HANDLE; + int count; + VkBuffer buffer; VkDeviceMemory memory; } vertices; struct Indices { - VkBuffer buffer = VK_NULL_HANDLE; + int count; + VkBuffer buffer; VkDeviceMemory memory; } indices; - glm::mat4 aabb; - std::vector nodes; std::vector linearNodes; std::vector skins; std::vector textures; - std::vector textureSamplers; std::vector materials; std::vector animations; - std::vector extensions; struct Dimensions { glm::vec3 min = glm::vec3(FLT_MAX); glm::vec3 max = glm::vec3(-FLT_MAX); + glm::vec3 size; + glm::vec3 center; + float radius; } dimensions; - struct LoaderInfo { - uint32_t* indexBuffer; - Vertex* vertexBuffer; - size_t indexPos = 0; - size_t vertexPos = 0; - }; + bool metallicRoughnessWorkflow = true; + bool buffersBound = false; + std::string path; - void destroy(VkDevice device); - void loadNode(glTFModel::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, LoaderInfo& loaderInfo, float globalscale); - void getNodeProps(const tinygltf::Node& node, const tinygltf::Model& model, size_t& vertexCount, size_t& indexCount); + Model() {}; + ~Model(); + void loadNode(glTFModel::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, std::vector& indexBuffer, std::vector& vertexBuffer, float globalscale); void loadSkins(tinygltf::Model& gltfModel); - void loadTextures(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue); - VkSamplerAddressMode getVkWrapMode(int32_t wrapMode); - VkFilter getVkFilterMode(int32_t filterMode); - void loadTextureSamplers(tinygltf::Model& gltfModel); + void loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue); void loadMaterials(tinygltf::Model& gltfModel); void loadAnimations(tinygltf::Model& gltfModel); - void loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, float scale = 1.0f); - void drawNode(Node* node, VkCommandBuffer commandBuffer); - void draw(VkCommandBuffer commandBuffer); - void calculateBoundingBox(Node* node, Node* parent); + void loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags = glTFModel::FileLoadingFlags::None, float scale = 1.0f); + void bindBuffers(VkCommandBuffer commandBuffer); + void drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1); + void draw(VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1); + void getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max); void getSceneDimensions(); void updateAnimation(uint32_t index, float time); Node* findNode(Node* parent, uint32_t index); Node* nodeFromIndex(uint32_t index); + void prepareNodeDescriptor(glTFModel::Node* node, VkDescriptorSetLayout descriptorSetLayout); }; diff --git a/src/render/render.cpp b/src/render/render.cpp index e7593ca..5914d1f 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -49,83 +49,7 @@ PlumageRender::PlumageRender(): }; } - void PlumageRender::renderNode(glTFModel::Node* node, uint32_t cbIndex, glTFModel::Material::AlphaMode alphaMode) { - if (node->mesh) { - // Render mesh primitives - for (glTFModel::Primitive* primitive : node->mesh->primitives) { - if (primitive->material.alphaMode == alphaMode) { - - VkPipeline pipeline = VK_NULL_HANDLE; - switch (alphaMode) { - case glTFModel::Material::ALPHAMODE_OPAQUE: - case glTFModel::Material::ALPHAMODE_MASK: - pipeline = primitive->material.doubleSided ? pipelines.pbrDoubleSided : pipelines.pbr; - break; - case glTFModel::Material::ALPHAMODE_BLEND: - pipeline = pipelines.pbrAlphaBlend; - break; - } - - if (pipeline != boundPipeline) { - vkCmdBindPipeline(commandBuffers[cbIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - boundPipeline = pipeline; - } - - const std::vector descriptorsets = { - descriptorSets[cbIndex].scene, - primitive->material.descriptorSet, - node->mesh->uniformBuffer.descriptorSet, - }; - vkCmdBindDescriptorSets(commandBuffers[cbIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast(descriptorsets.size()), descriptorsets.data(), 0, NULL); - - // Pass material parameters as push constants - PushConstBlockMaterial pushConstBlockMaterial{}; - pushConstBlockMaterial.emissiveFactor = primitive->material.emissiveFactor; - // To save push constant space, availabilty and texture coordiante set are combined - // -1 = texture not used for this material, >= 0 texture used and index of texture coordinate set - pushConstBlockMaterial.colorTextureSet = primitive->material.baseColorTexture != nullptr ? primitive->material.texCoordSets.baseColor : -1; - pushConstBlockMaterial.normalTextureSet = primitive->material.normalTexture != nullptr ? primitive->material.texCoordSets.normal : -1; - pushConstBlockMaterial.occlusionTextureSet = primitive->material.occlusionTexture != nullptr ? primitive->material.texCoordSets.occlusion : -1; - pushConstBlockMaterial.emissiveTextureSet = primitive->material.emissiveTexture != nullptr ? primitive->material.texCoordSets.emissive : -1; - pushConstBlockMaterial.alphaMask = static_cast(primitive->material.alphaMode == vkglTF::Material::ALPHAMODE_MASK); - pushConstBlockMaterial.alphaMaskCutoff = primitive->material.alphaCutoff; - - // TODO: glTF specs states that metallic roughness should be preferred, even if specular glosiness is present - - if (primitive->material.pbrWorkflows.metallicRoughness) { - // Metallic roughness workflow - pushConstBlockMaterial.workflow = static_cast(PBR_WORKFLOW_METALLIC_ROUGHNESS); - pushConstBlockMaterial.baseColorFactor = primitive->material.baseColorFactor; - pushConstBlockMaterial.metallicFactor = primitive->material.metallicFactor; - pushConstBlockMaterial.roughnessFactor = primitive->material.roughnessFactor; - pushConstBlockMaterial.PhysicalDescriptorTextureSet = primitive->material.metallicRoughnessTexture != nullptr ? primitive->material.texCoordSets.metallicRoughness : -1; - pushConstBlockMaterial.colorTextureSet = primitive->material.baseColorTexture != nullptr ? primitive->material.texCoordSets.baseColor : -1; - } - - if (primitive->material.pbrWorkflows.specularGlossiness) { - // Specular glossiness workflow - pushConstBlockMaterial.workflow = static_cast(PBR_WORKFLOW_SPECULAR_GLOSINESS); - pushConstBlockMaterial.PhysicalDescriptorTextureSet = primitive->material.extension.specularGlossinessTexture != nullptr ? primitive->material.texCoordSets.specularGlossiness : -1; - pushConstBlockMaterial.colorTextureSet = primitive->material.extension.diffuseTexture != nullptr ? primitive->material.texCoordSets.baseColor : -1; - pushConstBlockMaterial.diffuseFactor = primitive->material.extension.diffuseFactor; - pushConstBlockMaterial.specularFactor = glm::vec4(primitive->material.extension.specularFactor, 1.0f); - } - - vkCmdPushConstants(commandBuffers[cbIndex], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstBlockMaterial), &pushConstBlockMaterial); - - if (primitive->hasIndices) { - vkCmdDrawIndexed(commandBuffers[cbIndex], primitive->indexCount, 1, primitive->firstIndex, 0, 0); - } - else { - vkCmdDraw(commandBuffers[cbIndex], primitive->vertexCount, 1, 0, 0); - } - } - } - - }; - for (auto child : node->children) { - renderNode(child, cbIndex, alphaMode); - } + void PlumageRender::buildCommandBuffers() { diff --git a/src/render/render.h b/src/render/render.h index 9f68fd3..86ada2a 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -1,18 +1,6 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "algorithm" - - - -#include #include "vulkanexamplebase.h" #include "glTFModel.h" @@ -33,7 +21,7 @@ public: { glTFModel::Model scene; glTFModel::Model skybox; - }models; + }; struct Textures { vks::TextureCubeMap environmentCube; @@ -54,9 +42,9 @@ public: } shaderData; struct UniformBufferSet { - vks::Buffer scene; - vks::Buffer skybox; - vks::Buffer params; + Buffer scene; + Buffer skybox; + Buffer params; }; struct UBOMatrices { @@ -103,20 +91,6 @@ public: } filePath; - bool rotateModel = false; - glm::vec3 modelrot = glm::vec3(0.0f); - glm::vec3 modelPos = glm::vec3(0.0f); - - enum PBRWorkflows { PBR_WORKFLOW_METALLIC_ROUGHNESS = 0, PBR_WORKFLOW_SPECULAR_GLOSINESS = 1 }; - - - std::map environments; - std::string selectedEnvironment = "papermill"; - - - int32_t debugViewInputs = 0; - int32_t debugViewEquation = 0; - struct StagingBuffer { VkBuffer buffer; VkDeviceMemory memory; @@ -132,8 +106,6 @@ public: VkPipeline toneMapping = VK_NULL_HANDLE; } pipelines; - VkPipeline boundPipeline = VK_NULL_HANDLE; - struct PipelineLayouts { VkDescriptorSetLayout scene; @@ -143,45 +115,23 @@ public: } pipelineLayouts; VkPipelineLayout pipelineLayout; - struct DescriptorSets { VkDescriptorSet scene; VkDescriptorSet skybox; VkDescriptorSet tonemappingDescriptorSet = VK_NULL_HANDLE; }; + + + + struct DescriptorSetLayouts { VkDescriptorSetLayout scene; VkDescriptorSetLayout material; VkDescriptorSetLayout node; } descriptorSetLayouts; - std::vector descriptorSets; - std::vector commandBuffers; - std::vector uniformBuffers; - - std::vector waitFences; - std::vector renderCompleteSemaphores; - std::vector presentCompleteSemaphores; - - const uint32_t renderAhead = 2; - uint32_t frameIndex = 0; - - int32_t animationIndex = 0; - float animationTimer = 0.0f; - bool animate = true; - - bool displayBackground = true; - - struct LightSource { - glm::vec3 color = glm::vec3(1.0f); - glm::vec3 rotation = glm::vec3(75.0f, 40.0f, 0.0f); - } lightSource; - - - - /* struct OffScreen { VkImage image; @@ -189,7 +139,7 @@ public: VkDeviceMemory memory; VkFramebuffer framebuffer; } offscreen; - */ + struct IrradiancePushBlock { glm::mat4 mvp; @@ -211,42 +161,30 @@ public: { // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class - vkDestroyPipeline(device, pipelines.skybox, nullptr); - vkDestroyPipeline(device, pipelines.pbr, nullptr); - vkDestroyPipeline(device, pipelines.pbrAlphaBlend, nullptr); - - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.material, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.node, nullptr); - - models.scene.destroy(device); - models.skybox.destroy(device); - - for (auto buffer : uniformBuffers) { - buffer.params.destroy(); - buffer.scene.destroy(); - buffer.skybox.destroy(); - } - for (auto fence : waitFences) { - vkDestroyFence(device, fence, nullptr); - } - for (auto semaphore : renderCompleteSemaphores) { - vkDestroySemaphore(device, semaphore, nullptr); - } - for (auto semaphore : presentCompleteSemaphores) { - vkDestroySemaphore(device, semaphore, nullptr); + vkDestroyPipeline(device, pipelines.solid, nullptr); + vkDestroyPipeline(device, pipelines.toneMapping, nullptr); + if (pipelines.wireframe != VK_NULL_HANDLE) { + vkDestroyPipeline(device, pipelines.wireframe, nullptr); } - textures.environmentCube.destroy(); - textures.irradianceCube.destroy(); - textures.prefilteredCube.destroy(); - textures.lutBrdf.destroy(); - textures.empty.destroy(); + vkDestroyPipelineLayout(device, pipelineLayouts.pbrLayout, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.tonemappingLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.materialUniform, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.ssbo, nullptr); + + pbrFrameBuffer.color.destroy(device); + pbrFrameBuffer.depth.destroy(device); + pbrFrameBuffer.fbo.destroy(device); + vkDestroySampler(device, colorSampler, nullptr); + + shaderData.buffer.destroy(); + shaderData.skinSSBO.destroy(); } virtual void getEnabledFeatures(); - void renderNode(glTFModel::Node* node, uint32_t cbIndex, glTFModel::Material::AlphaMode alphaMode); + void createAttachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment* attachment, uint32_t width, uint32_t height); virtual void setupFrameBuffer(); void buildCommandBuffers(); void loadAssets();