1700 lines
52 KiB
C++
1700 lines
52 KiB
C++
/* Copyright (c) 2018-2025, Arm Limited and Contributors
|
|
* Copyright (c) 2019-2025, Sascha Willems
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 the "License";
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define TINYGLTF_IMPLEMENTATION
|
|
#include "gltf_loader.h"
|
|
|
|
#include <future>
|
|
#include <limits>
|
|
#include <queue>
|
|
|
|
#include "common/error.h"
|
|
|
|
#include "common/glm_common.h"
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
#include <core/util/profiling.hpp>
|
|
|
|
#include "api_vulkan_sample.h"
|
|
#include "common/utils.h"
|
|
#include "common/vk_common.h"
|
|
#include "core/device.h"
|
|
#include "core/image.h"
|
|
#include "core/util/logging.hpp"
|
|
#include "filesystem/legacy.h"
|
|
#include "scene_graph/components/camera.h"
|
|
#include "scene_graph/components/image.h"
|
|
#include "scene_graph/components/image/astc.h"
|
|
#include "scene_graph/components/light.h"
|
|
#include "scene_graph/components/mesh.h"
|
|
#include "scene_graph/components/pbr_material.h"
|
|
#include "scene_graph/components/perspective_camera.h"
|
|
#include "scene_graph/components/sampler.h"
|
|
#include "scene_graph/components/sub_mesh.h"
|
|
#include "scene_graph/components/texture.h"
|
|
#include "scene_graph/components/transform.h"
|
|
#include "scene_graph/node.h"
|
|
#include "scene_graph/scene.h"
|
|
#include "scene_graph/scripts/animation.h"
|
|
|
|
namespace vkb
|
|
{
|
|
namespace
|
|
{
|
|
inline VkFilter find_min_filter(int min_filter)
|
|
{
|
|
switch (min_filter)
|
|
{
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST:
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
|
|
return VK_FILTER_NEAREST;
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR:
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
|
|
return VK_FILTER_LINEAR;
|
|
default:
|
|
return VK_FILTER_LINEAR;
|
|
}
|
|
};
|
|
|
|
inline VkSamplerMipmapMode find_mipmap_mode(int min_filter)
|
|
{
|
|
switch (min_filter)
|
|
{
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
|
|
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
|
|
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
default:
|
|
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
}
|
|
};
|
|
|
|
inline VkFilter find_mag_filter(int mag_filter)
|
|
{
|
|
switch (mag_filter)
|
|
{
|
|
case TINYGLTF_TEXTURE_FILTER_NEAREST:
|
|
return VK_FILTER_NEAREST;
|
|
case TINYGLTF_TEXTURE_FILTER_LINEAR:
|
|
return VK_FILTER_LINEAR;
|
|
default:
|
|
return VK_FILTER_LINEAR;
|
|
}
|
|
};
|
|
|
|
inline VkSamplerAddressMode find_wrap_mode(int wrap)
|
|
{
|
|
switch (wrap)
|
|
{
|
|
case TINYGLTF_TEXTURE_WRAP_REPEAT:
|
|
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
|
|
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT:
|
|
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
|
default:
|
|
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
}
|
|
};
|
|
|
|
inline std::vector<uint8_t> get_attribute_data(const tinygltf::Model *model, uint32_t accessorId)
|
|
{
|
|
assert(accessorId < model->accessors.size());
|
|
auto &accessor = model->accessors[accessorId];
|
|
assert(accessor.bufferView < model->bufferViews.size());
|
|
auto &bufferView = model->bufferViews[accessor.bufferView];
|
|
assert(bufferView.buffer < model->buffers.size());
|
|
auto &buffer = model->buffers[bufferView.buffer];
|
|
|
|
size_t stride = accessor.ByteStride(bufferView);
|
|
size_t startByte = accessor.byteOffset + bufferView.byteOffset;
|
|
size_t endByte = startByte + accessor.count * stride;
|
|
|
|
return {buffer.data.begin() + startByte, buffer.data.begin() + endByte};
|
|
};
|
|
|
|
inline size_t get_attribute_size(const tinygltf::Model *model, uint32_t accessorId)
|
|
{
|
|
assert(accessorId < model->accessors.size());
|
|
return model->accessors[accessorId].count;
|
|
};
|
|
|
|
inline size_t get_attribute_stride(const tinygltf::Model *model, uint32_t accessorId)
|
|
{
|
|
assert(accessorId < model->accessors.size());
|
|
auto &accessor = model->accessors[accessorId];
|
|
assert(accessor.bufferView < model->bufferViews.size());
|
|
auto &bufferView = model->bufferViews[accessor.bufferView];
|
|
|
|
return accessor.ByteStride(bufferView);
|
|
};
|
|
|
|
inline VkFormat get_attribute_format(const tinygltf::Model *model, uint32_t accessorId)
|
|
{
|
|
assert(accessorId < model->accessors.size());
|
|
auto &accessor = model->accessors[accessorId];
|
|
|
|
VkFormat format;
|
|
|
|
switch (accessor.componentType)
|
|
{
|
|
case TINYGLTF_COMPONENT_TYPE_BYTE:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R8_SINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R8G8_SINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R8G8B8_SINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R8G8B8A8_SINT}};
|
|
|
|
format = mapped_format.at(accessor.type);
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R8_UINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R8G8_UINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R8G8B8_UINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R8G8B8A8_UINT}};
|
|
|
|
static const std::map<int, VkFormat> mapped_format_normalize = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R8_UNORM},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R8G8_UNORM},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R8G8B8_UNORM},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R8G8B8A8_UNORM}};
|
|
|
|
if (accessor.normalized)
|
|
{
|
|
format = mapped_format_normalize.at(accessor.type);
|
|
}
|
|
else
|
|
{
|
|
format = mapped_format.at(accessor.type);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_SHORT:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R8_SINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R8G8_SINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R8G8B8_SINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R8G8B8A8_SINT}};
|
|
|
|
format = mapped_format.at(accessor.type);
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R16_UINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R16G16_UINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R16G16B16_UINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R16G16B16A16_UINT}};
|
|
|
|
static const std::map<int, VkFormat> mapped_format_normalize = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R16_UNORM},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R16G16_UNORM},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R16G16B16_UNORM},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R16G16B16A16_UNORM}};
|
|
|
|
if (accessor.normalized)
|
|
{
|
|
format = mapped_format_normalize.at(accessor.type);
|
|
}
|
|
else
|
|
{
|
|
format = mapped_format.at(accessor.type);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_INT:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R32_SINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R32G32_SINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R32G32B32_SINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R32G32B32A32_SINT}};
|
|
|
|
format = mapped_format.at(accessor.type);
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R32_UINT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R32G32_UINT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R32G32B32_UINT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R32G32B32A32_UINT}};
|
|
|
|
format = mapped_format.at(accessor.type);
|
|
|
|
break;
|
|
}
|
|
case TINYGLTF_COMPONENT_TYPE_FLOAT:
|
|
{
|
|
static const std::map<int, VkFormat> mapped_format = {{TINYGLTF_TYPE_SCALAR, VK_FORMAT_R32_SFLOAT},
|
|
{TINYGLTF_TYPE_VEC2, VK_FORMAT_R32G32_SFLOAT},
|
|
{TINYGLTF_TYPE_VEC3, VK_FORMAT_R32G32B32_SFLOAT},
|
|
{TINYGLTF_TYPE_VEC4, VK_FORMAT_R32G32B32A32_SFLOAT}};
|
|
|
|
format = mapped_format.at(accessor.type);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
format = VK_FORMAT_UNDEFINED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return format;
|
|
};
|
|
|
|
inline std::vector<uint8_t> convert_underlying_data_stride(const std::vector<uint8_t> &src_data, uint32_t src_stride, uint32_t dst_stride)
|
|
{
|
|
auto elem_count = to_u32(src_data.size()) / src_stride;
|
|
|
|
std::vector<uint8_t> result(elem_count * dst_stride);
|
|
|
|
for (uint32_t idxSrc = 0, idxDst = 0;
|
|
idxSrc < src_data.size() && idxDst < result.size();
|
|
idxSrc += src_stride, idxDst += dst_stride)
|
|
{
|
|
std::copy(src_data.begin() + idxSrc, src_data.begin() + idxSrc + src_stride, result.begin() + idxDst);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
inline void upload_image_to_gpu(vkb::core::CommandBufferC &command_buffer, vkb::core::BufferC &staging_buffer, sg::Image &image)
|
|
{
|
|
// Clean up the image data, as they are copied in the staging buffer
|
|
image.clear_data();
|
|
|
|
{
|
|
ImageMemoryBarrier memory_barrier{};
|
|
memory_barrier.old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
memory_barrier.new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
memory_barrier.src_access_mask = 0;
|
|
memory_barrier.dst_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_HOST_BIT;
|
|
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
|
|
command_buffer.image_memory_barrier(image.get_vk_image_view(), memory_barrier);
|
|
}
|
|
|
|
// Create a buffer image copy for every mip level
|
|
auto &mipmaps = image.get_mipmaps();
|
|
|
|
std::vector<VkBufferImageCopy> buffer_copy_regions(mipmaps.size());
|
|
|
|
for (size_t i = 0; i < mipmaps.size(); ++i)
|
|
{
|
|
auto &mipmap = mipmaps[i];
|
|
auto ©_region = buffer_copy_regions[i];
|
|
|
|
copy_region.bufferOffset = mipmap.offset;
|
|
copy_region.imageSubresource = image.get_vk_image_view().get_subresource_layers();
|
|
// Update miplevel
|
|
copy_region.imageSubresource.mipLevel = mipmap.level;
|
|
copy_region.imageExtent = mipmap.extent;
|
|
}
|
|
|
|
command_buffer.copy_buffer_to_image(staging_buffer, image.get_vk_image(), buffer_copy_regions);
|
|
|
|
{
|
|
ImageMemoryBarrier memory_barrier{};
|
|
memory_barrier.old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
memory_barrier.new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
memory_barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
memory_barrier.dst_access_mask = VK_ACCESS_SHADER_READ_BIT;
|
|
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
|
|
command_buffer.image_memory_barrier(image.get_vk_image_view(), memory_barrier);
|
|
}
|
|
}
|
|
|
|
inline void prepare_meshlets(std::vector<Meshlet> &meshlets, std::unique_ptr<vkb::sg::SubMesh> &submesh, std::vector<unsigned char> &index_data)
|
|
{
|
|
Meshlet meshlet;
|
|
meshlet.vertex_count = 0;
|
|
meshlet.index_count = 0;
|
|
|
|
std::set<uint32_t> vertices; // set for unique vertices
|
|
uint32_t triangle_check = 0; // each meshlet needs to contain full primitives
|
|
|
|
for (uint32_t i = 0; i < submesh->vertex_indices; i++)
|
|
{
|
|
// index_data is unsigned char type, casting to uint32_t* will give proper value
|
|
meshlet.indices[meshlet.index_count] = *(reinterpret_cast<uint32_t *>(index_data.data()) + i);
|
|
|
|
if (vertices.insert(meshlet.indices[meshlet.index_count]).second)
|
|
{
|
|
++meshlet.vertex_count;
|
|
}
|
|
|
|
meshlet.index_count++;
|
|
triangle_check = triangle_check < 3 ? ++triangle_check : 1;
|
|
|
|
// 96 because for each traingle we draw a line in a mesh shader sample, 32 triangles/lines per meshlet = 64 vertices on output
|
|
if (meshlet.vertex_count == 64 || meshlet.index_count == 96 || i == submesh->vertex_indices - 1)
|
|
{
|
|
if (i == submesh->vertex_indices - 1)
|
|
{
|
|
assert(triangle_check == 3);
|
|
}
|
|
|
|
uint32_t counter = 0;
|
|
for (auto v : vertices)
|
|
{
|
|
meshlet.vertices[counter++] = v;
|
|
}
|
|
if (triangle_check != 3)
|
|
{
|
|
meshlet.index_count -= triangle_check;
|
|
i -= triangle_check;
|
|
triangle_check = 0;
|
|
}
|
|
|
|
meshlets.push_back(meshlet);
|
|
meshlet.vertex_count = 0;
|
|
meshlet.index_count = 0;
|
|
vertices.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool texture_needs_srgb_colorspace(const std::string &name)
|
|
{
|
|
// The gltf spec states that the base and emissive textures MUST be encoded with the sRGB
|
|
// transfer function. All other texture types are linear.
|
|
if (name == "baseColorTexture" || name == "emissiveTexture")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// metallicRoughnessTexture, normalTexture & occlusionTexture must be linear
|
|
assert(name == "metallicRoughnessTexture" || name == "normalTexture" || name == "occlusionTexture");
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unordered_map<std::string, bool> GLTFLoader::supported_extensions = {
|
|
{KHR_LIGHTS_PUNCTUAL_EXTENSION, false}};
|
|
|
|
GLTFLoader::GLTFLoader(vkb::core::DeviceC &device) :
|
|
device{device}
|
|
{
|
|
}
|
|
|
|
std::unique_ptr<sg::Scene> GLTFLoader::read_scene_from_file(const std::string &file_name, int scene_index, VkBufferUsageFlags additional_buffer_usage_flags)
|
|
{
|
|
PROFILE_SCOPE("Load GLTF Scene");
|
|
|
|
std::string err;
|
|
std::string warn;
|
|
|
|
tinygltf::TinyGLTF gltf_loader;
|
|
|
|
std::string gltf_file = vkb::fs::path::get(vkb::fs::path::Type::Assets) + file_name;
|
|
|
|
bool importResult = gltf_loader.LoadASCIIFromFile(&model, &err, &warn, gltf_file.c_str());
|
|
|
|
if (!importResult)
|
|
{
|
|
LOGE("Failed to load gltf file {}.", gltf_file.c_str());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (!err.empty())
|
|
{
|
|
LOGE("Error loading gltf model: {}.", err.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
if (!warn.empty())
|
|
{
|
|
LOGI("{}", warn.c_str());
|
|
}
|
|
|
|
size_t pos = file_name.find_last_of('/');
|
|
|
|
model_path = file_name.substr(0, pos);
|
|
|
|
if (pos == std::string::npos)
|
|
{
|
|
model_path.clear();
|
|
}
|
|
|
|
return std::make_unique<sg::Scene>(load_scene(scene_index, additional_buffer_usage_flags));
|
|
}
|
|
|
|
std::unique_ptr<sg::SubMesh> GLTFLoader::read_model_from_file(const std::string &file_name, uint32_t index, bool storage_buffer, VkBufferUsageFlags additional_buffer_usage_flags)
|
|
{
|
|
PROFILE_SCOPE("Load GLTF Model");
|
|
|
|
std::string err;
|
|
std::string warn;
|
|
|
|
tinygltf::TinyGLTF gltf_loader;
|
|
|
|
std::string gltf_file = vkb::fs::path::get(vkb::fs::path::Type::Assets) + file_name;
|
|
|
|
bool importResult = gltf_loader.LoadASCIIFromFile(&model, &err, &warn, gltf_file.c_str());
|
|
|
|
if (!importResult)
|
|
{
|
|
LOGE("Failed to load gltf file {}.", gltf_file.c_str());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (!err.empty())
|
|
{
|
|
LOGE("Error loading gltf model: {}.", err.c_str());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
if (!warn.empty())
|
|
{
|
|
LOGI("{}", warn.c_str());
|
|
}
|
|
|
|
size_t pos = file_name.find_last_of('/');
|
|
|
|
model_path = file_name.substr(0, pos);
|
|
|
|
if (pos == std::string::npos)
|
|
{
|
|
model_path.clear();
|
|
}
|
|
|
|
return std::move(load_model(index, storage_buffer, additional_buffer_usage_flags));
|
|
}
|
|
|
|
sg::Scene GLTFLoader::load_scene(int scene_index, VkBufferUsageFlags additional_buffer_usage_flags)
|
|
{
|
|
PROFILE_SCOPE("Process Scene");
|
|
|
|
auto scene = sg::Scene();
|
|
|
|
scene.set_name("gltf_scene");
|
|
|
|
// Check extensions
|
|
for (auto &used_extension : model.extensionsUsed)
|
|
{
|
|
auto it = supported_extensions.find(used_extension);
|
|
|
|
// Check if extension isn't supported by the GLTFLoader
|
|
if (it == supported_extensions.end())
|
|
{
|
|
// If extension is required then we shouldn't allow the scene to be loaded
|
|
if (std::ranges::find(model.extensionsRequired, used_extension) != model.extensionsRequired.end())
|
|
{
|
|
throw std::runtime_error("Cannot load glTF file. Contains a required unsupported extension: " + used_extension);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, if extension isn't required (but is in the file) then print a warning to the user
|
|
LOGW("glTF file contains an unsupported extension, unexpected results may occur: {}", used_extension);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Extension is supported, so enable it
|
|
LOGI("glTF file contains extension: {}", used_extension);
|
|
it->second = true;
|
|
}
|
|
}
|
|
|
|
// Load lights
|
|
std::vector<std::unique_ptr<sg::Light>> light_components = parse_khr_lights_punctual();
|
|
|
|
scene.set_components(std::move(light_components));
|
|
|
|
// Load samplers
|
|
std::vector<std::unique_ptr<sg::Sampler>>
|
|
sampler_components(model.samplers.size());
|
|
|
|
for (size_t sampler_index = 0; sampler_index < model.samplers.size(); sampler_index++)
|
|
{
|
|
auto sampler = parse_sampler(model.samplers[sampler_index]);
|
|
sampler_components[sampler_index] = std::move(sampler);
|
|
}
|
|
|
|
scene.set_components(std::move(sampler_components));
|
|
|
|
Timer timer;
|
|
timer.start();
|
|
|
|
// Load images
|
|
auto image_count = to_u32(model.images.size());
|
|
|
|
std::vector<std::future<std::unique_ptr<sg::Image>>> image_component_futures;
|
|
for (size_t image_index = 0; image_index < image_count; image_index++)
|
|
{
|
|
image_component_futures.push_back(std::async(
|
|
[this, image_index]() {
|
|
auto image = parse_image(model.images[image_index]);
|
|
|
|
LOGI("Loaded gltf image #{} ({})", image_index, model.images[image_index].uri.c_str());
|
|
|
|
return image;
|
|
}));
|
|
}
|
|
|
|
std::vector<std::unique_ptr<sg::Image>> image_components;
|
|
|
|
// Upload images to GPU. We do this in batches of 64MB of data to avoid needing
|
|
// double the amount of memory (all the images and all the corresponding buffers).
|
|
// This helps keep memory footprint lower which is helpful on smaller devices.
|
|
size_t image_index = 0;
|
|
while (image_index < image_count)
|
|
{
|
|
std::vector<vkb::core::BufferC> transient_buffers;
|
|
|
|
auto command_buffer = device.get_command_pool().request_command_buffer();
|
|
|
|
command_buffer->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0);
|
|
|
|
size_t batch_size = 0;
|
|
|
|
// Deal with 64MB of image data at a time to keep memory footprint low
|
|
while (image_index < image_count && batch_size < 64 * 1024 * 1024)
|
|
{
|
|
// Wait for this image to complete loading, then stage for upload
|
|
image_components.push_back(image_component_futures[image_index].get());
|
|
|
|
auto &image = image_components[image_index];
|
|
|
|
core::Buffer stage_buffer = vkb::core::BufferC::create_staging_buffer(device, image->get_data());
|
|
|
|
batch_size += image->get_data().size();
|
|
|
|
upload_image_to_gpu(*command_buffer, stage_buffer, *image);
|
|
|
|
transient_buffers.push_back(std::move(stage_buffer));
|
|
|
|
image_index++;
|
|
}
|
|
|
|
command_buffer->end();
|
|
|
|
auto &queue = device.get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
|
|
queue.submit(*command_buffer, device.get_fence_pool().request_fence());
|
|
|
|
device.get_fence_pool().wait();
|
|
device.get_fence_pool().reset();
|
|
device.get_command_pool().reset_pool();
|
|
device.wait_idle();
|
|
|
|
// Remove the staging buffers for the batch we just processed
|
|
transient_buffers.clear();
|
|
}
|
|
|
|
scene.set_components(std::move(image_components));
|
|
|
|
auto elapsed_time = timer.stop();
|
|
|
|
auto thread_count = std::thread::hardware_concurrency();
|
|
thread_count = thread_count == 0 ? 1 : thread_count;
|
|
LOGI("Time spent loading images: {} seconds across {} threads.", vkb::to_string(elapsed_time), thread_count);
|
|
|
|
// Load textures
|
|
auto images = scene.get_components<sg::Image>();
|
|
auto samplers = scene.get_components<sg::Sampler>();
|
|
auto default_sampler_linear = create_default_sampler(TINYGLTF_TEXTURE_FILTER_LINEAR);
|
|
auto default_sampler_nearest = create_default_sampler(TINYGLTF_TEXTURE_FILTER_NEAREST);
|
|
bool used_nearest_sampler = false;
|
|
|
|
for (auto &gltf_texture : model.textures)
|
|
{
|
|
auto texture = parse_texture(gltf_texture);
|
|
|
|
assert(gltf_texture.source < images.size());
|
|
texture->set_image(*images[gltf_texture.source]);
|
|
|
|
if (gltf_texture.sampler >= 0 && gltf_texture.sampler < static_cast<int>(samplers.size()))
|
|
{
|
|
texture->set_sampler(*samplers[gltf_texture.sampler]);
|
|
}
|
|
else
|
|
{
|
|
if (gltf_texture.name.empty())
|
|
{
|
|
gltf_texture.name = images[gltf_texture.source]->get_name();
|
|
}
|
|
|
|
// Get the properties for the image format. We'll need to check whether a linear sampler is valid.
|
|
const VkFormatProperties fmtProps = device.get_gpu().get_format_properties(images[gltf_texture.source]->get_format());
|
|
|
|
if (fmtProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)
|
|
{
|
|
texture->set_sampler(*default_sampler_linear);
|
|
}
|
|
else
|
|
{
|
|
texture->set_sampler(*default_sampler_nearest);
|
|
used_nearest_sampler = true;
|
|
}
|
|
}
|
|
|
|
scene.add_component(std::move(texture));
|
|
}
|
|
|
|
scene.add_component(std::move(default_sampler_linear));
|
|
if (used_nearest_sampler)
|
|
{
|
|
scene.add_component(std::move(default_sampler_nearest));
|
|
}
|
|
|
|
// Load materials
|
|
bool has_textures = scene.has_component<sg::Texture>();
|
|
std::vector<vkb::sg::Texture *> textures;
|
|
if (has_textures)
|
|
{
|
|
textures = scene.get_components<sg::Texture>();
|
|
}
|
|
|
|
for (auto &gltf_material : model.materials)
|
|
{
|
|
auto material = parse_material(gltf_material);
|
|
|
|
for (auto &gltf_value : gltf_material.values)
|
|
{
|
|
if (gltf_value.first.find("Texture") != std::string::npos)
|
|
{
|
|
std::string tex_name = to_snake_case(gltf_value.first);
|
|
|
|
assert(gltf_value.second.TextureIndex() < textures.size());
|
|
vkb::sg::Texture *tex = textures[gltf_value.second.TextureIndex()];
|
|
|
|
if (texture_needs_srgb_colorspace(gltf_value.first))
|
|
{
|
|
tex->get_image()->coerce_format_to_srgb();
|
|
}
|
|
|
|
material->textures[tex_name] = tex;
|
|
}
|
|
}
|
|
|
|
for (auto &gltf_value : gltf_material.additionalValues)
|
|
{
|
|
if (gltf_value.first.find("Texture") != std::string::npos)
|
|
{
|
|
std::string tex_name = to_snake_case(gltf_value.first);
|
|
|
|
assert(gltf_value.second.TextureIndex() < textures.size());
|
|
vkb::sg::Texture *tex = textures[gltf_value.second.TextureIndex()];
|
|
|
|
if (texture_needs_srgb_colorspace(gltf_value.first))
|
|
{
|
|
tex->get_image()->coerce_format_to_srgb();
|
|
}
|
|
|
|
material->textures[tex_name] = tex;
|
|
}
|
|
}
|
|
|
|
scene.add_component(std::move(material));
|
|
}
|
|
|
|
auto default_material = create_default_material();
|
|
|
|
// Load meshes
|
|
auto materials = scene.get_components<sg::PBRMaterial>();
|
|
|
|
for (auto &gltf_mesh : model.meshes)
|
|
{
|
|
PROFILE_SCOPE("Processing Mesh");
|
|
|
|
auto mesh = parse_mesh(gltf_mesh);
|
|
|
|
for (size_t i_primitive = 0; i_primitive < gltf_mesh.primitives.size(); i_primitive++)
|
|
{
|
|
const auto &gltf_primitive = gltf_mesh.primitives[i_primitive];
|
|
|
|
auto submesh_name = fmt::format("'{}' mesh, primitive #{}", gltf_mesh.name, i_primitive);
|
|
auto submesh = std::make_unique<sg::SubMesh>(std::move(submesh_name));
|
|
|
|
for (auto &attribute : gltf_primitive.attributes)
|
|
{
|
|
std::string attrib_name = attribute.first;
|
|
std::transform(attrib_name.begin(), attrib_name.end(), attrib_name.begin(), ::tolower);
|
|
|
|
auto vertex_data = get_attribute_data(&model, attribute.second);
|
|
|
|
if (attrib_name == "position")
|
|
{
|
|
assert(attribute.second < model.accessors.size());
|
|
submesh->vertices_count = to_u32(model.accessors[attribute.second].count);
|
|
}
|
|
|
|
vkb::core::BufferC buffer{device,
|
|
vertex_data.size(),
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | additional_buffer_usage_flags,
|
|
VMA_MEMORY_USAGE_CPU_TO_GPU};
|
|
buffer.update(vertex_data);
|
|
buffer.set_debug_name(fmt::format("'{}' mesh, primitive #{}: '{}' vertex buffer",
|
|
gltf_mesh.name, i_primitive, attrib_name));
|
|
|
|
submesh->vertex_buffers.insert(std::make_pair(attrib_name, std::move(buffer)));
|
|
|
|
sg::VertexAttribute attrib;
|
|
attrib.format = get_attribute_format(&model, attribute.second);
|
|
attrib.stride = to_u32(get_attribute_stride(&model, attribute.second));
|
|
|
|
submesh->set_attribute(attrib_name, attrib);
|
|
}
|
|
|
|
if (gltf_primitive.indices >= 0)
|
|
{
|
|
submesh->vertex_indices = to_u32(get_attribute_size(&model, gltf_primitive.indices));
|
|
|
|
auto format = get_attribute_format(&model, gltf_primitive.indices);
|
|
|
|
auto index_data = get_attribute_data(&model, gltf_primitive.indices);
|
|
|
|
switch (format)
|
|
{
|
|
case VK_FORMAT_R8_UINT:
|
|
// Converts uint8 data into uint16 data, still represented by a uint8 vector
|
|
index_data = convert_underlying_data_stride(index_data, 1, 2);
|
|
submesh->index_type = VK_INDEX_TYPE_UINT16;
|
|
break;
|
|
case VK_FORMAT_R16_UINT:
|
|
submesh->index_type = VK_INDEX_TYPE_UINT16;
|
|
break;
|
|
case VK_FORMAT_R32_UINT:
|
|
submesh->index_type = VK_INDEX_TYPE_UINT32;
|
|
break;
|
|
default:
|
|
LOGE("gltf primitive has invalid format type");
|
|
break;
|
|
}
|
|
|
|
submesh->index_buffer = std::make_unique<vkb::core::BufferC>(device,
|
|
index_data.size(),
|
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | additional_buffer_usage_flags,
|
|
VMA_MEMORY_USAGE_GPU_TO_CPU);
|
|
submesh->index_buffer->set_debug_name(fmt::format("'{}' mesh, primitive #{}: index buffer",
|
|
gltf_mesh.name, i_primitive));
|
|
|
|
submesh->index_buffer->update(index_data);
|
|
}
|
|
else
|
|
{
|
|
submesh->vertices_count = to_u32(get_attribute_size(&model, gltf_primitive.attributes.at("POSITION")));
|
|
}
|
|
|
|
if (gltf_primitive.material < 0)
|
|
{
|
|
submesh->set_material(*default_material);
|
|
}
|
|
else
|
|
{
|
|
assert(gltf_primitive.material < materials.size());
|
|
submesh->set_material(*materials[gltf_primitive.material]);
|
|
}
|
|
|
|
mesh->add_submesh(*submesh);
|
|
|
|
scene.add_component(std::move(submesh));
|
|
}
|
|
|
|
scene.add_component(std::move(mesh));
|
|
}
|
|
|
|
device.get_fence_pool().wait();
|
|
device.get_fence_pool().reset();
|
|
device.get_command_pool().reset_pool();
|
|
|
|
scene.add_component(std::move(default_material));
|
|
|
|
// Load cameras
|
|
for (auto &gltf_camera : model.cameras)
|
|
{
|
|
auto camera = parse_camera(gltf_camera);
|
|
scene.add_component(std::move(camera));
|
|
}
|
|
|
|
// Load nodes
|
|
auto meshes = scene.get_components<sg::Mesh>();
|
|
|
|
std::vector<std::unique_ptr<sg::Node>> nodes;
|
|
|
|
for (size_t node_index = 0; node_index < model.nodes.size(); ++node_index)
|
|
{
|
|
auto gltf_node = model.nodes[node_index];
|
|
auto node = parse_node(gltf_node, node_index);
|
|
|
|
if (gltf_node.mesh >= 0)
|
|
{
|
|
assert(gltf_node.mesh < meshes.size());
|
|
auto mesh = meshes[gltf_node.mesh];
|
|
|
|
node->set_component(*mesh);
|
|
|
|
mesh->add_node(*node);
|
|
}
|
|
|
|
if (gltf_node.camera >= 0)
|
|
{
|
|
auto cameras = scene.get_components<sg::Camera>();
|
|
assert(gltf_node.camera < cameras.size());
|
|
auto camera = cameras[gltf_node.camera];
|
|
|
|
node->set_component(*camera);
|
|
|
|
camera->set_node(*node);
|
|
}
|
|
|
|
if (auto extension = get_extension(gltf_node.extensions, KHR_LIGHTS_PUNCTUAL_EXTENSION))
|
|
{
|
|
auto lights = scene.get_components<sg::Light>();
|
|
int light_index = extension->Get("light").Get<int>();
|
|
assert(light_index < lights.size());
|
|
auto light = lights[light_index];
|
|
|
|
node->set_component(*light);
|
|
|
|
light->set_node(*node);
|
|
}
|
|
|
|
nodes.push_back(std::move(node));
|
|
}
|
|
|
|
std::vector<std::unique_ptr<sg::Animation>> animations;
|
|
|
|
// Load animations
|
|
for (size_t animation_index = 0; animation_index < model.animations.size(); ++animation_index)
|
|
{
|
|
auto &gltf_animation = model.animations[animation_index];
|
|
|
|
std::vector<sg::AnimationSampler> samplers;
|
|
|
|
for (size_t sampler_index = 0; sampler_index < gltf_animation.samplers.size(); ++sampler_index)
|
|
{
|
|
auto gltf_sampler = gltf_animation.samplers[sampler_index];
|
|
|
|
sg::AnimationSampler sampler;
|
|
if (gltf_sampler.interpolation == "LINEAR")
|
|
{
|
|
sampler.type = sg::AnimationType::Linear;
|
|
}
|
|
else if (gltf_sampler.interpolation == "STEP")
|
|
{
|
|
sampler.type = sg::AnimationType::Step;
|
|
}
|
|
else if (gltf_sampler.interpolation == "CUBICSPLINE")
|
|
{
|
|
sampler.type = sg::AnimationType::CubicSpline;
|
|
}
|
|
else
|
|
{
|
|
LOGW("Gltf animation sampler #{} has unknown interpolation value", sampler_index);
|
|
}
|
|
|
|
auto input_accessor = model.accessors[gltf_sampler.input];
|
|
auto input_accessor_data = get_attribute_data(&model, gltf_sampler.input);
|
|
|
|
const float *data = reinterpret_cast<const float *>(input_accessor_data.data());
|
|
for (size_t i = 0; i < input_accessor.count; ++i)
|
|
{
|
|
sampler.inputs.push_back(data[i]);
|
|
}
|
|
|
|
auto output_accessor = model.accessors[gltf_sampler.output];
|
|
auto output_accessor_data = get_attribute_data(&model, gltf_sampler.output);
|
|
|
|
switch (output_accessor.type)
|
|
{
|
|
case TINYGLTF_TYPE_VEC3:
|
|
{
|
|
const glm::vec3 *data = reinterpret_cast<const glm::vec3 *>(output_accessor_data.data());
|
|
for (size_t i = 0; i < output_accessor.count; ++i)
|
|
{
|
|
sampler.outputs.push_back(glm::vec4(data[i], 0.0f));
|
|
}
|
|
break;
|
|
}
|
|
case TINYGLTF_TYPE_VEC4:
|
|
{
|
|
const glm::vec4 *data = reinterpret_cast<const glm::vec4 *>(output_accessor_data.data());
|
|
for (size_t i = 0; i < output_accessor.count; ++i)
|
|
{
|
|
sampler.outputs.push_back(glm::vec4(data[i]));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
LOGW("Gltf animation sampler #{} has unknown output data type", sampler_index);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
samplers.push_back(sampler);
|
|
}
|
|
|
|
auto animation = std::make_unique<sg::Animation>(gltf_animation.name);
|
|
|
|
for (size_t channel_index = 0; channel_index < gltf_animation.channels.size(); ++channel_index)
|
|
{
|
|
auto &gltf_channel = gltf_animation.channels[channel_index];
|
|
|
|
sg::AnimationTarget target;
|
|
if (gltf_channel.target_path == "translation")
|
|
{
|
|
target = sg::AnimationTarget::Translation;
|
|
}
|
|
else if (gltf_channel.target_path == "rotation")
|
|
{
|
|
target = sg::AnimationTarget::Rotation;
|
|
}
|
|
else if (gltf_channel.target_path == "scale")
|
|
{
|
|
target = sg::AnimationTarget::Scale;
|
|
}
|
|
else if (gltf_channel.target_path == "weights")
|
|
{
|
|
LOGW("Gltf animation channel #{} has unsupported target path: {}", channel_index, gltf_channel.target_path);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
LOGW("Gltf animation channel #{} has unknown target path", channel_index);
|
|
continue;
|
|
}
|
|
|
|
float start_time{std::numeric_limits<float>::max()};
|
|
float end_time{std::numeric_limits<float>::min()};
|
|
|
|
for (auto input : samplers[gltf_channel.sampler].inputs)
|
|
{
|
|
if (input < start_time)
|
|
{
|
|
start_time = input;
|
|
}
|
|
if (input > end_time)
|
|
{
|
|
end_time = input;
|
|
}
|
|
}
|
|
|
|
animation->update_times(start_time, end_time);
|
|
|
|
animation->add_channel(*nodes[gltf_channel.target_node], target, samplers[gltf_channel.sampler]);
|
|
}
|
|
|
|
animations.push_back(std::move(animation));
|
|
}
|
|
|
|
scene.set_components(std::move(animations));
|
|
|
|
// Load scenes
|
|
std::queue<std::pair<sg::Node &, int>> traverse_nodes;
|
|
|
|
tinygltf::Scene *gltf_scene{nullptr};
|
|
|
|
if (scene_index >= 0 && scene_index < static_cast<int>(model.scenes.size()))
|
|
{
|
|
gltf_scene = &model.scenes[scene_index];
|
|
}
|
|
else if (model.defaultScene >= 0 && model.defaultScene < static_cast<int>(model.scenes.size()))
|
|
{
|
|
gltf_scene = &model.scenes[model.defaultScene];
|
|
}
|
|
else if (model.scenes.size() > 0)
|
|
{
|
|
gltf_scene = &model.scenes[0];
|
|
}
|
|
|
|
if (!gltf_scene)
|
|
{
|
|
throw std::runtime_error("Couldn't determine which scene to load!");
|
|
}
|
|
|
|
auto root_node = std::make_unique<sg::Node>(0, gltf_scene->name);
|
|
|
|
for (auto node_index : gltf_scene->nodes)
|
|
{
|
|
traverse_nodes.push(std::make_pair(std::ref(*root_node), node_index));
|
|
}
|
|
|
|
while (!traverse_nodes.empty())
|
|
{
|
|
auto node_it = traverse_nodes.front();
|
|
traverse_nodes.pop();
|
|
|
|
// @todo: this crashes on some very basic scenes
|
|
// assert(node_it.second < nodes.size());
|
|
if (node_it.second >= nodes.size())
|
|
{
|
|
continue;
|
|
}
|
|
auto ¤t_node = *nodes[node_it.second];
|
|
auto &traverse_root_node = node_it.first;
|
|
|
|
current_node.set_parent(traverse_root_node);
|
|
traverse_root_node.add_child(current_node);
|
|
|
|
for (auto child_node_index : model.nodes[node_it.second].children)
|
|
{
|
|
traverse_nodes.push(std::make_pair(std::ref(current_node), child_node_index));
|
|
}
|
|
}
|
|
|
|
scene.set_root_node(*root_node);
|
|
nodes.push_back(std::move(root_node));
|
|
|
|
// Store nodes into the scene
|
|
scene.set_nodes(std::move(nodes));
|
|
|
|
// Create node for the default camera
|
|
auto camera_node = std::make_unique<sg::Node>(-1, "default_camera");
|
|
|
|
auto default_camera = create_default_camera();
|
|
default_camera->set_node(*camera_node);
|
|
camera_node->set_component(*default_camera);
|
|
scene.add_component(std::move(default_camera));
|
|
|
|
scene.get_root_node().add_child(*camera_node);
|
|
scene.add_node(std::move(camera_node));
|
|
|
|
if (!scene.has_component<vkb::sg::Light>())
|
|
{
|
|
// Add a default light if none are present
|
|
vkb::add_directional_light(scene, glm::quat({glm::radians(-90.0f), 0.0f, glm::radians(30.0f)}));
|
|
}
|
|
|
|
return scene;
|
|
}
|
|
|
|
std::unique_ptr<sg::SubMesh> GLTFLoader::load_model(uint32_t index, bool storage_buffer, VkBufferUsageFlags additional_buffer_usage_flags)
|
|
{
|
|
PROFILE_SCOPE("Process Model");
|
|
|
|
auto submesh = std::make_unique<sg::SubMesh>();
|
|
|
|
std::vector<vkb::core::BufferC> transient_buffers;
|
|
|
|
auto &queue = device.get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
|
|
|
|
auto command_buffer = device.get_command_pool().request_command_buffer();
|
|
|
|
command_buffer->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
|
|
|
|
assert(index < model.meshes.size());
|
|
auto &gltf_mesh = model.meshes[index];
|
|
|
|
assert(!gltf_mesh.primitives.empty());
|
|
auto &gltf_primitive = gltf_mesh.primitives[0];
|
|
|
|
std::vector<Vertex> vertex_data;
|
|
std::vector<AlignedVertex> aligned_vertex_data;
|
|
|
|
const float *pos = nullptr;
|
|
const float *normals = nullptr;
|
|
const float *uvs = nullptr;
|
|
const uint16_t *joints = nullptr;
|
|
const float *weights = nullptr;
|
|
const float *colors = nullptr;
|
|
uint32_t color_component_count{4};
|
|
|
|
// Position attribute is required
|
|
auto &accessor = model.accessors[gltf_primitive.attributes.find("POSITION")->second];
|
|
size_t vertex_count = accessor.count;
|
|
auto &buffer_view = model.bufferViews[accessor.bufferView];
|
|
pos = reinterpret_cast<const float *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
|
|
submesh->vertices_count = static_cast<uint32_t>(vertex_count);
|
|
|
|
if (gltf_primitive.attributes.find("NORMAL") != gltf_primitive.attributes.end())
|
|
{
|
|
accessor = model.accessors[gltf_primitive.attributes.find("NORMAL")->second];
|
|
buffer_view = model.bufferViews[accessor.bufferView];
|
|
normals = reinterpret_cast<const float *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
}
|
|
|
|
if (gltf_primitive.attributes.find("TEXCOORD_0") != gltf_primitive.attributes.end())
|
|
{
|
|
accessor = model.accessors[gltf_primitive.attributes.find("TEXCOORD_0")->second];
|
|
buffer_view = model.bufferViews[accessor.bufferView];
|
|
uvs = reinterpret_cast<const float *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
}
|
|
|
|
if (gltf_primitive.attributes.find("COLOR_0") != gltf_primitive.attributes.end())
|
|
{
|
|
accessor = model.accessors[gltf_primitive.attributes.find("COLOR_0")->second];
|
|
buffer_view = model.bufferViews[accessor.bufferView];
|
|
colors = reinterpret_cast<const float *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
color_component_count = accessor.type == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 ? 3 : 4;
|
|
}
|
|
|
|
// Skinning
|
|
// Joints
|
|
if (gltf_primitive.attributes.find("JOINTS_0") != gltf_primitive.attributes.end())
|
|
{
|
|
accessor = model.accessors[gltf_primitive.attributes.find("JOINTS_0")->second];
|
|
buffer_view = model.bufferViews[accessor.bufferView];
|
|
joints = reinterpret_cast<const uint16_t *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
}
|
|
|
|
if (gltf_primitive.attributes.find("WEIGHTS_0") != gltf_primitive.attributes.end())
|
|
{
|
|
accessor = model.accessors[gltf_primitive.attributes.find("WEIGHTS_0")->second];
|
|
buffer_view = model.bufferViews[accessor.bufferView];
|
|
weights = reinterpret_cast<const float *>(&(model.buffers[buffer_view.buffer].data[accessor.byteOffset + buffer_view.byteOffset]));
|
|
}
|
|
|
|
bool has_skin = (joints && weights);
|
|
|
|
if (storage_buffer)
|
|
{
|
|
for (size_t v = 0; v < vertex_count; v++)
|
|
{
|
|
AlignedVertex vert{};
|
|
vert.pos = glm::vec4(glm::make_vec3(&pos[v * 3]), 1.0f);
|
|
vert.normal = normals ? glm::vec4(glm::normalize(glm::make_vec3(&normals[v * 3])), 0.0f) : glm::vec4(0.0f);
|
|
aligned_vertex_data.push_back(vert);
|
|
}
|
|
|
|
vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(device, aligned_vertex_data);
|
|
|
|
vkb::core::BufferC buffer{device,
|
|
aligned_vertex_data.size() * sizeof(AlignedVertex),
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_GPU_ONLY};
|
|
|
|
command_buffer->copy_buffer(stage_buffer, buffer, aligned_vertex_data.size() * sizeof(AlignedVertex));
|
|
|
|
auto pair = std::make_pair("vertex_buffer", std::move(buffer));
|
|
submesh->vertex_buffers.insert(std::move(pair));
|
|
|
|
transient_buffers.push_back(std::move(stage_buffer));
|
|
}
|
|
else
|
|
{
|
|
for (size_t v = 0; v < vertex_count; v++)
|
|
{
|
|
Vertex vert{};
|
|
vert.pos = glm::vec4(glm::make_vec3(&pos[v * 3]), 1.0f);
|
|
vert.normal = glm::normalize(glm::vec3(normals ? glm::make_vec3(&normals[v * 3]) : glm::vec3(0.0f)));
|
|
vert.uv = uvs ? glm::make_vec2(&uvs[v * 2]) : glm::vec3(0.0f);
|
|
if (colors)
|
|
{
|
|
switch (color_component_count)
|
|
{
|
|
case 3:
|
|
vert.color = glm::vec4(glm::make_vec3(&colors[v * 3]), 1.0f);
|
|
break;
|
|
case 4:
|
|
vert.color = glm::make_vec4(&colors[v * 4]);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vert.color = glm::vec4(1.0f);
|
|
}
|
|
vert.joint0 = has_skin ? glm::vec4(glm::make_vec4(&joints[v * 4])) : glm::vec4(0.0f);
|
|
vert.weight0 = has_skin ? glm::make_vec4(&weights[v * 4]) : glm::vec4(0.0f);
|
|
vertex_data.push_back(vert);
|
|
}
|
|
|
|
vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(device, vertex_data);
|
|
|
|
vkb::core::BufferC buffer{device,
|
|
vertex_data.size() * sizeof(Vertex),
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_GPU_ONLY};
|
|
|
|
command_buffer->copy_buffer(stage_buffer, buffer, vertex_data.size() * sizeof(Vertex));
|
|
|
|
auto pair = std::make_pair("vertex_buffer", std::move(buffer));
|
|
submesh->vertex_buffers.insert(std::move(pair));
|
|
|
|
transient_buffers.push_back(std::move(stage_buffer));
|
|
}
|
|
|
|
if (gltf_primitive.indices >= 0)
|
|
{
|
|
submesh->vertex_indices = to_u32(get_attribute_size(&model, gltf_primitive.indices));
|
|
|
|
auto format = get_attribute_format(&model, gltf_primitive.indices);
|
|
auto index_data = get_attribute_data(&model, gltf_primitive.indices);
|
|
|
|
switch (format)
|
|
{
|
|
case VK_FORMAT_R32_UINT:
|
|
{
|
|
// Correct format
|
|
break;
|
|
}
|
|
case VK_FORMAT_R16_UINT:
|
|
{
|
|
index_data = convert_underlying_data_stride(index_data, 2, 4);
|
|
break;
|
|
}
|
|
case VK_FORMAT_R8_UINT:
|
|
{
|
|
index_data = convert_underlying_data_stride(index_data, 1, 4);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Always do uint32
|
|
submesh->index_type = VK_INDEX_TYPE_UINT32;
|
|
|
|
if (storage_buffer)
|
|
{
|
|
// prepare meshlets
|
|
std::vector<Meshlet> meshlets;
|
|
prepare_meshlets(meshlets, submesh, index_data);
|
|
|
|
// vertex_indices and index_buffer are used for meshlets now
|
|
submesh->vertex_indices = static_cast<uint32_t>(meshlets.size());
|
|
|
|
vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(device, meshlets);
|
|
|
|
submesh->index_buffer = std::make_unique<vkb::core::BufferC>(device,
|
|
meshlets.size() * sizeof(Meshlet),
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_GPU_ONLY);
|
|
|
|
command_buffer->copy_buffer(stage_buffer, *submesh->index_buffer, meshlets.size() * sizeof(Meshlet));
|
|
|
|
transient_buffers.push_back(std::move(stage_buffer));
|
|
}
|
|
else
|
|
{
|
|
vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(device, index_data);
|
|
|
|
submesh->index_buffer = std::make_unique<vkb::core::BufferC>(device,
|
|
index_data.size(),
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_GPU_ONLY);
|
|
|
|
command_buffer->copy_buffer(stage_buffer, *submesh->index_buffer, index_data.size());
|
|
|
|
transient_buffers.push_back(std::move(stage_buffer));
|
|
}
|
|
}
|
|
|
|
command_buffer->end();
|
|
|
|
queue.submit(*command_buffer, device.get_fence_pool().request_fence());
|
|
|
|
device.get_fence_pool().wait();
|
|
device.get_fence_pool().reset();
|
|
device.get_command_pool().reset_pool();
|
|
|
|
return std::move(submesh);
|
|
}
|
|
|
|
std::unique_ptr<sg::Node> GLTFLoader::parse_node(const tinygltf::Node &gltf_node, size_t index) const
|
|
{
|
|
auto node = std::make_unique<sg::Node>(index, gltf_node.name);
|
|
|
|
auto &transform = node->get_component<sg::Transform>();
|
|
|
|
if (!gltf_node.translation.empty())
|
|
{
|
|
glm::vec3 translation;
|
|
|
|
std::transform(gltf_node.translation.begin(), gltf_node.translation.end(), glm::value_ptr(translation), TypeCast<double, float>{});
|
|
|
|
transform.set_translation(translation);
|
|
}
|
|
|
|
if (!gltf_node.rotation.empty())
|
|
{
|
|
glm::quat rotation;
|
|
|
|
std::transform(gltf_node.rotation.begin(), gltf_node.rotation.end(), glm::value_ptr(rotation), TypeCast<double, float>{});
|
|
|
|
transform.set_rotation(rotation);
|
|
}
|
|
|
|
if (!gltf_node.scale.empty())
|
|
{
|
|
glm::vec3 scale;
|
|
|
|
std::transform(gltf_node.scale.begin(), gltf_node.scale.end(), glm::value_ptr(scale), TypeCast<double, float>{});
|
|
|
|
transform.set_scale(scale);
|
|
}
|
|
|
|
if (!gltf_node.matrix.empty())
|
|
{
|
|
glm::mat4 matrix;
|
|
|
|
std::transform(gltf_node.matrix.begin(), gltf_node.matrix.end(), glm::value_ptr(matrix), TypeCast<double, float>{});
|
|
|
|
transform.set_matrix(matrix);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
std::unique_ptr<sg::Camera> GLTFLoader::parse_camera(const tinygltf::Camera &gltf_camera) const
|
|
{
|
|
std::unique_ptr<sg::Camera> camera;
|
|
|
|
if (gltf_camera.type == "perspective")
|
|
{
|
|
auto perspective_camera = std::make_unique<sg::PerspectiveCamera>(gltf_camera.name);
|
|
|
|
perspective_camera->set_aspect_ratio(static_cast<float>(gltf_camera.perspective.aspectRatio));
|
|
perspective_camera->set_field_of_view(static_cast<float>(gltf_camera.perspective.yfov));
|
|
perspective_camera->set_near_plane(static_cast<float>(gltf_camera.perspective.znear));
|
|
perspective_camera->set_far_plane(static_cast<float>(gltf_camera.perspective.zfar));
|
|
|
|
camera = std::move(perspective_camera);
|
|
}
|
|
else
|
|
{
|
|
LOGW("Camera type not supported");
|
|
}
|
|
|
|
return camera;
|
|
}
|
|
|
|
std::unique_ptr<sg::Mesh> GLTFLoader::parse_mesh(const tinygltf::Mesh &gltf_mesh) const
|
|
{
|
|
return std::make_unique<sg::Mesh>(gltf_mesh.name);
|
|
}
|
|
|
|
std::unique_ptr<sg::PBRMaterial> GLTFLoader::parse_material(const tinygltf::Material &gltf_material) const
|
|
{
|
|
auto material = std::make_unique<sg::PBRMaterial>(gltf_material.name);
|
|
|
|
// Initialize base color to 1.0f as per glTF spec
|
|
material->base_color_factor = glm::vec4(1.0f);
|
|
|
|
for (auto &gltf_value : gltf_material.values)
|
|
{
|
|
if (gltf_value.first == "baseColorFactor")
|
|
{
|
|
const auto &color_factor = gltf_value.second.ColorFactor();
|
|
material->base_color_factor = glm::vec4(color_factor[0], color_factor[1], color_factor[2], color_factor[3]);
|
|
}
|
|
else if (gltf_value.first == "metallicFactor")
|
|
{
|
|
material->metallic_factor = static_cast<float>(gltf_value.second.Factor());
|
|
}
|
|
else if (gltf_value.first == "roughnessFactor")
|
|
{
|
|
material->roughness_factor = static_cast<float>(gltf_value.second.Factor());
|
|
}
|
|
}
|
|
|
|
for (auto &gltf_value : gltf_material.additionalValues)
|
|
{
|
|
if (gltf_value.first == "emissiveFactor")
|
|
{
|
|
const auto &emissive_factor = gltf_value.second.number_array;
|
|
|
|
material->emissive = glm::vec3(emissive_factor[0], emissive_factor[1], emissive_factor[2]);
|
|
}
|
|
else if (gltf_value.first == "alphaMode")
|
|
{
|
|
if (gltf_value.second.string_value == "BLEND")
|
|
{
|
|
material->alpha_mode = vkb::sg::AlphaMode::Blend;
|
|
}
|
|
else if (gltf_value.second.string_value == "OPAQUE")
|
|
{
|
|
material->alpha_mode = vkb::sg::AlphaMode::Opaque;
|
|
}
|
|
else if (gltf_value.second.string_value == "MASK")
|
|
{
|
|
material->alpha_mode = vkb::sg::AlphaMode::Mask;
|
|
}
|
|
}
|
|
else if (gltf_value.first == "alphaCutoff")
|
|
{
|
|
material->alpha_cutoff = static_cast<float>(gltf_value.second.number_value);
|
|
}
|
|
else if (gltf_value.first == "doubleSided")
|
|
{
|
|
material->double_sided = gltf_value.second.bool_value;
|
|
}
|
|
}
|
|
|
|
return material;
|
|
}
|
|
|
|
std::unique_ptr<sg::Image> GLTFLoader::parse_image(tinygltf::Image &gltf_image) const
|
|
{
|
|
std::unique_ptr<sg::Image> image{nullptr};
|
|
|
|
if (gltf_image.name.empty())
|
|
{
|
|
gltf_image.name = gltf_image.uri;
|
|
}
|
|
|
|
if (!gltf_image.image.empty())
|
|
{
|
|
// Image embedded in gltf file
|
|
auto mipmap = sg::Mipmap{
|
|
/* .level = */ 0,
|
|
/* .offset = */ 0,
|
|
/* .extent = */ {/* .width = */ static_cast<uint32_t>(gltf_image.width),
|
|
/* .height = */ static_cast<uint32_t>(gltf_image.height),
|
|
/* .depth = */ 1u}};
|
|
std::vector<sg::Mipmap> mipmaps{mipmap};
|
|
image = std::make_unique<sg::Image>(gltf_image.name, std::move(gltf_image.image), std::move(mipmaps));
|
|
}
|
|
else
|
|
{
|
|
// Load image from uri
|
|
auto image_uri = model_path + "/" + gltf_image.uri;
|
|
image = sg::Image::load(gltf_image.name, image_uri, vkb::sg::Image::Unknown);
|
|
}
|
|
|
|
// Check whether the format is supported by the GPU
|
|
if (sg::is_astc(image->get_format()))
|
|
{
|
|
if (!device.is_image_format_supported(image->get_format()))
|
|
{
|
|
image = std::make_unique<sg::Astc>(*image);
|
|
image->generate_mipmaps();
|
|
}
|
|
}
|
|
|
|
image->create_vk_image(device);
|
|
|
|
return image;
|
|
}
|
|
|
|
std::unique_ptr<sg::Sampler> GLTFLoader::parse_sampler(const tinygltf::Sampler &gltf_sampler) const
|
|
{
|
|
auto name = gltf_sampler.name;
|
|
|
|
VkFilter min_filter = find_min_filter(gltf_sampler.minFilter);
|
|
VkFilter mag_filter = find_mag_filter(gltf_sampler.magFilter);
|
|
|
|
VkSamplerMipmapMode mipmap_mode = find_mipmap_mode(gltf_sampler.minFilter);
|
|
|
|
VkSamplerAddressMode address_mode_u = find_wrap_mode(gltf_sampler.wrapS);
|
|
VkSamplerAddressMode address_mode_v = find_wrap_mode(gltf_sampler.wrapT);
|
|
|
|
VkSamplerCreateInfo sampler_info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
|
|
sampler_info.magFilter = mag_filter;
|
|
sampler_info.minFilter = min_filter;
|
|
sampler_info.mipmapMode = mipmap_mode;
|
|
sampler_info.addressModeU = address_mode_u;
|
|
sampler_info.addressModeV = address_mode_v;
|
|
sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
sampler_info.maxLod = std::numeric_limits<float>::max();
|
|
|
|
core::Sampler vk_sampler{device, sampler_info};
|
|
vk_sampler.set_debug_name(gltf_sampler.name);
|
|
|
|
return std::make_unique<sg::Sampler>(name, std::move(vk_sampler));
|
|
}
|
|
|
|
std::unique_ptr<sg::Texture> GLTFLoader::parse_texture(const tinygltf::Texture &gltf_texture) const
|
|
{
|
|
return std::make_unique<sg::Texture>(gltf_texture.name);
|
|
}
|
|
|
|
std::unique_ptr<sg::PBRMaterial> GLTFLoader::create_default_material()
|
|
{
|
|
tinygltf::Material gltf_material;
|
|
return parse_material(gltf_material);
|
|
}
|
|
|
|
std::unique_ptr<sg::Sampler> GLTFLoader::create_default_sampler(int filter)
|
|
{
|
|
tinygltf::Sampler gltf_sampler;
|
|
|
|
gltf_sampler.minFilter = filter;
|
|
gltf_sampler.magFilter = filter;
|
|
|
|
gltf_sampler.wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT;
|
|
gltf_sampler.wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT;
|
|
|
|
return parse_sampler(gltf_sampler);
|
|
}
|
|
|
|
std::unique_ptr<sg::Camera> GLTFLoader::create_default_camera()
|
|
{
|
|
tinygltf::Camera gltf_camera;
|
|
|
|
gltf_camera.name = "default_camera";
|
|
gltf_camera.type = "perspective";
|
|
|
|
gltf_camera.perspective.aspectRatio = 1.77f;
|
|
gltf_camera.perspective.yfov = 1.0f;
|
|
gltf_camera.perspective.znear = 0.1f;
|
|
gltf_camera.perspective.zfar = 1000.0f;
|
|
|
|
return parse_camera(gltf_camera);
|
|
}
|
|
|
|
std::vector<std::unique_ptr<sg::Light>> GLTFLoader::parse_khr_lights_punctual()
|
|
{
|
|
if (is_extension_enabled(KHR_LIGHTS_PUNCTUAL_EXTENSION))
|
|
{
|
|
if (model.extensions.find(KHR_LIGHTS_PUNCTUAL_EXTENSION) == model.extensions.end() || !model.extensions.at(KHR_LIGHTS_PUNCTUAL_EXTENSION).Has("lights"))
|
|
{
|
|
return {};
|
|
}
|
|
auto &khr_lights = model.extensions.at(KHR_LIGHTS_PUNCTUAL_EXTENSION).Get("lights");
|
|
|
|
std::vector<std::unique_ptr<sg::Light>> light_components(khr_lights.ArrayLen());
|
|
|
|
for (size_t light_index = 0; light_index < khr_lights.ArrayLen(); ++light_index)
|
|
{
|
|
auto &khr_light = khr_lights.Get(static_cast<int>(light_index));
|
|
|
|
// Spec states a light has to have a type to be valid
|
|
if (!khr_light.Has("type"))
|
|
{
|
|
LOGE("KHR_lights_punctual extension: light {} doesn't have a type!", light_index);
|
|
throw std::runtime_error("Couldn't load glTF file, KHR_lights_punctual extension is invalid");
|
|
}
|
|
|
|
auto light = std::make_unique<sg::Light>(khr_light.Get("name").Get<std::string>());
|
|
|
|
sg::LightType type;
|
|
sg::LightProperties properties;
|
|
|
|
// Get type
|
|
auto &gltf_light_type = khr_light.Get("type").Get<std::string>();
|
|
if (gltf_light_type == "point")
|
|
{
|
|
type = sg::LightType::Point;
|
|
}
|
|
else if (gltf_light_type == "spot")
|
|
{
|
|
type = sg::LightType::Spot;
|
|
}
|
|
else if (gltf_light_type == "directional")
|
|
{
|
|
type = sg::LightType::Directional;
|
|
}
|
|
else
|
|
{
|
|
LOGE("KHR_lights_punctual extension: light type '{}' is invalid", gltf_light_type);
|
|
throw std::runtime_error("Couldn't load glTF file, KHR_lights_punctual extension is invalid");
|
|
}
|
|
|
|
// Get properties
|
|
if (khr_light.Has("color"))
|
|
{
|
|
properties.color = glm::vec3(
|
|
static_cast<float>(khr_light.Get("color").Get(0).Get<double>()),
|
|
static_cast<float>(khr_light.Get("color").Get(1).Get<double>()),
|
|
static_cast<float>(khr_light.Get("color").Get(2).Get<double>()));
|
|
}
|
|
|
|
if (khr_light.Has("intensity"))
|
|
{
|
|
properties.intensity = static_cast<float>(khr_light.Get("intensity").Get<double>());
|
|
}
|
|
|
|
if (type != sg::LightType::Directional)
|
|
{
|
|
properties.range = static_cast<float>(khr_light.Get("range").Get<double>());
|
|
if (type != sg::LightType::Point)
|
|
{
|
|
if (!khr_light.Has("spot"))
|
|
{
|
|
LOGE("KHR_lights_punctual extension: spot light doesn't have a 'spot' property set", gltf_light_type);
|
|
throw std::runtime_error("Couldn't load glTF file, KHR_lights_punctual extension is invalid");
|
|
}
|
|
|
|
properties.inner_cone_angle = static_cast<float>(khr_light.Get("spot").Get("innerConeAngle").Get<double>());
|
|
|
|
if (khr_light.Get("spot").Has("outerConeAngle"))
|
|
{
|
|
properties.outer_cone_angle = static_cast<float>(khr_light.Get("spot").Get("outerConeAngle").Get<double>());
|
|
}
|
|
else
|
|
{
|
|
// Spec states default value is PI/4
|
|
properties.outer_cone_angle = glm::pi<float>() / 4.0f;
|
|
}
|
|
}
|
|
}
|
|
else if (type == sg::LightType::Directional || type == sg::LightType::Spot)
|
|
{
|
|
// The spec states that the light will inherit the transform of the node.
|
|
// The light's direction is defined as the 3-vector (0.0, 0.0, -1.0) and
|
|
// the rotation of the node orients the light accordingly.
|
|
properties.direction = glm::vec3(0.0f, 0.0f, -1.0f);
|
|
}
|
|
|
|
light->set_light_type(type);
|
|
light->set_properties(properties);
|
|
|
|
light_components[light_index] = std::move(light);
|
|
}
|
|
|
|
return light_components;
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool GLTFLoader::is_extension_enabled(const std::string &requested_extension)
|
|
{
|
|
auto it = supported_extensions.find(requested_extension);
|
|
if (it != supported_extensions.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
tinygltf::Value *GLTFLoader::get_extension(tinygltf::ExtensionMap &tinygltf_extensions, const std::string &extension)
|
|
{
|
|
auto it = tinygltf_extensions.find(extension);
|
|
if (it != tinygltf_extensions.end())
|
|
{
|
|
return &it->second;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
} // namespace vkb
|