From 8c857eb7075f894e87e668128610135cb06f1fcb Mon Sep 17 00:00:00 2001 From: InkSoul Date: Mon, 5 Jun 2023 21:31:21 +0800 Subject: [PATCH] Revert "Revert "save to revert"" This reverts commit b18564c1f53aceae3ce9059deba813dd3d0e34d1. --- src/render/glTFModel.cpp | 2410 ++++++++++++++++---------------------- src/render/glTFModel.h | 250 ++-- src/render/render.cpp | 78 +- src/render/render.h | 118 +- 4 files changed, 1293 insertions(+), 1563 deletions(-) diff --git a/src/render/glTFModel.cpp b/src/render/glTFModel.cpp index 2fe3648..838ddd5 100644 --- a/src/render/glTFModel.cpp +++ b/src/render/glTFModel.cpp @@ -1,124 +1,108 @@ + //#pragma once #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION -#define TINYGLTF_NO_STB_IMAGE_WRITE +#define STBI_MSC_SECURE_CRT -#include "glTFModel.h" +#include "VulkanglTFModel.h" -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) +namespace glTFModel { - // 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; - } + // 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); } - return tinygltf::LoadImageData(image, imageIndex, error, warning, req_width, req_height, bytes, size, userData); -} + // Texture + void Texture::updateDescriptor() + { + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; + } -//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) + void Texture::destroy() { vkDestroyImageView(device->logicalDevice, view, nullptr); vkDestroyImage(device->logicalDevice, image, nullptr); vkFreeMemory(device->logicalDevice, deviceMemory, nullptr); vkDestroySampler(device->logicalDevice, sampler, nullptr); } -} -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) + void Texture::fromglTfImage(tinygltf::Image& gltfimage, TextureSampler textureSampler, vks::VulkanDevice* device, VkQueue copyQueue) { - // 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 + this->device = device; unsigned char* buffer = nullptr; VkDeviceSize bufferSize = 0; bool deleteBuffer = false; - // 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; + 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; buffer = new unsigned char[bufferSize]; unsigned char* rgba = buffer; - unsigned char* rgb = &gltfImage.image[0]; - for (size_t i = 0; i < gltfImage.width * gltfImage.height; ++i) { - memcpy(rgba, rgb, sizeof(unsigned char) * 3); + 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]; + } rgba += 4; rgb += 3; } deleteBuffer = true; } else { - buffer = &gltfImage.image[0]; - bufferSize = gltfImage.image.size(); + buffer = &gltfimage.image[0]; + bufferSize = gltfimage.image.size(); } - format = VK_FORMAT_R8G8B8A8_UNORM; + VkFormat 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; @@ -188,7 +172,7 @@ void glTFModel::Texture::fromglTfImage(tinygltf::Image& gltfImage, std::string p 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; @@ -200,6 +184,7 @@ void glTFModel::Texture::fromglTfImage(tinygltf::Image& gltfImage, std::string p 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); @@ -266,1365 +251,1022 @@ void glTFModel::Texture::fromglTfImage(tinygltf::Image& gltfImage, std::string p 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_SHADER_READ_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_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); } - if (deleteBuffer) { - delete[] buffer; - } - device->flushCommandBuffer(blitCmd, copyQueue, true); - } - else { - // Texture is stored in an external ktx file - std::string filename = path + "/" + gltfImage.uri; - ktxTexture* ktxTexture; + 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)); - 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); + 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)); - this->device = device; - width = ktxTexture->baseWidth; - height = ktxTexture->baseHeight; - mipLevels = ktxTexture->numLevels; + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; - ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture); - ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture); - // @todo: Use ktxTexture_GetVkFormat(ktxTexture) - format = VK_FORMAT_R8G8B8A8_UNORM; + if (deleteBuffer) + delete[] buffer; - // 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)); - - 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); -} - - -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()); - } - - // Samplers - for (auto& samp : anim.samplers) { - glTFModel::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); - - 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]); - } - delete[] buf; - for (auto input : sampler.inputs) { - if (input < animation.start) { - animation.start = input; - }; - if (input > animation.end) { - animation.end = input; - } - } - } - - // 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); - - 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.outputsVec4.push_back(glm::vec4(buf[index], 0.0f)); - } - 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; - } - default: { - std::cout << "unknown type" << std::endl; - break; - } - } - } - - animation.samplers.push_back(sampler); - } - - // Channels - for (auto& source : anim.channels) { - glTFModel::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); - } -} - -/* - 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 (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)); - } + // 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; }; - // 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); + void Primitive::setBoundingBox(glm::vec3 min, glm::vec3 max) { + bb.min = min; + bb.max = max; + bb.valid = true; + } + + // 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) }; + }; + + Mesh::~Mesh() { + vkDestroyBuffer(device->logicalDevice, uniformBuffer.buffer, nullptr); + vkFreeMemory(device->logicalDevice, uniformBuffer.memory, nullptr); + for (Primitive* p : primitives) + delete p; + } + + void Mesh::setBoundingBox(glm::vec3 min, glm::vec3 max) { + bb.min = min; + bb.max = max; + bb.valid = true; + } + + // 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; + } + 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)); + } + else { + memcpy(mesh->uniformBuffer.mapped, &m, sizeof(glm::mat4)); + } + } + + for (auto& child : children) { + child->update(); } } - // 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]; + Node::~Node() { + if (mesh) { + delete mesh; + } + for (auto& child : children) { + delete child; + } + } - 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; + // 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); } - 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()) + // 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 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])); - } + 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; - hasSkin = (jointsBuffer && weightsBuffer); + int posByteStride; + int normByteStride; + int uv0ByteStride; + int uv1ByteStride; + int color0ByteStride; + int jointByteStride; + int weightByteStride; - + int jointComponentType; - // 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) + // 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) { - case 3 : - vert.color = glm::vec4(glm::make_vec3(&colorBuffer[v * 3]), 1.0f); - case 4 : - vert.color = glm::make_vec4(&colorBuffer[v * 4]); - + 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); + } + + // 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]; + 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); + } + } + + 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; + } + 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(); + } + } + } + + materials.push_back(material); + } + // Push a default material at the end of the list for meshes with no material assigned + materials.push_back(Material()); + } + + 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()); + } + + // 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); + for (size_t index = 0; index < accessor.count; index++) { + sampler.inputs.push_back(buf[index]); + } + + for (auto input : sampler.inputs) { + if (input < animation.start) { + animation.start = input; + }; + if (input > animation.end) { + animation.end = input; } } - else - { - vert.color = glm::vec4(1.0f); + } + + // 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; } - 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); + 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; + } + } + } + + 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(); } } - // 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]; + } + else { + // TODO: throw + std::cerr << "Could not load gltf file: " << error << std::endl; + return; + } - indexCount += static_cast(accessor.count); + extensions = gltfModel.extensionsUsed; - // 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; + 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); + } + } + 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; } } - 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); -} + parentBvh.min = glm::min(parentBvh.min, node->bvh.min); + parentBvh.max = glm::min(parentBvh.max, node->bvh.max); -// 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); + for (auto& child : node->children) { + calculateBoundingBox(child, node); } - 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); + } + + void Model::getSceneDimensions() + { + // Calculate binary volume hierarchy for all nodes in the scene + for (auto node : linearNodes) { + calculateBoundingBox(node, nullptr); } - if (gltfModel.animations.size() > 0) { - loadAnimations(gltfModel); - } - loadSkins(gltfModel); + + dimensions.min = glm::vec3(FLT_MAX); + dimensions.max = glm::vec3(-FLT_MAX); for (auto node : linearNodes) { - // Assign skins - if (node->skinIndex > -1) { - node->skin = skins[node->skinIndex]; + if (node->bvh.valid) { + dimensions.min = glm::min(dimensions.min, node->bvh.min); + dimensions.max = glm::max(dimensions.max, node->bvh.max); } - // Initial pose - if (node->mesh) { + } + + // 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()) { + 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; + } + } + } + } + if (updated) { + for (auto& node : nodes) { node->update(); } } } - else { - // TODO: throw - vks::tools::exitFatal("Could not load glTF file \"" + filename + "\": " + error, -1); - return; - } - // 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; - } - } - } + 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; } - 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) { + Node* Model::nodeFromIndex(uint32_t index) { + Node* nodeFound = nullptr; 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); + nodeFound = findNode(node, index); + if (nodeFound) { + break; } } + return nodeFound; } - 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 1fda6b7..1489e71 100644 --- a/src/render/glTFModel.h +++ b/src/render/glTFModel.h @@ -34,35 +34,27 @@ namespace glTFModel { - enum DescriptorBindingFlags { - ImageBaseColor = 0x00000001, - ImageNormalMap = 0x00000002 + 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 FileLoadingFlags { - None = 0x00000000, - PreTransformVertices = 0x00000001, - PreMultiplyVertexColors = 0x00000002, - FlipY = 0x00000004, - DontLoadImages = 0x00000008 + struct TextureSampler { + VkFilter magFilter; + VkFilter minFilter; + VkSamplerAddressMode addressModeU; + VkSamplerAddressMode addressModeV; + VkSamplerAddressMode addressModeW; }; - 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 { - int32_t imageIndex; - vks::VulkanDevice* device = nullptr; + vks::VulkanDevice* device; VkImage image; VkImageLayout imageLayout; VkDeviceMemory deviceMemory; @@ -74,96 +66,85 @@ namespace glTFModel VkSampler sampler; void updateDescriptor(); void destroy(); - void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue); - + // 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); }; - // 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); - 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; - + 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; 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; - - 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) {}; - - + bool hasIndices; + BoundingBox bb; + Primitive(uint32_t firstIndex, uint32_t indexCount, uint32_t vertexCount, Material& material); + void setBoundingBox(glm::vec3 min, glm::vec3 max); }; - // 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; - std::string name; - + BoundingBox bb; + BoundingBox aabb; struct UniformBuffer { VkBuffer buffer; VkDeviceMemory memory; VkDescriptorBufferInfo descriptor; - VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkDescriptorSet descriptorSet; void* mapped; } uniformBuffer; - struct UniformBlock { glm::mat4 matrix; - glm::mat4 jointMatrix[64]{}; - float jointCount{ 0 }; + glm::mat4 jointMatrix[128]{}; + 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; @@ -176,133 +157,102 @@ 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(); - - - }; - /* - 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 - { - enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; - InterpolationType interpolation; - std::vector inputs; - std::vector outputsVec4; - - }; - - struct AnimationChannel - { + struct AnimationChannel { enum PathType { TRANSLATION, ROTATION, SCALE }; PathType path; Node* node; uint32_t samplerIndex; - }; - struct Animation - { + struct AnimationSampler { + enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; + InterpolationType interpolation; + std::vector inputs; + std::vector outputsVec4; + }; + + 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; }; - /* - glTF model loading and rendering class - */ - class Model { - private: - glTFModel::Texture* getTexture(uint32_t index); - glTFModel::Texture emptyTexture; - void createEmptyTexture(VkQueue transferQueue); - public: + struct Model { + vks::VulkanDevice* device; - VkDescriptorPool descriptorPool; + + struct Vertex { + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv0; + glm::vec2 uv1; + glm::vec4 joint0; + glm::vec4 weight0; + glm::vec4 color; + }; struct Vertices { - int count; - VkBuffer buffer; + VkBuffer buffer = VK_NULL_HANDLE; VkDeviceMemory memory; } vertices; struct Indices { - int count; - VkBuffer buffer; + VkBuffer buffer = VK_NULL_HANDLE; 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; - bool metallicRoughnessWorkflow = true; - bool buffersBound = false; - std::string path; + struct LoaderInfo { + uint32_t* indexBuffer; + Vertex* vertexBuffer; + size_t indexPos = 0; + size_t vertexPos = 0; + }; - 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 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); void loadSkins(tinygltf::Model& gltfModel); - void loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue); + 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 loadMaterials(tinygltf::Model& gltfModel); void loadAnimations(tinygltf::Model& gltfModel); - 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 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 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 5914d1f..e7593ca 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -49,7 +49,83 @@ 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 86ada2a..9f68fd3 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -1,6 +1,18 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include "algorithm" + + + +#include #include "vulkanexamplebase.h" #include "glTFModel.h" @@ -21,7 +33,7 @@ public: { glTFModel::Model scene; glTFModel::Model skybox; - }; + }models; struct Textures { vks::TextureCubeMap environmentCube; @@ -42,9 +54,9 @@ public: } shaderData; struct UniformBufferSet { - Buffer scene; - Buffer skybox; - Buffer params; + vks::Buffer scene; + vks::Buffer skybox; + vks::Buffer params; }; struct UBOMatrices { @@ -91,6 +103,20 @@ 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; @@ -106,6 +132,8 @@ public: VkPipeline toneMapping = VK_NULL_HANDLE; } pipelines; + VkPipeline boundPipeline = VK_NULL_HANDLE; + struct PipelineLayouts { VkDescriptorSetLayout scene; @@ -115,23 +143,45 @@ 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; @@ -139,7 +189,7 @@ public: VkDeviceMemory memory; VkFramebuffer framebuffer; } offscreen; - + */ struct IrradiancePushBlock { glm::mat4 mvp; @@ -161,30 +211,42 @@ public: { // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class - vkDestroyPipeline(device, pipelines.solid, nullptr); - vkDestroyPipeline(device, pipelines.toneMapping, nullptr); - if (pipelines.wireframe != VK_NULL_HANDLE) { - vkDestroyPipeline(device, pipelines.wireframe, nullptr); + 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); } - 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(); + textures.environmentCube.destroy(); + textures.irradianceCube.destroy(); + textures.prefilteredCube.destroy(); + textures.lutBrdf.destroy(); + textures.empty.destroy(); } virtual void getEnabledFeatures(); - void createAttachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment* attachment, uint32_t width, uint32_t height); + void renderNode(glTFModel::Node* node, uint32_t cbIndex, glTFModel::Material::AlphaMode alphaMode); virtual void setupFrameBuffer(); void buildCommandBuffers(); void loadAssets();