diff --git a/src/render/glTFModel.cpp b/src/render/glTFModel.cpp index 2d04a02..6c558b0 100644 --- a/src/render/glTFModel.cpp +++ b/src/render/glTFModel.cpp @@ -1,8 +1,16 @@ -#pragma once +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define TINYGLTF_NO_STB_IMAGE_WRITE + #include "glTFModel.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 @@ -10,24 +18,81 @@ The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure */ -void VulkanglTFModel::loadImages(tinygltf::Model& input) +// 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) { - // 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 - images.resize(input.images.size()); - for (size_t i = 0; i < input.images.size(); i++) { - tinygltf::Image& glTFImage = input.images[i]; - // Get the image data from the glTF loader + // 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; + } + } + + return tinygltf::LoadImageData(image, imageIndex, error, warning, req_width, req_height, bytes, size, userData); +} + +//will be used for samples that don't require images to be loaded +bool loadImageDataFuncEmpty(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) +{ + // This function will be used for samples that don't require images to be loaded + return true; +} + +/* + glTF texture loader +*/ + +void glTFModel::Texture::updateDescriptor() +{ + descriptor.sampler = sampler; + descriptor.imageView = view; + descriptor.imageLayout = imageLayout; +} + +void glTFModel::Texture::destroy() +{ + if (device) + { + vkDestroyImageView(device->logicalDevice, view, nullptr); + vkDestroyImage(device->logicalDevice, image, nullptr); + vkFreeMemory(device->logicalDevice, deviceMemory, nullptr); + vkDestroySampler(device->logicalDevice, sampler, nullptr); + } +} + +void 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) + { + // loaded using STB_Image + // Images can be stored inside the glTF (which is the case for the sample model), so instead of directly + // loading them from disk, we fetch them from the glTF loader and upload the buffers + unsigned char* buffer = nullptr; VkDeviceSize bufferSize = 0; bool deleteBuffer = false; // 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) { + 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) { + unsigned char* rgb = &gltfImage.image[0]; + for (size_t i = 0; i < gltfImage.width * gltfImage.height; ++i) { memcpy(rgba, rgb, sizeof(unsigned char) * 3); rgba += 4; rgb += 3; @@ -35,17 +100,340 @@ void VulkanglTFModel::loadImages(tinygltf::Model& input) deleteBuffer = true; } else { - buffer = &glTFImage.image[0]; - bufferSize = glTFImage.image.size(); + buffer = &gltfImage.image[0]; + bufferSize = gltfImage.image.size(); } - // Load texture from image buffer - images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue); + + format = VK_FORMAT_R8G8B8A8_UNORM; + + VkFormatProperties formatProperties; + + 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; + + VkBufferCreateInfo bufferCreateInfo{}; + bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + 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, buffer, bufferSize); + vkUnmapMemory(device->logicalDevice, stagingMemory); + + VkImageCreateInfo imageCreateInfo{}; + imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + 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.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_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)); + + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.levelCount = 1; + subresourceRange.layerCount = 1; + + { + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.srcAccessMask = 0; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange = subresourceRange; + vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); + } + + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = 0; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = width; + bufferCopyRegion.imageExtent.height = height; + 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; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imageMemoryBarrier.image = image; + 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); + vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); + + // Generate the mip chain (glTF uses jpg and png, so we need to create this manually) + VkCommandBuffer blitCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + for (uint32_t i = 1; i < mipLevels; i++) { + VkImageBlit imageBlit{}; + + imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlit.srcSubresource.layerCount = 1; + imageBlit.srcSubresource.mipLevel = i - 1; + imageBlit.srcOffsets[1].x = int32_t(width >> (i - 1)); + imageBlit.srcOffsets[1].y = int32_t(height >> (i - 1)); + imageBlit.srcOffsets[1].z = 1; + + imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlit.dstSubresource.layerCount = 1; + imageBlit.dstSubresource.mipLevel = i; + imageBlit.dstOffsets[1].x = int32_t(width >> i); + imageBlit.dstOffsets[1].y = int32_t(height >> i); + imageBlit.dstOffsets[1].z = 1; + + VkImageSubresourceRange mipSubRange = {}; + mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + mipSubRange.baseMipLevel = i; + mipSubRange.levelCount = 1; + mipSubRange.layerCount = 1; + + { + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.srcAccessMask = 0; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange = mipSubRange; + vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); + } + + vkCmdBlitImage(blitCmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlit, VK_FILTER_LINEAR); + + { + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + imageMemoryBarrier.image = image; + imageMemoryBarrier.subresourceRange = mipSubRange; + vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier); + } + } + + subresourceRange.levelCount = mipLevels; + imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + { + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + 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.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; + + 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); + + this->device = device; + width = ktxTexture->baseWidth; + height = ktxTexture->baseHeight; + mipLevels = ktxTexture->numLevels; + + ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture); + ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture); + // @todo: Use ktxTexture_GetVkFormat(ktxTexture) + format = VK_FORMAT_R8G8B8A8_UNORM; + + // Get device properties for the requested texture format + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties); + + VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + + VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); + bufferCreateInfo.size = ktxTextureSize; + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); + + VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); + + uint8_t* data; + VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data)); + memcpy(data, ktxTextureData, ktxTextureSize); + vkUnmapMemory(device->logicalDevice, stagingMemory); + + std::vector bufferCopyRegions; + for (uint32_t i = 0; i < mipLevels; i++) + { + ktx_size_t offset; + KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset); + assert(result == KTX_SUCCESS); + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = i; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i); + bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i); + bufferCopyRegion.imageExtent.depth = 1; + bufferCopyRegion.bufferOffset = offset; + bufferCopyRegions.push_back(bufferCopyRegion); + } + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = mipLevels; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image)); + + vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0)); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = mipLevels; + subresourceRange.layerCount = 1; + + vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); + vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(bufferCopyRegions.size()), bufferCopyRegions.data()); + vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange); + device->flushCommandBuffer(copyCmd, copyQueue); + this->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); + vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); + + ktxTexture_Destroy(ktxTexture); + } + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + samplerInfo.compareOp = VK_COMPARE_OP_NEVER; + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + samplerInfo.maxAnisotropy = 1.0; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxLod = (float)mipLevels; + samplerInfo.maxAnisotropy = 8.0f; + samplerInfo.anisotropyEnable = VK_TRUE; + VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); + + 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 VulkanglTFModel::loadTextures(tinygltf::Model& input) { textures.resize(input.textures.size()); @@ -142,7 +530,49 @@ void VulkanglTFModel::loadAnimations(tinygltf::Model& input) } } } +*/ +/* + glTF material +*/ +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); +} + + + +/* void VulkanglTFModel::loadMaterials(tinygltf::Model& input) { materials.resize(input.materials.size()); @@ -179,7 +609,7 @@ void VulkanglTFModel::loadMaterials(tinygltf::Model& input) } } } - +*/ void VulkanglTFModel::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, uint32_t nodeIndex, std::vector& indexBuffer, std::vector& vertexBuffer) { VulkanglTFModel::Node* node = new VulkanglTFModel::Node{}; diff --git a/src/render/glTFModel.h b/src/render/glTFModel.h index 62cf121..1fda6b7 100644 --- a/src/render/glTFModel.h +++ b/src/render/glTFModel.h @@ -23,148 +23,200 @@ #include "tiny_gltf.h" - -#include "vulkanexamplebase.h" +#include "VulkanDevice.h" +#include "vulkan/vulkan.h" #define ENABLE_VALIDATION false // Contains everything required to render a glTF model in Vulkan // This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure -namespace VulkanglTFModel +namespace glTFModel { - // The class requires some Vulkan objects so it can create it's own resources - vks::VulkanDevice* vulkanDevice; - VkQueue copyQueue; - uint32_t nodeCount; - enum DescriptorBindingFlags { ImageBaseColor = 0x00000001, ImageNormalMap = 0x00000002 }; + enum FileLoadingFlags { + None = 0x00000000, + PreTransformVertices = 0x00000001, + PreMultiplyVertexColors = 0x00000002, + FlipY = 0x00000004, + DontLoadImages = 0x00000008 + }; + + enum RenderFlags { + BindImages = 0x00000001, + RenderOpaqueNodes = 0x00000002, + RenderAlphaMaskedNodes = 0x00000004, + RenderAlphaBlendedNodes = 0x00000008 + }; + extern VkDescriptorSetLayout descriptorSetLayoutImage; extern VkDescriptorSetLayout descriptorSetLayoutUbo; extern VkMemoryPropertyFlags memoryPropertyFlags; extern uint32_t descriptorBindingFlags; - // The vertex layout for the samples' model - struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv; - glm::vec3 color; - glm::vec3 tangent; - glm::vec3 jointIndices; - glm::vec3 jointWeights; + // A glTF texture stores a reference to the image and a sampler + struct Texture { + int32_t imageIndex; + vks::VulkanDevice* device = nullptr; + VkImage image; + VkImageLayout imageLayout; + VkDeviceMemory deviceMemory; + VkImageView view; + uint32_t width, height; + uint32_t mipLevels; + uint32_t layerCount; + VkDescriptorImageInfo descriptor; + VkSampler sampler; + void updateDescriptor(); + void destroy(); + void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue); + }; - // Single vertex buffer for all primitives - struct Vertices { - VkBuffer buffer; - VkDeviceMemory memory; - } vertices; + // 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; - // Single index buffer for all primitives - struct Indices { - int count; - VkBuffer buffer; - VkDeviceMemory memory; - } indices; + glTFModel::Texture* specularGlossinessTexture; + glTFModel::Texture* diffuseTexture; - // The following structures roughly represent the glTF scene structure - // To keep things simple, they only contain those properties that are required for this sample - struct Node; + 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; - int32_t materialIndex; + 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) {}; + + }; // Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives struct Mesh { - std::vector primitives; - }; + vks::VulkanDevice* device; - // A node represents an object in the glTF scene graph - struct Node { - Node* parent; - uint32_t index; - std::vector children; - Mesh mesh; - glm::vec3 translation{}; - glm::vec3 scale{ 1.0f }; - glm::quat rotation{}; - int32_t skin = -1; - glm::mat4 getLocalMatrix() - { - return bAnimateNode ? glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) : matrix; - } - glm::mat4 matrix; - bool bAnimateNode = false; + std::vector primitives; + std::string name; - ~Node() { - for (auto& child : children) { - delete child; - }; - } + struct UniformBuffer { + VkBuffer buffer; + VkDeviceMemory memory; + VkDescriptorBufferInfo descriptor; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + void* mapped; + } uniformBuffer; + struct UniformBlock { + glm::mat4 matrix; + glm::mat4 jointMatrix[64]{}; + float jointCount{ 0 }; + } uniformBlock; + + Mesh(vks::VulkanDevice* device, glm::mat4 matrix); + ~Mesh(); }; - // material data for pbr - struct MaterialData - { - vks::Buffer buffer; - VkDescriptorSet descriptorSet; - struct Values - { - glm::vec3 emissiveFactor; - glm::vec4 baseColorFactor; - }values; + + struct Node; - }; - // A glTF material stores information in e.g. the texture that is attached to it and colors - struct Material { - glm::vec4 baseColorFactor = glm::vec4(1.0f); - uint32_t baseColorTextureIndex; - uint32_t normalMapTextureIndex; - uint32_t matalicRoughTextureIndex; - int32_t emissiveTextureIndex = -1; - MaterialData materialData; - }; - - // Contains the texture for a single glTF image - // Images may be reused by texture objects and are as such separated - struct Image { - vks::Texture2D texture; - // We also store (and create) a descriptor set that's used to access this texture from the fragment shader - VkDescriptorSet descriptorSet; - }; - - // A glTF texture stores a reference to the image and a sampler - // In this sample, we are only interested in the image - struct Texture { - int32_t imageIndex; - }; - - // structure of skin + /* + glTF skin + */ struct Skin { std::string name; Node* skeletonRoot = nullptr; std::vector inverseBindMatrices; std::vector joints; - vks::Buffer ssbo; - VkDescriptorSet descriptorSet; + }; + // A node represents an object in the glTF scene graph + struct Node { + Node* parent; + uint32_t index; + std::vector children; + glm::mat4 matrix; + std::string name; + Mesh* mesh; + Skin* skin; + int32_t skinIndex = -1; + glm::vec3 translation{}; + glm::vec3 scale{ 1.0f }; + glm::quat rotation{}; + 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 { - std::string interpolation; + enum InterpolationType { LINEAR, STEP, CUBICSPLINE }; + InterpolationType interpolation; std::vector inputs; std::vector outputsVec4; @@ -172,7 +224,8 @@ namespace VulkanglTFModel struct AnimationChannel { - std::string path; + enum PathType { TRANSLATION, ROTATION, SCALE }; + PathType path; Node* node; uint32_t samplerIndex; @@ -190,53 +243,68 @@ namespace VulkanglTFModel }; /* - Model data + glTF model loading and rendering class */ - std::vector images; - std::vector textures; - std::vector materials; - std::vector nodes; - std::vector skins; - std::vector animations; + class Model { + private: + glTFModel::Texture* getTexture(uint32_t index); + glTFModel::Texture emptyTexture; + void createEmptyTexture(VkQueue transferQueue); + public: + vks::VulkanDevice* device; + VkDescriptorPool descriptorPool; - uint32_t activeAnimation = 0; + struct Vertices { + int count; + VkBuffer buffer; + VkDeviceMemory memory; + } vertices; + struct Indices { + int count; + VkBuffer buffer; + VkDeviceMemory memory; + } indices; + + std::vector nodes; + std::vector linearNodes; + + std::vector skins; + + std::vector textures; + std::vector materials; + std::vector animations; + + 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; + + Model() {}; + ~Model(); + void loadNode(glTFModel::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, std::vector& indexBuffer, std::vector& vertexBuffer, float globalscale); + void loadSkins(tinygltf::Model& gltfModel); + void loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue); + void loadMaterials(tinygltf::Model& gltfModel); + void loadAnimations(tinygltf::Model& gltfModel); + void loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags = glTFModel::FileLoadingFlags::None, float scale = 1.0f); + void bindBuffers(VkCommandBuffer commandBuffer); + void drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1); + void draw(VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1); + void getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max); + void getSceneDimensions(); + void updateAnimation(uint32_t index, float time); + Node* findNode(Node* parent, uint32_t index); + Node* nodeFromIndex(uint32_t index); + void prepareNodeDescriptor(glTFModel::Node* node, VkDescriptorSetLayout descriptorSetLayout); + }; + - - //VulkanglTFModel(); - ~VulkanglTFModel() - { - for (auto node : nodes) { - delete node; - } - // Release all Vulkan resources allocated for the model - vkDestroyBuffer(vulkanDevice->logicalDevice, vertices.buffer, nullptr); - vkFreeMemory(vulkanDevice->logicalDevice, vertices.memory, nullptr); - vkDestroyBuffer(vulkanDevice->logicalDevice, indices.buffer, nullptr); - vkFreeMemory(vulkanDevice->logicalDevice, indices.memory, nullptr); - for (auto& material : materials) - { - material.materialData.buffer.destroy(); - } - for (Image image : images) { - vkDestroyImageView(vulkanDevice->logicalDevice, image.texture.view, nullptr); - vkDestroyImage(vulkanDevice->logicalDevice, image.texture.image, nullptr); - vkDestroySampler(vulkanDevice->logicalDevice, image.texture.sampler, nullptr); - vkFreeMemory(vulkanDevice->logicalDevice, image.texture.deviceMemory, nullptr); - } - } - void loadImages(tinygltf::Model& input); - void loadTextures(tinygltf::Model& input); - void loadMaterials(tinygltf::Model& input); - Node* findNode(Node* parent, uint32_t index); - Node* nodeFromIndex(uint32_t index); - //void loadSkins(tinygltf::Model& input); - void loadAnimations(tinygltf::Model& input); - void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, uint32_t nodeIndex, std::vector& indexBuffer, std::vector& vertexBuffer); - glm::mat4 getNodeMatrix(VulkanglTFModel::Node* node); - void updateNodeMatrix(Node* node, std::vector& nodeMatrics); - //void updateJoints(VulkanglTFModel::Node* node); - void updateAnimation(float deltaTime, vks::Buffer& buffer); - void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node* node, bool bPushConstants); - void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, bool flag); -}; \ No newline at end of file +} \ No newline at end of file