initial commit-moved from vulkan_guide

This commit is contained in:
2025-10-10 22:53:54 +09:00
commit 8853429937
2484 changed files with 973414 additions and 0 deletions

600
src/scene/vk_loader.cpp Normal file
View File

@@ -0,0 +1,600 @@
#include "stb_image.h"
#include <iostream>
#include "vk_loader.h"
#include "core/vk_engine.h"
#include "render/vk_materials.h"
#include "core/vk_initializers.h"
#include "core/vk_types.h"
#include <glm/gtx/quaternion.hpp>
#include <fastgltf/glm_element_traits.hpp>
#include <fastgltf/parser.hpp>
#include <fastgltf/tools.hpp>
#include <fastgltf/util.hpp>
#include <optional>
//> loadimg
std::optional<AllocatedImage> load_image(VulkanEngine *engine, fastgltf::Asset &asset, fastgltf::Image &image, bool srgb)
{
AllocatedImage newImage{};
int width, height, nrChannels;
std::visit(
fastgltf::visitor{
[](auto &arg) {
},
[&](fastgltf::sources::URI &filePath) {
assert(filePath.fileByteOffset == 0); // We don't support offsets with stbi.
assert(filePath.uri.isLocalPath()); // We're only capable of loading
// local files.
const std::string path(filePath.uri.path().begin(),
filePath.uri.path().end()); // Thanks C++.
unsigned char *data = stbi_load(path.c_str(), &width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
},
[&](fastgltf::sources::Vector &vector) {
unsigned char *data = stbi_load_from_memory(vector.bytes.data(), static_cast<int>(vector.bytes.size()),
&width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
},
[&](fastgltf::sources::BufferView &view) {
auto &bufferView = asset.bufferViews[view.bufferViewIndex];
auto &buffer = asset.buffers[bufferView.bufferIndex];
std::visit(fastgltf::visitor{
// We only care about VectorWithMime here, because we
// specify LoadExternalBuffers, meaning all buffers
// are already loaded into a vector.
[](auto &arg) {
},
[&](fastgltf::sources::Vector &vector) {
unsigned char *data = stbi_load_from_memory(
vector.bytes.data() + bufferView.byteOffset,
static_cast<int>(bufferView.byteLength),
&width, &height, &nrChannels, 4);
if (data)
{
VkExtent3D imagesize;
imagesize.width = width;
imagesize.height = height;
imagesize.depth = 1;
VkFormat fmt = srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
newImage = engine->_resourceManager->create_image(
data, imagesize, fmt, VK_IMAGE_USAGE_SAMPLED_BIT, false);
stbi_image_free(data);
}
}
},
buffer.data);
},
},
image.data);
// if any of the attempts to load the data failed, we havent written the image
// so handle is null
if (newImage.image == VK_NULL_HANDLE)
{
return {};
}
else
{
return newImage;
}
}
//< loadimg
//> filters
VkFilter extract_filter(fastgltf::Filter filter)
{
switch (filter)
{
// nearest samplers
case fastgltf::Filter::Nearest:
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::NearestMipMapLinear:
return VK_FILTER_NEAREST;
// linear samplers
case fastgltf::Filter::Linear:
case fastgltf::Filter::LinearMipMapNearest:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_FILTER_LINEAR;
}
}
VkSamplerMipmapMode extract_mipmap_mode(fastgltf::Filter filter)
{
switch (filter)
{
case fastgltf::Filter::NearestMipMapNearest:
case fastgltf::Filter::LinearMipMapNearest:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case fastgltf::Filter::NearestMipMapLinear:
case fastgltf::Filter::LinearMipMapLinear:
default:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
}
//< filters
std::optional<std::shared_ptr<LoadedGLTF> > loadGltf(VulkanEngine *engine, std::string_view filePath)
{
//> load_1
fmt::print("Loading GLTF: {}", filePath);
std::shared_ptr<LoadedGLTF> scene = std::make_shared<LoadedGLTF>();
scene->creator = engine;
LoadedGLTF &file = *scene.get();
fastgltf::Parser parser{};
constexpr auto gltfOptions = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble |
fastgltf::Options::LoadGLBBuffers | fastgltf::Options::LoadExternalBuffers;
// fastgltf::Options::LoadExternalImages;
fastgltf::GltfDataBuffer data;
data.loadFromFile(filePath);
fastgltf::Asset gltf;
std::filesystem::path path = filePath;
auto type = fastgltf::determineGltfFileType(&data);
if (type == fastgltf::GltfType::glTF)
{
auto load = parser.loadGLTF(&data, path.parent_path(), gltfOptions);
if (load)
{
gltf = std::move(load.get());
}
else
{
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
}
else if (type == fastgltf::GltfType::GLB)
{
auto load = parser.loadBinaryGLTF(&data, path.parent_path(), gltfOptions);
if (load)
{
gltf = std::move(load.get());
}
else
{
std::cerr << "Failed to load glTF: " << fastgltf::to_underlying(load.error()) << std::endl;
return {};
}
}
else
{
std::cerr << "Failed to determine glTF container" << std::endl;
return {};
}
//< load_1
//> load_2
// we can stimate the descriptors we will need accurately
std::vector<DescriptorAllocatorGrowable::PoolSizeRatio> sizes = {
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}
};
file.descriptorPool.init(engine->_deviceManager->device(), gltf.materials.size(), sizes);
//< load_2
//> load_samplers
// load samplers
for (fastgltf::Sampler &sampler: gltf.samplers)
{
VkSamplerCreateInfo sampl = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .pNext = nullptr};
sampl.maxLod = VK_LOD_CLAMP_NONE;
sampl.minLod = 0.0f;
sampl.magFilter = extract_filter(sampler.magFilter.value_or(fastgltf::Filter::Nearest));
sampl.minFilter = extract_filter(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
sampl.mipmapMode = extract_mipmap_mode(sampler.minFilter.value_or(fastgltf::Filter::Nearest));
// Address modes: default to glTF Repeat
auto toAddress = [](fastgltf::Wrap w) -> VkSamplerAddressMode {
switch (w) {
case fastgltf::Wrap::ClampToEdge: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case fastgltf::Wrap::MirroredRepeat: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case fastgltf::Wrap::Repeat:
default: return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
};
// fastgltf::Sampler::wrapS/wrapT are non-optional and already default to Repeat
sampl.addressModeU = toAddress(sampler.wrapS);
sampl.addressModeV = toAddress(sampler.wrapT);
sampl.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampl.unnormalizedCoordinates = VK_FALSE;
VkSampler newSampler;
vkCreateSampler(engine->_deviceManager->device(), &sampl, nullptr, &newSampler);
file.samplers.push_back(newSampler);
}
//< load_samplers
//> load_arrays
// temporal arrays for all the objects to use while creating the GLTF data
std::vector<std::shared_ptr<MeshAsset> > meshes;
std::vector<std::shared_ptr<Node> > nodes;
std::vector<AllocatedImage> images;
std::vector<std::shared_ptr<GLTFMaterial> > materials;
//< load_arrays
// load all textures
for (fastgltf::Image &image: gltf.images)
{
// Default-load GLTF images as linear; baseColor is reloaded as sRGB when bound
std::optional<AllocatedImage> img = load_image(engine, gltf, image, false);
if (img.has_value())
{
images.push_back(*img);
file.images[image.name.c_str()] = *img;
}
else
{
// we failed to load, so lets give the slot a default white texture to not
// completely break loading
images.push_back(engine->_errorCheckerboardImage);
std::cout << "gltf failed to load texture " << image.name << std::endl;
}
}
//> load_buffer
// create buffer to hold the material data
file.materialDataBuffer = engine->_resourceManager->create_buffer(
sizeof(GLTFMetallic_Roughness::MaterialConstants) * gltf.materials.size(),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
int data_index = 0;
GLTFMetallic_Roughness::MaterialConstants *sceneMaterialConstants = (GLTFMetallic_Roughness::MaterialConstants *)
file.materialDataBuffer.info.pMappedData;
//< load_buffer
//
//> load_material
for (fastgltf::Material &mat: gltf.materials)
{
std::shared_ptr<GLTFMaterial> newMat = std::make_shared<GLTFMaterial>();
materials.push_back(newMat);
file.materials[mat.name.c_str()] = newMat;
GLTFMetallic_Roughness::MaterialConstants constants;
constants.colorFactors.x = mat.pbrData.baseColorFactor[0];
constants.colorFactors.y = mat.pbrData.baseColorFactor[1];
constants.colorFactors.z = mat.pbrData.baseColorFactor[2];
constants.colorFactors.w = mat.pbrData.baseColorFactor[3];
constants.metal_rough_factors.x = mat.pbrData.metallicFactor;
constants.metal_rough_factors.y = mat.pbrData.roughnessFactor;
// write material parameters to buffer
sceneMaterialConstants[data_index] = constants;
MaterialPass passType = MaterialPass::MainColor;
if (mat.alphaMode == fastgltf::AlphaMode::Blend)
{
passType = MaterialPass::Transparent;
}
GLTFMetallic_Roughness::MaterialResources materialResources;
// default the material textures
materialResources.colorImage = engine->_whiteImage;
materialResources.colorSampler = engine->_samplerManager->defaultLinear();
materialResources.metalRoughImage = engine->_whiteImage;
materialResources.metalRoughSampler = engine->_samplerManager->defaultLinear();
// set the uniform buffer for the material data
materialResources.dataBuffer = file.materialDataBuffer.buffer;
materialResources.dataBufferOffset = data_index * sizeof(GLTFMetallic_Roughness::MaterialConstants);
// grab textures from gltf file
if (mat.pbrData.baseColorTexture.has_value())
{
const auto &tex = gltf.textures[mat.pbrData.baseColorTexture.value().textureIndex];
size_t imgIndex = tex.imageIndex.value();
// Sampler is optional in glTF; fall back to default if missing
bool hasSampler = tex.samplerIndex.has_value();
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
// Reload albedo as sRGB, independent of the global image cache
if (imgIndex < gltf.images.size())
{
auto albedoImg = load_image(engine, gltf, gltf.images[imgIndex], true);
if (albedoImg.has_value())
{
materialResources.colorImage = *albedoImg;
// Track for cleanup using a unique key
std::string key = std::string("albedo_") + mat.name.c_str() + "_" + std::to_string(imgIndex);
file.images[key] = *albedoImg;
}
else
{
materialResources.colorImage = images[imgIndex];
}
}
else
{
materialResources.colorImage = engine->_errorCheckerboardImage;
}
materialResources.colorSampler = hasSampler ? file.samplers[sampler]
: engine->_samplerManager->defaultLinear();
}
// Metallic-Roughness texture
if (mat.pbrData.metallicRoughnessTexture.has_value())
{
const auto &tex = gltf.textures[mat.pbrData.metallicRoughnessTexture.value().textureIndex];
size_t imgIndex = tex.imageIndex.value();
bool hasSampler = tex.samplerIndex.has_value();
size_t sampler = hasSampler ? tex.samplerIndex.value() : SIZE_MAX;
if (imgIndex < images.size())
{
materialResources.metalRoughImage = images[imgIndex];
materialResources.metalRoughSampler = hasSampler ? file.samplers[sampler]
: engine->_samplerManager->defaultLinear();
}
}
// build material
newMat->data = engine->metalRoughMaterial.write_material(engine->_deviceManager->device(), passType, materialResources,
file.descriptorPool);
data_index++;
}
//< load_material
// Flush material constants buffer so GPU sees updated data on non-coherent memory
if (!gltf.materials.empty())
{
VkDeviceSize totalSize = sizeof(GLTFMetallic_Roughness::MaterialConstants) * gltf.materials.size();
vmaFlushAllocation(engine->_deviceManager->allocator(), file.materialDataBuffer.allocation, 0, totalSize);
}
// use the same vectors for all meshes so that the memory doesnt reallocate as
// often
std::vector<uint32_t> indices;
std::vector<Vertex> vertices;
for (fastgltf::Mesh &mesh: gltf.meshes)
{
std::shared_ptr<MeshAsset> newmesh = std::make_shared<MeshAsset>();
meshes.push_back(newmesh);
file.meshes[mesh.name.c_str()] = newmesh;
newmesh->name = mesh.name;
// clear the mesh arrays each mesh, we dont want to merge them by error
indices.clear();
vertices.clear();
for (auto &&p: mesh.primitives)
{
GeoSurface newSurface;
newSurface.startIndex = (uint32_t) indices.size();
newSurface.count = (uint32_t) gltf.accessors[p.indicesAccessor.value()].count;
size_t initial_vtx = vertices.size();
// load indexes
{
fastgltf::Accessor &indexaccessor = gltf.accessors[p.indicesAccessor.value()];
indices.reserve(indices.size() + indexaccessor.count);
fastgltf::iterateAccessor<std::uint32_t>(gltf, indexaccessor,
[&](std::uint32_t idx) {
indices.push_back(idx + initial_vtx);
});
}
// load vertex positions
{
fastgltf::Accessor &posAccessor = gltf.accessors[p.findAttribute("POSITION")->second];
vertices.resize(vertices.size() + posAccessor.count);
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, posAccessor,
[&](glm::vec3 v, size_t index) {
Vertex newvtx;
newvtx.position = v;
newvtx.normal = {1, 0, 0};
newvtx.color = glm::vec4{1.f};
newvtx.uv_x = 0;
newvtx.uv_y = 0;
vertices[initial_vtx + index] = newvtx;
});
}
// load vertex normals
auto normals = p.findAttribute("NORMAL");
if (normals != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec3>(gltf, gltf.accessors[(*normals).second],
[&](glm::vec3 v, size_t index) {
vertices[initial_vtx + index].normal = v;
});
}
// load UVs
auto uv = p.findAttribute("TEXCOORD_0");
if (uv != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec2>(gltf, gltf.accessors[(*uv).second],
[&](glm::vec2 v, size_t index) {
vertices[initial_vtx + index].uv_x = v.x;
vertices[initial_vtx + index].uv_y = v.y;
});
}
// load vertex colors
auto colors = p.findAttribute("COLOR_0");
if (colors != p.attributes.end())
{
fastgltf::iterateAccessorWithIndex<glm::vec4>(gltf, gltf.accessors[(*colors).second],
[&](glm::vec4 v, size_t index) {
vertices[initial_vtx + index].color = v;
});
}
if (p.materialIndex.has_value())
{
newSurface.material = materials[p.materialIndex.value()];
}
else
{
newSurface.material = materials[0];
}
glm::vec3 minpos = vertices[initial_vtx].position;
glm::vec3 maxpos = vertices[initial_vtx].position;
for (int i = initial_vtx; i < vertices.size(); i++)
{
minpos = glm::min(minpos, vertices[i].position);
maxpos = glm::max(maxpos, vertices[i].position);
}
newSurface.bounds.origin = (maxpos + minpos) / 2.f;
newSurface.bounds.extents = (maxpos - minpos) / 2.f;
newSurface.bounds.sphereRadius = glm::length(newSurface.bounds.extents);
newmesh->surfaces.push_back(newSurface);
}
newmesh->meshBuffers = engine->_resourceManager->uploadMesh(indices, vertices);
}
//> load_nodes
// load all nodes and their meshes
for (fastgltf::Node &node: gltf.nodes)
{
std::shared_ptr<Node> newNode;
// find if the node has a mesh, and if it does hook it to the mesh pointer and allocate it with the meshnode class
if (node.meshIndex.has_value())
{
newNode = std::make_shared<MeshNode>();
static_cast<MeshNode *>(newNode.get())->mesh = meshes[*node.meshIndex];
}
else
{
newNode = std::make_shared<Node>();
}
nodes.push_back(newNode);
file.nodes[node.name.c_str()];
std::visit(fastgltf::visitor{
[&](fastgltf::Node::TransformMatrix matrix) {
memcpy(&newNode->localTransform, matrix.data(), sizeof(matrix));
},
[&](fastgltf::Node::TRS transform) {
glm::vec3 tl(transform.translation[0], transform.translation[1],
transform.translation[2]);
glm::quat rot(transform.rotation[3], transform.rotation[0], transform.rotation[1],
transform.rotation[2]);
glm::vec3 sc(transform.scale[0], transform.scale[1], transform.scale[2]);
glm::mat4 tm = glm::translate(glm::mat4(1.f), tl);
glm::mat4 rm = glm::toMat4(rot);
glm::mat4 sm = glm::scale(glm::mat4(1.f), sc);
newNode->localTransform = tm * rm * sm;
}
},
node.transform);
}
//< load_nodes
//> load_graph
// run loop again to setup transform hierarchy
for (int i = 0; i < gltf.nodes.size(); i++)
{
fastgltf::Node &node = gltf.nodes[i];
std::shared_ptr<Node> &sceneNode = nodes[i];
for (auto &c: node.children)
{
sceneNode->children.push_back(nodes[c]);
nodes[c]->parent = sceneNode;
}
}
// find the top nodes, with no parents
for (auto &node: nodes)
{
if (node->parent.lock() == nullptr)
{
file.topNodes.push_back(node);
node->refreshTransform(glm::mat4{1.f});
}
}
return scene;
//< load_graph
}
void LoadedGLTF::Draw(const glm::mat4 &topMatrix, DrawContext &ctx)
{
// create renderables from the scenenodes
for (auto &n: topNodes)
{
n->Draw(topMatrix, ctx);
}
}
void LoadedGLTF::clearAll()
{
VkDevice dv = creator->_deviceManager->device();
for (auto &[k, v]: meshes)
{
creator->_resourceManager->destroy_buffer(v->meshBuffers.indexBuffer);
creator->_resourceManager->destroy_buffer(v->meshBuffers.vertexBuffer);
}
for (auto &[k, v]: images)
{
if (v.image == creator->_errorCheckerboardImage.image)
{
// dont destroy the default images
continue;
}
creator->_resourceManager->destroy_image(v);
}
for (auto &sampler: samplers)
{
vkDestroySampler(dv, sampler, nullptr);
}
auto materialBuffer = materialDataBuffer;
auto samplersToDestroy = samplers;
descriptorPool.destroy_pools(dv);
creator->_resourceManager->destroy_buffer(materialBuffer);
}