From d50a5d0f404d76e40ff185bdc65eadd32976311b Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 12 Apr 2020 18:37:25 +0200 Subject: [PATCH 01/20] Replace ASSIMP with glTF Initial version of mesh loading and rendering example withouth ASSIMP (mainly due to Android build woes) --- examples/mesh/mesh.cpp | 672 ++++++++++++++++++++++++----------------- 1 file changed, 393 insertions(+), 279 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 8a64e47e..1cf7aa80 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -1,11 +1,20 @@ /* * Vulkan Example - Model loading and rendering * -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2016-2020 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ +/* + * Shows how to load and display a simple mesh from a glTF file + * Note that this isn't a complete glTF loader and only basic functions are shown here + * This means only linear nodes (no parent<->child tree), no animations, no skins, etc. + * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 + * + * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/ + */ + #include #include #include @@ -18,16 +27,15 @@ #include #include -#include -#include -#include -#include +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define TINYGLTF_NO_STB_IMAGE_WRITE +#include "tiny_gltf.h" #include #include "vulkanexamplebase.h" #include "VulkanTexture.hpp" -#define VERTEX_BUFFER_BIND_ID 0 #define ENABLE_VALIDATION false class VulkanExample : public VulkanExampleBase @@ -39,12 +47,6 @@ public: vks::Texture2D colorMap; } textures; - struct { - VkPipelineVertexInputStateCreateInfo inputState; - std::vector bindingDescriptions; - std::vector attributeDescriptions; - } vertices; - // Vertex layout used in this example // This must fit input locations of the vertex shader used to render the model struct Vertex { @@ -54,9 +56,33 @@ public: glm::vec3 color; }; + struct ModelNode; + + // Represents a single mesh-based node in the glTF scene graph + // This is simplified as much as possible to make this sample easy to understand + struct ModelNode { + ModelNode* parent; + uint32_t firstIndex; + uint32_t indexCount; + glm::mat4 matrix; + std::vector children; + }; + + // Represents a glTF material used to access e.g. the texture to choose for a mesh + // Only includes the most basic properties required for this sample + struct ModelMaterial { + glm::vec4 baseColorFactor = glm::vec4(1.0f); + uint32_t baseColorTextureIndex; + }; + // Contains all Vulkan resources required to represent vertex and index buffers for a model // This is for demonstration and learning purposes, the other examples use a model loader class for easy access struct Model { + std::vector images; + // Textures in glTF are indices used by material to select an image (and optionally samplers) + std::vector textures; + std::vector materials; + std::vector nodes; struct { VkBuffer buffer; VkDeviceMemory memory; @@ -157,16 +183,16 @@ public: VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid); + drawglTFModel(drawCmdBuffers[i]); + + /* VkDeviceSize offsets[1] = { 0 }; // Bind mesh vertex buffer vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &model.vertices.buffer, offsets); @@ -174,240 +200,357 @@ public: vkCmdBindIndexBuffer(drawCmdBuffers[i], model.indices.buffer, 0, VK_INDEX_TYPE_UINT32); // Render mesh vertex buffer using its indices vkCmdDrawIndexed(drawCmdBuffers[i], model.indices.count, 1, 0, 0, 0); + */ drawUI(drawCmdBuffers[i]); - vkCmdEndRenderPass(drawCmdBuffers[i]); - VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } - // Load a model from file using the ASSIMP model loader and generate all resources required to render the model - void loadModel(std::string filename) + void drawglTFNode(VkCommandBuffer commandBuffer, ModelNode node) { - // Load the model from file using ASSIMP + if (node.indexCount > 0) { + vkCmdDrawIndexed(commandBuffer, node.indexCount, 1, node.firstIndex, 0, 0); + } + for (auto& child : node.children) { + drawglTFNode(commandBuffer, child); + } + } - const aiScene* scene; - Assimp::Importer Importer; + void drawglTFModel(VkCommandBuffer commandBuffer) + { + // All vertices and indices are stored in single buffers, so we only need to bind once + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &model.vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, model.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + for (auto& node : model.nodes) { + drawglTFNode(commandBuffer, node); + } + } + + /* + Load images from the glTF file + Textures 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 + */ + void loadglTFImages(tinygltf::Model& glTFModel) + { + model.images.resize(glTFModel.images.size()); + for (size_t i = 0; i < glTFModel.images.size(); i++) { + tinygltf::Image& glTFImage = glTFModel.images[i]; + // Get the image data from the glTF loader + 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; + 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) { + for (int32_t j = 0; j < 3; ++j) { + rgba[j] = rgb[j]; + } + rgba += 4; + rgb += 3; + } + deleteBuffer = true; + } + else { + buffer = &glTFImage.image[0]; + bufferSize = glTFImage.image.size(); + } + model.images[i].fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, queue); + } + } - // Flags for loading the mesh - static const int assimpFlags = aiProcess_FlipWindingOrder | aiProcess_Triangulate | aiProcess_PreTransformVertices; + /* + Load texture information + These nodes store the index of the image used by a material that sources this texture + */ + void loadglTFTextures(tinygltf::Model& glTFModel) + { + model.textures.resize(glTFModel.textures.size()); + for (size_t i = 0; i < glTFModel.textures.size(); i++) { + model.textures[i] = glTFModel.textures[i].source; + } + } + + /* + Load Materials from the glTF file + Materials contain basic properties like colors and references to the textures used by that material + We only read the most basic properties required for our sample + */ + void loadglTFMaterials(const tinygltf::Model& glTFModel) + { + model.materials.resize(glTFModel.materials.size()); + for (size_t i = 0; i < glTFModel.materials.size(); i++) { + tinygltf::Material glTFMaterial = glTFModel.materials[i]; + // Get the base color factor + if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { + model.materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); + } + // Get base color texture index + if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { + model.materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); + } + } + } + + // Load a single glTF node + // glTF scenes are made up of nodes that contain mesh data + // This is the most basic way of loading a glTF node that ignores parent->child relations and nested matrices + void loadglTFNode(ModelNode* parent, const tinygltf::Node& glTFNode, const tinygltf::Model& glTFModel, std::vector& indexBuffer, std::vector& vertexBuffer) + { + ModelNode node{}; + node.matrix = glm::mat4(1.0f); + + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + if (glTFNode.translation.size() == 3) { + node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(glTFNode.translation.data()))); + } + if (glTFNode.rotation.size() == 4) { + glm::quat q = glm::make_quat(glTFNode.rotation.data()); + node.matrix *= glm::mat4(q); + } + if (glTFNode.scale.size() == 3) { + node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(glTFNode.translation.data()))); + } + if (glTFNode.matrix.size() == 16) { + node.matrix = glm::make_mat4x4(glTFNode.matrix.data()); + }; + + // Load node's children + if (glTFNode.children.size() > 0) { + for (size_t i = 0; i < glTFNode.children.size(); i++) { + loadglTFNode(&node, glTFModel.nodes[glTFNode.children[i]], glTFModel, indexBuffer, vertexBuffer); + } + } + + // If the node contains mesh data, we load vertices and indices from the the buffers + // In glTF this is done via accessors and buffer views + if (glTFNode.mesh > -1) { + const tinygltf::Mesh mesh = glTFModel.meshes[glTFNode.mesh]; + uint32_t indexStart = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& primitive = mesh.primitives[i]; + // Vertices + { + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + size_t vertexCount = 0; + + // Get buffer data for vertex normals + if (primitive.attributes.find("POSITION") != primitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; + positionBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + vertexCount = accessor.count; + } + // Get buffer data for vertex normals + if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("NORMAL")->second]; + const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; + normalsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); + vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + vertexBuffer.push_back(vert); + } + } + // Indices + { + const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.indices]; + const tinygltf::BufferView& bufferView = glTFModel.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = glTFModel.buffers[bufferView.buffer]; + + indexCount += static_cast(accessor.count); + + switch (accessor.componentType) { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { + uint32_t* buf = new uint32_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + uint16_t* buf = new uint16_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { + uint8_t* buf = new uint8_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + default: + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; + return; + } + } + } + node.firstIndex = indexStart; + node.indexCount = indexCount; + } + + if (parent) { + parent->children.push_back(node); + } + else { + model.nodes.push_back(node); + } + } + + // @todo + void loadglTF(std::string filename) + { + tinygltf::Model gltfModel; + tinygltf::TinyGLTF gltfContext; + std::string error, warning; + + this->device = device; #if defined(__ANDROID__) - // Meshes are stored inside the apk on Android (compressed) - // So they need to be loaded via the asset manager - + // On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); assert(asset); size_t size = AAsset_getLength(asset); - assert(size > 0); - - void *meshData = malloc(size); - AAsset_read(asset, meshData, size); + char* fileData = new char[size]; + AAsset_read(asset, fileData, size); AAsset_close(asset); - - scene = Importer.ReadFileFromMemory(meshData, size, assimpFlags); - - free(meshData); + std::string baseDir; + bool fileLoaded = gltfContext.LoadASCIIFromString(&gltfModel, &error, &warning, fileData, size, baseDir); + free(fileData); #else - scene = Importer.ReadFile(filename.c_str(), assimpFlags); + bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename); #endif - - // Generate vertex buffer from ASSIMP scene data - float scale = 1.0f; + std::vector indexBuffer; std::vector vertexBuffer; - // Iterate through all meshes in the file and extract the vertex components - for (uint32_t m = 0; m < scene->mNumMeshes; m++) - { - for (uint32_t v = 0; v < scene->mMeshes[m]->mNumVertices; v++) - { - Vertex vertex; - - // Use glm make_* functions to convert ASSIMP vectors to glm vectors - vertex.pos = glm::make_vec3(&scene->mMeshes[m]->mVertices[v].x) * scale; - vertex.normal = glm::make_vec3(&scene->mMeshes[m]->mNormals[v].x); - // Texture coordinates and colors may have multiple channels, we only use the first [0] one - vertex.uv = glm::make_vec2(&scene->mMeshes[m]->mTextureCoords[0][v].x); - // Mesh may not have vertex colors - vertex.color = (scene->mMeshes[m]->HasVertexColors(0)) ? glm::make_vec3(&scene->mMeshes[m]->mColors[0][v].r) : glm::vec3(1.0f); - - // Vulkan uses a right-handed NDC (contrary to OpenGL), so simply flip Y-Axis - vertex.pos.y *= -1.0f; - - vertexBuffer.push_back(vertex); + if (fileLoaded) { + loadglTFImages(gltfModel); + loadglTFMaterials(gltfModel); + loadglTFTextures(gltfModel); + const tinygltf::Scene& scene = gltfModel.scenes[0]; + for (size_t i = 0; i < scene.nodes.size(); i++) { + const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]]; + loadglTFNode(nullptr, node, gltfModel, indexBuffer, vertexBuffer); } } + else { + // TODO: throw + std::cerr << "Could not load gltf file: " << error << std::endl; + return; + } + size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); - - // Generate index buffer from ASSIMP scene data - std::vector indexBuffer; - for (uint32_t m = 0; m < scene->mNumMeshes; m++) - { - uint32_t indexBase = static_cast(indexBuffer.size()); - for (uint32_t f = 0; f < scene->mMeshes[m]->mNumFaces; f++) - { - // We assume that all faces are triangulated - for (uint32_t i = 0; i < 3; i++) - { - indexBuffer.push_back(scene->mMeshes[m]->mFaces[f].mIndices[i] + indexBase); - } - } - } size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); model.indices.count = static_cast(indexBuffer.size()); - // Static mesh should always be device local + //assert((vertexBufferSize > 0) && (indexBufferSize > 0)); - bool useStaging = true; + struct StagingBuffer { + VkBuffer buffer; + VkDeviceMemory memory; + } vertexStaging, indexStaging; - if (useStaging) - { - struct { - VkBuffer buffer; - VkDeviceMemory memory; - } vertexStaging, indexStaging; + // Create staging buffers + // Vertex data + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + vertexBufferSize, + &vertexStaging.buffer, + &vertexStaging.memory, + vertexBuffer.data())); + // Index data + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + indexBufferSize, + &indexStaging.buffer, + &indexStaging.memory, + indexBuffer.data())); - // Create staging buffers - // Vertex data - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - vertexBufferSize, - &vertexStaging.buffer, - &vertexStaging.memory, - vertexBuffer.data())); - // Index data - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - indexBufferSize, - &indexStaging.buffer, - &indexStaging.memory, - indexBuffer.data())); + // Create device local buffers + // Vertex buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + vertexBufferSize, + &model.vertices.buffer, + &model.vertices.memory)); + // Index buffer + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + indexBufferSize, + &model.indices.buffer, + &model.indices.memory)); - // Create device local buffers - // Vertex buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - vertexBufferSize, - &model.vertices.buffer, - &model.vertices.memory)); - // Index buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - indexBufferSize, - &model.indices.buffer, - &model.indices.memory)); + // Copy data from staging buffers (host) do device local buffer (gpu) + VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - // Copy from staging buffers - VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBufferCopy copyRegion = {}; - VkBufferCopy copyRegion = {}; + copyRegion.size = vertexBufferSize; + vkCmdCopyBuffer( + copyCmd, + vertexStaging.buffer, + model.vertices.buffer, + 1, + ©Region); - copyRegion.size = vertexBufferSize; - vkCmdCopyBuffer( - copyCmd, - vertexStaging.buffer, - model.vertices.buffer, - 1, - ©Region); + copyRegion.size = indexBufferSize; + vkCmdCopyBuffer( + copyCmd, + indexStaging.buffer, + model.indices.buffer, + 1, + ©Region); - copyRegion.size = indexBufferSize; - vkCmdCopyBuffer( - copyCmd, - indexStaging.buffer, - model.indices.buffer, - 1, - ©Region); + VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); - VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); - - vkDestroyBuffer(device, vertexStaging.buffer, nullptr); - vkFreeMemory(device, vertexStaging.memory, nullptr); - vkDestroyBuffer(device, indexStaging.buffer, nullptr); - vkFreeMemory(device, indexStaging.memory, nullptr); - } - else - { - // Vertex buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, - vertexBufferSize, - &model.vertices.buffer, - &model.vertices.memory, - vertexBuffer.data())); - // Index buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_INDEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, - indexBufferSize, - &model.indices.buffer, - &model.indices.memory, - indexBuffer.data())); - } + vkDestroyBuffer(device, vertexStaging.buffer, nullptr); + vkFreeMemory(device, vertexStaging.memory, nullptr); + vkDestroyBuffer(device, indexStaging.buffer, nullptr); + vkFreeMemory(device, indexStaging.memory, nullptr); } void loadAssets() { - loadModel(getAssetPath() + "models/voyager/voyager.dae"); + loadglTF(getAssetPath() + "models/voyager/voyager.gltf"); textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_rgba_unorm.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); } - void setupVertexDescriptions() - { - // Binding description - vertices.bindingDescriptions.resize(1); - vertices.bindingDescriptions[0] = - vks::initializers::vertexInputBindingDescription( - VERTEX_BUFFER_BIND_ID, - sizeof(Vertex), - VK_VERTEX_INPUT_RATE_VERTEX); - - // Attribute descriptions - // Describes memory layout and shader positions - vertices.attributeDescriptions.resize(4); - // Location 0 : Position - vertices.attributeDescriptions[0] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 0, - VK_FORMAT_R32G32B32_SFLOAT, - offsetof(Vertex, pos)); - // Location 1 : Normal - vertices.attributeDescriptions[1] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 1, - VK_FORMAT_R32G32B32_SFLOAT, - offsetof(Vertex, normal)); - // Location 2 : Texture coordinates - vertices.attributeDescriptions[2] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 2, - VK_FORMAT_R32G32_SFLOAT, - offsetof(Vertex, uv)); - // Location 3 : Color - vertices.attributeDescriptions[3] = - vks::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 3, - VK_FORMAT_R32G32B32_SFLOAT, - offsetof(Vertex, color)); - - vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - vertices.inputState.vertexBindingDescriptionCount = static_cast(vertices.bindingDescriptions.size()); - vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); - vertices.inputState.vertexAttributeDescriptionCount = static_cast(vertices.attributeDescriptions.size()); - vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); - } - void setupDescriptorPool() { // Example uses one ubo and one combined image sampler @@ -473,84 +616,56 @@ public: void preparePipelines() { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vks::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_BACK_BIT, - VK_FRONT_FACE_CLOCKWISE, - 0); - - VkPipelineColorBlendAttachmentState blendAttachmentState = - vks::initializers::pipelineColorBlendAttachmentState( - 0xf, - VK_FALSE); - - VkPipelineColorBlendStateCreateInfo colorBlendState = - vks::initializers::pipelineColorBlendStateCreateInfo( - 1, - &blendAttachmentState); - - VkPipelineDepthStencilStateCreateInfo depthStencilState = - vks::initializers::pipelineDepthStencilStateCreateInfo( - VK_TRUE, - VK_TRUE, - VK_COMPARE_OP_LESS_OR_EQUAL); - - VkPipelineViewportStateCreateInfo viewportState = - vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); - - VkPipelineMultisampleStateCreateInfo multisampleState = - vks::initializers::pipelineMultisampleStateCreateInfo( - VK_SAMPLE_COUNT_1_BIT, - 0); - - std::vector dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR + VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI); + VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); + const std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); + // Vertex input bindings and attributes + const std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo( - dynamicStateEnables.data(), - static_cast(dynamicStateEnables.size()), - 0); + const std::vector vertexInputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), // Location 0: Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)), // Location 1: Normal + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, uv)), // Location 2: Texture coordinates + vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)), // Location 3: Color + }; + VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputStateCI.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); + vertexInputStateCI.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + const std::array shaderStages = { + loadShader(getAssetPath() + "shaders/mesh/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getAssetPath() + "shaders/mesh/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) + }; + + VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); + pipelineCI.pVertexInputState = &vertexInputStateCI; + pipelineCI.pInputAssemblyState = &inputAssemblyStateCI; + pipelineCI.pRasterizationState = &rasterizationStateCI; + pipelineCI.pColorBlendState = &colorBlendStateCI; + pipelineCI.pMultisampleState = &multisampleStateCI; + pipelineCI.pViewportState = &viewportStateCI; + pipelineCI.pDepthStencilState = &depthStencilStateCI; + pipelineCI.pDynamicState = &dynamicStateCI; + pipelineCI.stageCount = static_cast(shaderStages.size()); + pipelineCI.pStages = shaderStages.data(); // Solid rendering pipeline - // Load shaders - std::array shaderStages; - - shaderStages[0] = loadShader(getAssetPath() + "shaders/mesh/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader(getAssetPath() + "shaders/mesh/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo( - pipelineLayout, - renderPass, - 0); - - pipelineCreateInfo.pVertexInputState = &vertices.inputState; - pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; - pipelineCreateInfo.pRasterizationState = &rasterizationState; - pipelineCreateInfo.pColorBlendState = &colorBlendState; - pipelineCreateInfo.pMultisampleState = &multisampleState; - pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.pDynamicState = &dynamicState; - pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); - pipelineCreateInfo.pStages = shaderStages.data(); - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid)); + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid)); // Wire frame rendering pipeline if (deviceFeatures.fillModeNonSolid) { - rasterizationState.polygonMode = VK_POLYGON_MODE_LINE; - rasterizationState.lineWidth = 1.0f; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.wireframe)); + rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE; + rasterizationStateCI.lineWidth = 1.0f; + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe)); } } @@ -601,7 +716,6 @@ public: { VulkanExampleBase::prepare(); loadAssets(); - setupVertexDescriptions(); prepareUniformBuffers(); setupDescriptorSetLayout(); preparePipelines(); From 9f7d13d5e0af585190d9611f7146c9b20a299cc9 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 12 Apr 2020 21:24:33 +0200 Subject: [PATCH 02/20] Mesh loading and rendering now properly displaying glTF mesh Added textures, modified structure to be more in line with glTF layout Split matrices and material descriptor set --- examples/mesh/mesh.cpp | 227 +++++++++++++++++++++-------------------- 1 file changed, 117 insertions(+), 110 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 1cf7aa80..e5bcbba3 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -38,17 +38,26 @@ #define ENABLE_VALIDATION false +// Contains everything required to render a glTF model in Vulkan +// This class is very simplified but retains the basic glTF structure +class VulkanglTFModel +{ + // The vertex layout for the samples' model + struct Vertex { + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv; + glm::vec3 color; + }; + + +}; + class VulkanExample : public VulkanExampleBase { public: bool wireframe = false; - struct { - vks::Texture2D colorMap; - } textures; - - // Vertex layout used in this example - // This must fit input locations of the vertex shader used to render the model struct Vertex { glm::vec3 pos; glm::vec3 normal; @@ -58,27 +67,42 @@ public: struct ModelNode; - // Represents a single mesh-based node in the glTF scene graph - // This is simplified as much as possible to make this sample easy to understand - struct ModelNode { - ModelNode* parent; + // A primitive contains the data for a single draw call + struct Primitive { uint32_t firstIndex; uint32_t indexCount; - glm::mat4 matrix; + int32_t materialIndex; + }; + + // Contains the node's geometry and can be made up of an arbitrary number of primitives + struct Mesh { + std::vector primitives; + }; + + // A node represents an object in the glTF scene graph + struct ModelNode { + ModelNode* parent; std::vector children; + Mesh mesh; + glm::mat4 matrix; }; // Represents a glTF material used to access e.g. the texture to choose for a mesh - // Only includes the most basic properties required for this sample struct ModelMaterial { glm::vec4 baseColorFactor = glm::vec4(1.0f); uint32_t baseColorTextureIndex; }; + // @todo + struct ModelImage { + vks::Texture2D texture; + VkDescriptorSet descriptorSet; + }; + // Contains all Vulkan resources required to represent vertex and index buffers for a model // This is for demonstration and learning purposes, the other examples use a model loader class for easy access struct Model { - std::vector images; + std::vector images; // Textures in glTF are indices used by material to select an image (and optionally samplers) std::vector textures; std::vector materials; @@ -119,7 +143,11 @@ public: VkPipelineLayout pipelineLayout; VkDescriptorSet descriptorSet; - VkDescriptorSetLayout descriptorSetLayout; + + struct DescriptorSetLayouts { + VkDescriptorSetLayout matrices; + VkDescriptorSetLayout textures; + } descriptorSetLayouts; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { @@ -128,8 +156,9 @@ public: rotationSpeed = 0.5f; rotation = { -0.5f, -112.75f, 0.0f }; cameraPos = { 0.1f, 1.1f, 0.0f }; - title = "Model rendering"; + title = "glTF model rendering"; settings.overlay = true; + //@todo: Use camera } ~VulkanExample() @@ -142,11 +171,11 @@ public: } vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); model.destroy(device); - textures.colorMap.destroy(); uniformBuffers.scene.destroy(); } @@ -175,33 +204,20 @@ public: renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; + const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) { - // Set target frame buffer renderPassBeginInfo.framebuffer = frameBuffers[i]; - VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); - vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); + // Bind scene matrices descriptor to set 0 + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid); - drawglTFModel(drawCmdBuffers[i]); - - /* - VkDeviceSize offsets[1] = { 0 }; - // Bind mesh vertex buffer - vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &model.vertices.buffer, offsets); - // Bind mesh index buffer - vkCmdBindIndexBuffer(drawCmdBuffers[i], model.indices.buffer, 0, VK_INDEX_TYPE_UINT32); - // Render mesh vertex buffer using its indices - vkCmdDrawIndexed(drawCmdBuffers[i], model.indices.count, 1, 0, 0, 0); - */ - drawUI(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); @@ -210,8 +226,15 @@ public: void drawglTFNode(VkCommandBuffer commandBuffer, ModelNode node) { - if (node.indexCount > 0) { - vkCmdDrawIndexed(commandBuffer, node.indexCount, 1, node.firstIndex, 0, 0); + if (node.mesh.primitives.size() > 0) { + for (Primitive& primitive : node.mesh.primitives) { + if (primitive.indexCount > 0) { + // @todo: link mat to node + uint32_t texture = model.textures[model.materials[primitive.materialIndex].baseColorTextureIndex]; + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &model.images[texture].descriptorSet, 0, nullptr); + vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); + } + } } for (auto& child : node.children) { drawglTFNode(commandBuffer, child); @@ -262,7 +285,8 @@ public: buffer = &glTFImage.image[0]; bufferSize = glTFImage.image.size(); } - model.images[i].fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, queue); + // Load texture from image buffer + model.images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, queue); } } @@ -334,12 +358,12 @@ public: // In glTF this is done via accessors and buffer views if (glTFNode.mesh > -1) { const tinygltf::Mesh mesh = glTFModel.meshes[glTFNode.mesh]; - uint32_t indexStart = static_cast(indexBuffer.size()); - uint32_t vertexStart = static_cast(vertexBuffer.size()); - uint32_t indexCount = 0; // Iterate through all primitives of this node's mesh for (size_t i = 0; i < mesh.primitives.size(); i++) { - const tinygltf::Primitive& primitive = mesh.primitives[i]; + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; + uint32_t firstIndex = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; // Vertices { const float* positionBuffer = nullptr; @@ -348,22 +372,22 @@ public: size_t vertexCount = 0; // Get buffer data for vertex normals - if (primitive.attributes.find("POSITION") != primitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("POSITION")->second]; + if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("POSITION")->second]; const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; positionBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); vertexCount = accessor.count; } // Get buffer data for vertex normals - if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("NORMAL")->second]; + if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; normalsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); } // Get buffer data for vertex texture coordinates // glTF supports multiple sets, we only load the first one - if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.attributes.find("TEXCOORD_0")->second]; + if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; texCoordsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); } @@ -374,17 +398,21 @@ public: vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + vert.color = glm::vec3(1.0f); + // Flip Y-Axis + vert.pos.y *= -1.0f; vertexBuffer.push_back(vert); } } // Indices { - const tinygltf::Accessor& accessor = glTFModel.accessors[primitive.indices]; + const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.indices]; const tinygltf::BufferView& bufferView = glTFModel.bufferViews[accessor.bufferView]; const tinygltf::Buffer& buffer = glTFModel.buffers[bufferView.buffer]; indexCount += static_cast(accessor.count); + // glTF supports different component types of indices switch (accessor.componentType) { case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { uint32_t* buf = new uint32_t[accessor.count]; @@ -415,9 +443,12 @@ public: return; } } + Primitive primitive{}; + primitive.firstIndex = firstIndex; + primitive.indexCount = indexCount; + primitive.materialIndex = glTFPrimitive.material; + node.mesh.primitives.push_back(primitive); } - node.firstIndex = indexStart; - node.indexCount = indexCount; } if (parent) { @@ -548,76 +579,54 @@ public: void loadAssets() { loadglTF(getAssetPath() + "models/voyager/voyager.gltf"); - textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_rgba_unorm.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); } - void setupDescriptorPool() + void setupDescriptors() { - // Example uses one ubo and one combined image sampler - std::vector poolSizes = - { + /* + This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures) + */ + + std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1), + // One combined image sampler per model image/texture + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(model.images.size())), }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - static_cast(poolSizes.size()), - poolSizes.data(), - 1); - + // One set for matrices and one per model image/texture + const uint32_t maxSetCount = static_cast(model.images.size()) + 1; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - void setupDescriptorSetLayout() - { - std::vector setLayoutBindings = - { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_VERTEX_BIT, - 0), - // Binding 1 : Fragment shader combined sampler - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 1), - }; + // Descriptor set layout for passing matrices + VkDescriptorSetLayoutBinding setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0); + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(&setLayoutBinding, 1); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices)); + // Descriptor set layout for passing material textures + setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures)); + // Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material) + std::array setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; + VkPipelineLayoutCreateInfo pipelineLayoutCI= vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - static_cast(setLayoutBindings.size())); - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); - - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = - vks::initializers::pipelineLayoutCreateInfo( - &descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - } - - void setupDescriptorSet() - { - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + // Descriptor set for scene matrices + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - - std::vector writeDescriptorSets = { - // Binding 0 : Vertex shader uniform buffer - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor), - // Binding 1 : Color map - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.colorMap.descriptor) - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + // Descriptor sets for materials + for (auto& image : model.images) { + const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet)); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + } } void preparePipelines() { VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); - VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0); + VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI); VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL); @@ -717,10 +726,8 @@ public: VulkanExampleBase::prepare(); loadAssets(); prepareUniformBuffers(); - setupDescriptorSetLayout(); + setupDescriptors(); preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); buildCommandBuffers(); prepared = true; } From bb9374b2ecc0bee8194726020a08ceb49f297855 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 12 Apr 2020 21:58:45 +0200 Subject: [PATCH 03/20] Split shader UBOs --- data/shaders/mesh/mesh.frag | 2 +- data/shaders/mesh/mesh.frag.spv | Bin 2008 -> 2008 bytes data/shaders/mesh/mesh.vert | 2 +- data/shaders/mesh/mesh.vert.spv | Bin 2748 -> 2748 bytes 4 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/shaders/mesh/mesh.frag b/data/shaders/mesh/mesh.frag index 746f45a7..17d4a95b 100644 --- a/data/shaders/mesh/mesh.frag +++ b/data/shaders/mesh/mesh.frag @@ -1,6 +1,6 @@ #version 450 -layout (binding = 1) uniform sampler2D samplerColorMap; +layout (set = 1, binding = 0) uniform sampler2D samplerColorMap; layout (location = 0) in vec3 inNormal; layout (location = 1) in vec3 inColor; diff --git a/data/shaders/mesh/mesh.frag.spv b/data/shaders/mesh/mesh.frag.spv index bae230cce23c591dc8c77e89df57013b9a41a6a0..3c870fe52862fce99f79607ec09940cf8e376fee 100644 GIT binary patch delta 41 tcmcb?e}kWsnMs+Qfq{{MV>(%?laL*#Lm{1#bWV delta 41 scmcb?e}kWsnMs+Qfq{{MeIsW(qaXtixHGUY@B(Q?AZFaWkkOnC0D#H`ZU6uP diff --git a/data/shaders/mesh/mesh.vert b/data/shaders/mesh/mesh.vert index d9139507..22b6aeb9 100644 --- a/data/shaders/mesh/mesh.vert +++ b/data/shaders/mesh/mesh.vert @@ -5,7 +5,7 @@ layout (location = 1) in vec3 inNormal; layout (location = 2) in vec2 inUV; layout (location = 3) in vec3 inColor; -layout (binding = 0) uniform UBO +layout (set = 0, binding = 0) uniform UBO { mat4 projection; mat4 model; diff --git a/data/shaders/mesh/mesh.vert.spv b/data/shaders/mesh/mesh.vert.spv index 82758e686cfd5a35ead56c59b5827cba399c672f..2d0a79fc927d5ef5a3553d76fa5e729080693ada 100644 GIT binary patch delta 18 ZcmdlZx<{0gnMs+Qfq{{MV Date: Sun, 12 Apr 2020 21:59:26 +0200 Subject: [PATCH 04/20] Move loading into dedicated VulkanglTF class --- examples/mesh/mesh.cpp | 624 +++++++++++++++++++++-------------------- 1 file changed, 314 insertions(+), 310 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index e5bcbba3..713ec7a3 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -11,6 +11,8 @@ * Note that this isn't a complete glTF loader and only basic functions are shown here * This means only linear nodes (no parent<->child tree), no animations, no skins, etc. * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 + * + * Other samples will load models using a dedicated model loader with more features (see base/VulkanglTFModel.hpp) * * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/ */ @@ -42,6 +44,11 @@ // This class is very simplified but retains the basic glTF structure class VulkanglTFModel { +public: + // The class requires some Vulkan objects so it can create it's own resources + vks::VulkanDevice* vulkanDevice; + VkQueue copyQueue; + // The vertex layout for the samples' model struct Vertex { glm::vec3 pos; @@ -50,6 +57,270 @@ class VulkanglTFModel glm::vec3 color; }; + // Single vertex buffer for all primitives + struct { + VkBuffer buffer; + VkDeviceMemory memory; + } vertices; + + // Single index buffer for all primitives + struct { + int count; + VkBuffer buffer; + VkDeviceMemory memory; + } indices; + + // 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; + + // A primitive contains the data for a single draw call + struct Primitive { + uint32_t firstIndex; + uint32_t indexCount; + int32_t materialIndex; + }; + + // Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives + struct Mesh { + std::vector primitives; + }; + + // A node represents an object in the glTF scene graph + struct Node { + Node* parent; + std::vector children; + Mesh mesh; + glm::mat4 matrix; + }; + + // A glTF material stores information in e.g. the exture that is attached to it and colors + struct Material { + glm::vec4 baseColorFactor = glm::vec4(1.0f); + uint32_t baseColorTextureIndex; + }; + + // Contains the texture for a single glTF image + // Images may be reused by texture objects and are as such separted + 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; + }; + + /* + Model data + */ + std::vector images; + std::vector textures; + std::vector materials; + std::vector nodes; + + /* + glTF loading functions + + The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure + */ + + void loadImages(tinygltf::Model& input) + { + // 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 + 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; + 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) { + for (int32_t j = 0; j < 3; ++j) { + rgba[j] = rgb[j]; + } + rgba += 4; + rgb += 3; + } + deleteBuffer = true; + } + else { + buffer = &glTFImage.image[0]; + bufferSize = glTFImage.image.size(); + } + // Load texture from image buffer + images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue); + } + } + + void loadTextures(tinygltf::Model& input) + { + textures.resize(input.textures.size()); + for (size_t i = 0; i < input.textures.size(); i++) { + textures[i].imageIndex = input.textures[i].source; + } + } + + void loadMaterials(tinygltf::Model& input) + { + materials.resize(input.materials.size()); + for (size_t i = 0; i < input.materials.size(); i++) { + // We only read the most basic properties required for our sample + tinygltf::Material glTFMaterial = input.materials[i]; + // Get the base color factor + if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { + materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); + } + // Get base color texture index + if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { + materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); + } + } + } + + void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, std::vector& indexBuffer, std::vector& vertexBuffer) + { + VulkanglTFModel::Node node{}; + node.matrix = glm::mat4(1.0f); + + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + if (inputNode.translation.size() == 3) { + node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); + } + if (inputNode.rotation.size() == 4) { + glm::quat q = glm::make_quat(inputNode.rotation.data()); + node.matrix *= glm::mat4(q); + } + if (inputNode.scale.size() == 3) { + node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); + } + if (inputNode.matrix.size() == 16) { + node.matrix = glm::make_mat4x4(inputNode.matrix.data()); + }; + + // Load node's children + if (inputNode.children.size() > 0) { + for (size_t i = 0; i < inputNode.children.size(); i++) { + loadNode(input.nodes[inputNode.children[i]], input , &node, indexBuffer, vertexBuffer); + } + } + + // If the node contains mesh data, we load vertices and indices from the the buffers + // In glTF this is done via accessors and buffer views + if (inputNode.mesh > -1) { + const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; + uint32_t firstIndex = static_cast(indexBuffer.size()); + uint32_t vertexStart = static_cast(vertexBuffer.size()); + uint32_t indexCount = 0; + // Vertices + { + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + size_t vertexCount = 0; + + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + positionBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + vertexCount = accessor.count; + } + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + normalsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { + Vertex vert{}; + vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); + vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); + vert.color = glm::vec3(1.0f); + // Flip Y-Axis + vert.pos.y *= -1.0f; + vertexBuffer.push_back(vert); + } + } + // Indices + { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices]; + const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer]; + + indexCount += static_cast(accessor.count); + + // glTF supports different component types of indices + switch (accessor.componentType) { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { + uint32_t* buf = new uint32_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + uint16_t* buf = new uint16_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { + uint8_t* buf = new uint8_t[accessor.count]; + memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); + for (size_t index = 0; index < accessor.count; index++) { + indexBuffer.push_back(buf[index] + vertexStart); + } + break; + } + default: + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; + return; + } + } + Primitive primitive{}; + primitive.firstIndex = firstIndex; + primitive.indexCount = indexCount; + primitive.materialIndex = glTFPrimitive.material; + node.mesh.primitives.push_back(primitive); + } + } + + if (parent) { + parent->children.push_back(node); + } + else { + nodes.push_back(node); + } + } + }; @@ -58,73 +329,7 @@ class VulkanExample : public VulkanExampleBase public: bool wireframe = false; - struct Vertex { - glm::vec3 pos; - glm::vec3 normal; - glm::vec2 uv; - glm::vec3 color; - }; - - struct ModelNode; - - // A primitive contains the data for a single draw call - struct Primitive { - uint32_t firstIndex; - uint32_t indexCount; - int32_t materialIndex; - }; - - // Contains the node's geometry and can be made up of an arbitrary number of primitives - struct Mesh { - std::vector primitives; - }; - - // A node represents an object in the glTF scene graph - struct ModelNode { - ModelNode* parent; - std::vector children; - Mesh mesh; - glm::mat4 matrix; - }; - - // Represents a glTF material used to access e.g. the texture to choose for a mesh - struct ModelMaterial { - glm::vec4 baseColorFactor = glm::vec4(1.0f); - uint32_t baseColorTextureIndex; - }; - - // @todo - struct ModelImage { - vks::Texture2D texture; - VkDescriptorSet descriptorSet; - }; - - // Contains all Vulkan resources required to represent vertex and index buffers for a model - // This is for demonstration and learning purposes, the other examples use a model loader class for easy access - struct Model { - std::vector images; - // Textures in glTF are indices used by material to select an image (and optionally samplers) - std::vector textures; - std::vector materials; - std::vector nodes; - struct { - VkBuffer buffer; - VkDeviceMemory memory; - } vertices; - struct { - int count; - VkBuffer buffer; - VkDeviceMemory memory; - } indices; - // Destroys all Vulkan resources created for this model - void destroy(VkDevice device) - { - vkDestroyBuffer(device, vertices.buffer, nullptr); - vkFreeMemory(device, vertices.memory, nullptr); - vkDestroyBuffer(device, indices.buffer, nullptr); - vkFreeMemory(device, indices.memory, nullptr); - }; - } model; + VulkanglTFModel glTFModel; struct { vks::Buffer scene; @@ -174,7 +379,8 @@ public: vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); - model.destroy(device); + // @todo + //model.destroy(device); uniformBuffers.scene.destroy(); } @@ -224,14 +430,17 @@ public: } } - void drawglTFNode(VkCommandBuffer commandBuffer, ModelNode node) + /* + glTF rendering functions + */ + void drawglTFNode(VkCommandBuffer commandBuffer, VulkanglTFModel::Node node) { if (node.mesh.primitives.size() > 0) { - for (Primitive& primitive : node.mesh.primitives) { + for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) { if (primitive.indexCount > 0) { // @todo: link mat to node - uint32_t texture = model.textures[model.materials[primitive.materialIndex].baseColorTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &model.images[texture].descriptorSet, 0, nullptr); + VulkanglTFModel::Texture texture = glTFModel.textures[glTFModel.materials[primitive.materialIndex].baseColorTextureIndex]; + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &glTFModel.images[texture.imageIndex].descriptorSet, 0, nullptr); vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); } } @@ -245,224 +454,17 @@ public: { // All vertices and indices are stored in single buffers, so we only need to bind once VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &model.vertices.buffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, model.indices.buffer, 0, VK_INDEX_TYPE_UINT32); - for (auto& node : model.nodes) { + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &glTFModel.vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, glTFModel.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + for (auto& node : glTFModel.nodes) { drawglTFNode(commandBuffer, node); } } - /* - Load images from the glTF file - Textures 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 - */ - void loadglTFImages(tinygltf::Model& glTFModel) - { - model.images.resize(glTFModel.images.size()); - for (size_t i = 0; i < glTFModel.images.size(); i++) { - tinygltf::Image& glTFImage = glTFModel.images[i]; - // Get the image data from the glTF loader - 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; - 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) { - for (int32_t j = 0; j < 3; ++j) { - rgba[j] = rgb[j]; - } - rgba += 4; - rgb += 3; - } - deleteBuffer = true; - } - else { - buffer = &glTFImage.image[0]; - bufferSize = glTFImage.image.size(); - } - // Load texture from image buffer - model.images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, queue); - } - } - - /* - Load texture information - These nodes store the index of the image used by a material that sources this texture - */ - void loadglTFTextures(tinygltf::Model& glTFModel) - { - model.textures.resize(glTFModel.textures.size()); - for (size_t i = 0; i < glTFModel.textures.size(); i++) { - model.textures[i] = glTFModel.textures[i].source; - } - } - - /* - Load Materials from the glTF file - Materials contain basic properties like colors and references to the textures used by that material - We only read the most basic properties required for our sample - */ - void loadglTFMaterials(const tinygltf::Model& glTFModel) - { - model.materials.resize(glTFModel.materials.size()); - for (size_t i = 0; i < glTFModel.materials.size(); i++) { - tinygltf::Material glTFMaterial = glTFModel.materials[i]; - // Get the base color factor - if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) { - model.materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data()); - } - // Get base color texture index - if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) { - model.materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex(); - } - } - } - - // Load a single glTF node - // glTF scenes are made up of nodes that contain mesh data - // This is the most basic way of loading a glTF node that ignores parent->child relations and nested matrices - void loadglTFNode(ModelNode* parent, const tinygltf::Node& glTFNode, const tinygltf::Model& glTFModel, std::vector& indexBuffer, std::vector& vertexBuffer) - { - ModelNode node{}; - node.matrix = glm::mat4(1.0f); - - // Get the local node matrix - // It's either made up from translation, rotation, scale or a 4x4 matrix - if (glTFNode.translation.size() == 3) { - node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(glTFNode.translation.data()))); - } - if (glTFNode.rotation.size() == 4) { - glm::quat q = glm::make_quat(glTFNode.rotation.data()); - node.matrix *= glm::mat4(q); - } - if (glTFNode.scale.size() == 3) { - node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(glTFNode.translation.data()))); - } - if (glTFNode.matrix.size() == 16) { - node.matrix = glm::make_mat4x4(glTFNode.matrix.data()); - }; - - // Load node's children - if (glTFNode.children.size() > 0) { - for (size_t i = 0; i < glTFNode.children.size(); i++) { - loadglTFNode(&node, glTFModel.nodes[glTFNode.children[i]], glTFModel, indexBuffer, vertexBuffer); - } - } - - // If the node contains mesh data, we load vertices and indices from the the buffers - // In glTF this is done via accessors and buffer views - if (glTFNode.mesh > -1) { - const tinygltf::Mesh mesh = glTFModel.meshes[glTFNode.mesh]; - // Iterate through all primitives of this node's mesh - for (size_t i = 0; i < mesh.primitives.size(); i++) { - const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; - uint32_t firstIndex = static_cast(indexBuffer.size()); - uint32_t vertexStart = static_cast(vertexBuffer.size()); - uint32_t indexCount = 0; - // Vertices - { - const float* positionBuffer = nullptr; - const float* normalsBuffer = nullptr; - const float* texCoordsBuffer = nullptr; - size_t vertexCount = 0; - - // Get buffer data for vertex normals - if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("POSITION")->second]; - const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; - positionBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - vertexCount = accessor.count; - } - // Get buffer data for vertex normals - if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; - const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; - normalsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - } - // Get buffer data for vertex texture coordinates - // glTF supports multiple sets, we only load the first one - if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { - const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; - const tinygltf::BufferView& view = glTFModel.bufferViews[accessor.bufferView]; - texCoordsBuffer = reinterpret_cast(&(glTFModel.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - } - - // Append data to model's vertex buffer - for (size_t v = 0; v < vertexCount; v++) { - Vertex vert{}; - vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); - vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); - vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); - vert.color = glm::vec3(1.0f); - // Flip Y-Axis - vert.pos.y *= -1.0f; - vertexBuffer.push_back(vert); - } - } - // Indices - { - const tinygltf::Accessor& accessor = glTFModel.accessors[glTFPrimitive.indices]; - const tinygltf::BufferView& bufferView = glTFModel.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = glTFModel.buffers[bufferView.buffer]; - - indexCount += static_cast(accessor.count); - - // glTF supports different component types of indices - switch (accessor.componentType) { - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - uint32_t* buf = new uint32_t[accessor.count]; - memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t)); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { - uint16_t* buf = new uint16_t[accessor.count]; - memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t)); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { - uint8_t* buf = new uint8_t[accessor.count]; - memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t)); - for (size_t index = 0; index < accessor.count; index++) { - indexBuffer.push_back(buf[index] + vertexStart); - } - break; - } - default: - std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; - return; - } - } - Primitive primitive{}; - primitive.firstIndex = firstIndex; - primitive.indexCount = indexCount; - primitive.materialIndex = glTFPrimitive.material; - node.mesh.primitives.push_back(primitive); - } - } - - if (parent) { - parent->children.push_back(node); - } - else { - model.nodes.push_back(node); - } - } - // @todo void loadglTF(std::string filename) { - tinygltf::Model gltfModel; + tinygltf::Model glTFInput; tinygltf::TinyGLTF gltfContext; std::string error, warning; @@ -478,22 +480,26 @@ public: AAsset_read(asset, fileData, size); AAsset_close(asset); std::string baseDir; - bool fileLoaded = gltfContext.LoadASCIIFromString(&gltfModel, &error, &warning, fileData, size, baseDir); + bool fileLoaded = gltfContext.LoadASCIIFromString(&glTFInput, &error, &warning, fileData, size, baseDir); free(fileData); #else - bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename); + bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename); #endif + + glTFModel.vulkanDevice = vulkanDevice; + glTFModel.copyQueue = queue; + std::vector indexBuffer; - std::vector vertexBuffer; + std::vector vertexBuffer; if (fileLoaded) { - loadglTFImages(gltfModel); - loadglTFMaterials(gltfModel); - loadglTFTextures(gltfModel); - const tinygltf::Scene& scene = gltfModel.scenes[0]; + glTFModel.loadImages(glTFInput); + glTFModel.loadMaterials(glTFInput); + glTFModel.loadTextures(glTFInput); + const tinygltf::Scene& scene = glTFInput.scenes[0]; for (size_t i = 0; i < scene.nodes.size(); i++) { - const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]]; - loadglTFNode(nullptr, node, gltfModel, indexBuffer, vertexBuffer); + const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]]; + glTFModel.loadNode(node, glTFInput, nullptr, indexBuffer, vertexBuffer); } } else { @@ -502,11 +508,9 @@ public: return; } - size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex); + size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFModel::Vertex); size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); - model.indices.count = static_cast(indexBuffer.size()); - - //assert((vertexBufferSize > 0) && (indexBufferSize > 0)); + glTFModel.indices.count = static_cast(indexBuffer.size()); struct StagingBuffer { VkBuffer buffer; @@ -537,15 +541,15 @@ public: VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBufferSize, - &model.vertices.buffer, - &model.vertices.memory)); + &glTFModel.vertices.buffer, + &glTFModel.vertices.memory)); // Index buffer VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBufferSize, - &model.indices.buffer, - &model.indices.memory)); + &glTFModel.indices.buffer, + &glTFModel.indices.memory)); // Copy data from staging buffers (host) do device local buffer (gpu) VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); @@ -556,7 +560,7 @@ public: vkCmdCopyBuffer( copyCmd, vertexStaging.buffer, - model.vertices.buffer, + glTFModel.vertices.buffer, 1, ©Region); @@ -564,7 +568,7 @@ public: vkCmdCopyBuffer( copyCmd, indexStaging.buffer, - model.indices.buffer, + glTFModel.indices.buffer, 1, ©Region); @@ -590,10 +594,10 @@ public: std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), // One combined image sampler per model image/texture - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(model.images.size())), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast(glTFModel.images.size())), }; // One set for matrices and one per model image/texture - const uint32_t maxSetCount = static_cast(model.images.size()) + 1; + const uint32_t maxSetCount = static_cast(glTFModel.images.size()) + 1; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); @@ -615,7 +619,7 @@ public: VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor); vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); // Descriptor sets for materials - for (auto& image : model.images) { + for (auto& image : glTFModel.images) { const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet)); VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor); @@ -636,13 +640,13 @@ public: VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); // Vertex input bindings and attributes const std::vector vertexInputBindings = { - vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX), + vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX), }; const std::vector vertexInputAttributes = { - vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)), // Location 0: Position - vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)), // Location 1: Normal - vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, uv)), // Location 2: Texture coordinates - vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)), // Location 3: Color + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, pos)), // Location 0: Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, normal)),// Location 1: Normal + vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)), // Location 2: Texture coordinates + vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)), // Location 3: Color }; VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputStateCI.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); From 2966d0ee5de6843709710b2c4e36c91e6d64c764 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 12 Apr 2020 22:07:54 +0200 Subject: [PATCH 05/20] Move drawing into dedicated VulkanglTF class Comments, code-cleanup --- examples/mesh/mesh.cpp | 85 ++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 713ec7a3..5226580f 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -41,13 +41,13 @@ #define ENABLE_VALIDATION false // Contains everything required to render a glTF model in Vulkan -// This class is very simplified but retains the basic glTF structure +// This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure class VulkanglTFModel { public: // The class requires some Vulkan objects so it can create it's own resources vks::VulkanDevice* vulkanDevice; - VkQueue copyQueue; + VkQueue copyQueue; // The vertex layout for the samples' model struct Vertex { @@ -321,6 +321,40 @@ public: } } + /* + glTF rendering functions + */ + + // Draw a single node including child nodes (if present) + void drawglTFNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node) + { + if (node.mesh.primitives.size() > 0) { + for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) { + if (primitive.indexCount > 0) { + // Get the texture index for this primitive + VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex]; + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr); + vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); + } + } + } + for (auto& child : node.children) { + drawglTFNode(commandBuffer, pipelineLayout, child); + } + } + + // Draw the glTF scene starting at the top-level-nodes + void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout) + { + // All vertices and indices are stored in single buffers, so we only need to bind once + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); + // Render all nodes at top-level + for (auto& node : nodes) { + drawglTFNode(commandBuffer, pipelineLayout, node); + } + } }; @@ -423,44 +457,13 @@ public: // Bind scene matrices descriptor to set 0 vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid); - drawglTFModel(drawCmdBuffers[i]); + glTFModel.draw(drawCmdBuffers[i], pipelineLayout); drawUI(drawCmdBuffers[i]); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } - /* - glTF rendering functions - */ - void drawglTFNode(VkCommandBuffer commandBuffer, VulkanglTFModel::Node node) - { - if (node.mesh.primitives.size() > 0) { - for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) { - if (primitive.indexCount > 0) { - // @todo: link mat to node - VulkanglTFModel::Texture texture = glTFModel.textures[glTFModel.materials[primitive.materialIndex].baseColorTextureIndex]; - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &glTFModel.images[texture.imageIndex].descriptorSet, 0, nullptr); - vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); - } - } - } - for (auto& child : node.children) { - drawglTFNode(commandBuffer, child); - } - } - - void drawglTFModel(VkCommandBuffer commandBuffer) - { - // All vertices and indices are stored in single buffers, so we only need to bind once - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &glTFModel.vertices.buffer, offsets); - vkCmdBindIndexBuffer(commandBuffer, glTFModel.indices.buffer, 0, VK_INDEX_TYPE_UINT32); - for (auto& node : glTFModel.nodes) { - drawglTFNode(commandBuffer, node); - } - } - // @todo void loadglTF(std::string filename) { @@ -486,6 +489,7 @@ public: bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename); #endif + // Pass some Vulkan resources required for setup and rendering to the glTF model loading class glTFModel.vulkanDevice = vulkanDevice; glTFModel.copyQueue = queue; @@ -508,6 +512,10 @@ public: return; } + // Create and upload vertex and index buffer + // We will be using one single vertex buffer and one single index buffer for the whole glTF scene + // Primitives (of the glTF model) will then index into these using index offsets + size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFModel::Vertex); size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); glTFModel.indices.count = static_cast(indexBuffer.size()); @@ -517,8 +525,7 @@ public: VkDeviceMemory memory; } vertexStaging, indexStaging; - // Create staging buffers - // Vertex data + // Create host visible staging buffers (source) VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, @@ -535,15 +542,13 @@ public: &indexStaging.memory, indexBuffer.data())); - // Create device local buffers - // Vertex buffer + // Create device local buffers (targat) VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBufferSize, &glTFModel.vertices.buffer, &glTFModel.vertices.memory)); - // Index buffer VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, @@ -553,7 +558,6 @@ public: // Copy data from staging buffers (host) do device local buffer (gpu) VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = {}; copyRegion.size = vertexBufferSize; @@ -574,6 +578,7 @@ public: VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); + // Free staging resources vkDestroyBuffer(device, vertexStaging.buffer, nullptr); vkFreeMemory(device, vertexStaging.memory, nullptr); vkDestroyBuffer(device, indexStaging.buffer, nullptr); From 374ee215bb264a027a2477efb7958bb4c63be7be Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 12 Apr 2020 22:12:44 +0200 Subject: [PATCH 06/20] User camera class --- examples/mesh/mesh.cpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 5226580f..c4d44791 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -390,14 +390,14 @@ public: VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { - zoom = -5.5f; - zoomSpeed = 2.5f; - rotationSpeed = 0.5f; - rotation = { -0.5f, -112.75f, 0.0f }; - cameraPos = { 0.1f, 1.1f, 0.0f }; title = "glTF model rendering"; + camera.type = Camera::CameraType::lookat; + camera.movementSpeed = 2.5f; + camera.rotationSpeed = 0.5f; + camera.setPosition(glm::vec3(0.1f, 1.1f, -20.0f)); + camera.setRotation(glm::vec3(-0.5f, -112.75f, 0.0f)); + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); settings.overlay = true; - //@todo: Use camera } ~VulkanExample() @@ -705,14 +705,8 @@ public: void updateUniformBuffers() { - uboVS.projection = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f); - glm::mat4 viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom)); - - uboVS.model = viewMatrix * glm::translate(glm::mat4(1.0f), cameraPos); - uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); - uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); - uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); - + uboVS.projection = camera.matrices.perspective; + uboVS.model = camera.matrices.view; memcpy(uniformBuffers.scene.mapped, &uboVS, sizeof(uboVS)); } From 6c43ab37ff3508924b2a79d456246b617f32c857 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 13 Apr 2020 16:04:59 +0200 Subject: [PATCH 07/20] Added property to flip y-axis to camera class --- base/camera.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/base/camera.hpp b/base/camera.hpp index 97a8824b..c7036992 100644 --- a/base/camera.hpp +++ b/base/camera.hpp @@ -23,11 +23,15 @@ private: glm::mat4 rotM = glm::mat4(1.0f); glm::mat4 transM; - rotM = glm::rotate(rotM, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); + rotM = glm::rotate(rotM, glm::radians(rotation.x * (flipY ? -1.0f : 1.0f)), glm::vec3(1.0f, 0.0f, 0.0f)); rotM = glm::rotate(rotM, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); rotM = glm::rotate(rotM, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); - transM = glm::translate(glm::mat4(1.0f), position); + glm::vec3 translation = position; + if (flipY) { + translation.y *= -1.0f; + } + transM = glm::translate(glm::mat4(1.0f), translation); if (type == CameraType::firstperson) { @@ -51,6 +55,7 @@ public: float movementSpeed = 1.0f; bool updated = false; + bool flipY = false; struct { @@ -85,6 +90,9 @@ public: this->znear = znear; this->zfar = zfar; matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + if (flipY) { + matrices.perspective[1, 1] *= -1.0f; + } }; void updateAspectRatio(float aspect) From 579c7d086f016af2a5440bd3ffa9e163a00980b7 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 13 Apr 2020 16:26:40 +0200 Subject: [PATCH 08/20] Calculate matrices from node-hierarchy and pass via push constant --- data/shaders/mesh/mesh.frag | 2 +- data/shaders/mesh/mesh.frag.spv | Bin 2008 -> 2024 bytes data/shaders/mesh/mesh.vert | 23 +++++++++++------------ data/shaders/mesh/mesh.vert.spv | Bin 2748 -> 3212 bytes examples/mesh/mesh.cpp | 24 +++++++++++++++++++----- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/data/shaders/mesh/mesh.frag b/data/shaders/mesh/mesh.frag index 17d4a95b..fb415cef 100644 --- a/data/shaders/mesh/mesh.frag +++ b/data/shaders/mesh/mesh.frag @@ -18,7 +18,7 @@ void main() vec3 L = normalize(inLightVec); vec3 V = normalize(inViewVec); vec3 R = reflect(-L, N); - vec3 diffuse = max(dot(N, L), 0.0) * inColor; + vec3 diffuse = max(dot(N, L), 0.15) * inColor; vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75); outFragColor = vec4(diffuse * color.rgb + specular, 1.0); } \ No newline at end of file diff --git a/data/shaders/mesh/mesh.frag.spv b/data/shaders/mesh/mesh.frag.spv index 3c870fe52862fce99f79607ec09940cf8e376fee..5cd562653a43a2f4e3575be29980fd2deef432dd 100644 GIT binary patch delta 484 zcmXw#KS~2p5XNVAqfs!VvCve4ji7~92*hj>-KdFwqLqawu=E70h1dyVWo{920I%T% z1g~Hr`1_vkz2yfp-^|W@?|nAEn~gl%uA8~7@J(MEw_eWg>h9&+y+3=o`0*RBX78KD zuGMVW%nm>&)2#nz2^8MU@8Sw>s~9r_j@`Bzr; zNE~*c3o^3C2$|}GYAsNoOuaBj1`a_c)Kf676SW>IR3E`UD3_sQScNGMqWa0dR)qp_ zSR{u}alt9EhNv+O9TRKOW9*p_t55nhnoOU8be*J~6HC)&l6DcawuNW8!lPVXf(GaZ QHEe)xr)N~{mk}NK11u^e#sB~S delta 442 zcmXw!y-q?w5QS&%RnbI3V_~dJY)G`QFor~diz|pfpccl$C$RJh(1OsJ5TC>l--So; z6)X&VXSutZlbJblcFxXkZCMk@pNw;8Yqmjb zs<8J?s@t(H+DCME$O|AYVV`#w)H%S&&dNH>e)2^&I)`#lk1ewk;x9h+KveIa5e={e1cf3s diff --git a/data/shaders/mesh/mesh.vert b/data/shaders/mesh/mesh.vert index 22b6aeb9..2760684e 100644 --- a/data/shaders/mesh/mesh.vert +++ b/data/shaders/mesh/mesh.vert @@ -5,12 +5,16 @@ layout (location = 1) in vec3 inNormal; layout (location = 2) in vec2 inUV; layout (location = 3) in vec3 inColor; -layout (set = 0, binding = 0) uniform UBO +layout (set = 0, binding = 0) uniform UBOScene { mat4 projection; - mat4 model; + mat4 view; vec4 lightPos; -} ubo; +} uboScene; + +layout(push_constant) uniform PushConsts { + mat4 model; +} primitive; layout (location = 0) out vec3 outNormal; layout (location = 1) out vec3 outColor; @@ -18,21 +22,16 @@ layout (location = 2) out vec2 outUV; layout (location = 3) out vec3 outViewVec; layout (location = 4) out vec3 outLightVec; -out gl_PerVertex -{ - vec4 gl_Position; -}; - void main() { outNormal = inNormal; outColor = inColor; outUV = inUV; - gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0); + gl_Position = uboScene.projection * uboScene.view * primitive.model * vec4(inPos.xyz, 1.0); - vec4 pos = ubo.model * vec4(inPos, 1.0); - outNormal = mat3(ubo.model) * inNormal; - vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz; + vec4 pos = uboScene.view * vec4(inPos, 1.0); + outNormal = mat3(uboScene.view) * inNormal; + vec3 lPos = mat3(uboScene.view) * uboScene.lightPos.xyz; outLightVec = lPos - pos.xyz; outViewVec = -pos.xyz; } \ No newline at end of file diff --git a/data/shaders/mesh/mesh.vert.spv b/data/shaders/mesh/mesh.vert.spv index 2d0a79fc927d5ef5a3553d76fa5e729080693ada..cf6fc4ac34758c1159e7f64a2d1acf8dcb20d627 100644 GIT binary patch literal 3212 zcmZ9NYjYG;5Qc}`08tc?i;8ix@s37RKoo&M(2W{NAZEP_*<{=dZe~-v8?^i)RsI6~ zN&YIoQ04Q?Ovf=N)tr9c)3?)ob{HR_kSCC%$kWI($Qk4vq=R&kasI~G|0d8R5$Z7} zVT{w?9o^~=w%2>;a>mW_J*wc$gWW>E*B_w5O%!>vyrjg|fX!OFI=7xIFCg|)$#dXt z@9s)EXs3fw`YRB&`{_ne3NcGtYk82Rzj$ul4R&kj zW!FLu^}7s=_YUB-0?BLYpe6KA|jHlfqOoQDuiD=&tux88ce;hx& zo<=?EV=*u%J}Bi?jd454R$&2-egx?foDoso;`}l614x{U zV~%0(o6%nExo<`v^Em_aS&R9Ov1==@z1TMu`HrLO8|Px*Sj0PtZanXQf`3o3HxSp^ z3ZF*T{tWn8cJ1Y+(R@!4!!yOYo@ciP&#g=BH$%=mFR(Wd7*xY{q#8<%Xk0ZXzt9aW3ah&TR%-`EMn_vM-I zP<)-tv+OS+O~g5CZ!cd)( zm5;l;i|!n66UP~zN90_=T|k#}*1=t@IQzUp$DGUii2mAp?tA!?xSm}?%&(0#`#wHI z{7qQ1{Ss>zy|{wDzZX~G8!dBwHgX9-Rp zMq8Z584_WOJTr%$~P--6{8SNKF9zJs$CV{7ZZ{Pz5acrU+6 z_QrYMMdafS_bN`_6|tjt>u|=uhuFJ2>~hiT4RpDPzlrWUi})$JoZ^c3e^j&i3C+v$MoZ^ajzgOSD`*7MAtIIBagw-YFU!ZBqM*si- literal 2748 zcmZXU*KQP142Fj!8+z}3L+B;+-a<`)KtjpVOMn#&+U%fZ5!`{q6Tlj`AI4n zA z#2n$n<4l(1ToGrD)i|+n!)V0G{fjGddA5FEQR|7`TfhC@f+~Rr3YcTxtoKvg_x*}} zui~dAwm-|crDqLrhFE(QRzGVUU5&rBiQTV*=ajbm=@L7m*q?_TcXD5BZ{@<@ndu+< z&dhz!vwxmvEuOcORbP4i#m+R&TaImP?2Dao8&Y{QI%2H|}&0+u4Y%@ob6p(SM}G`owv6u%n*4rER?DJ8$2R@qv9q;$ysSc4F^9Z{y=EcB67iaC@-jW>vVo zC1<}TxVin^hZ?WH`_A<{dEFaAJzpPdcFqS-->3KT-eUdYPKU8)-suROeAIswyITJ- zu$&V0AIFxf)_N}8&T2Euoto00>ymi=vbEup$&P(*Y zoJZwV#Tuchild tree), no animations, no skins, etc. + * This means no complex materials, no animations, no skins, etc. * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 * * Other samples will load models using a dedicated model loader with more features (see base/VulkanglTFModel.hpp) @@ -203,7 +203,7 @@ public: node.matrix *= glm::mat4(q); } if (inputNode.scale.size() == 3) { - node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); + node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.scale.data()))); } if (inputNode.matrix.size() == 16) { node.matrix = glm::make_mat4x4(inputNode.matrix.data()); @@ -261,8 +261,6 @@ public: vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); vert.color = glm::vec3(1.0f); - // Flip Y-Axis - vert.pos.y *= -1.0f; vertexBuffer.push_back(vert); } } @@ -329,10 +327,21 @@ public: void drawglTFNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node) { if (node.mesh.primitives.size() > 0) { + // Pass the node's matrix via push constanst + // Traverse the node hierarchy to the top-most parent to get the final matrix of the current node + glm::mat4 nodeMatrix = node.matrix; + VulkanglTFModel::Node* currentParent = node.parent; + while (currentParent) { + nodeMatrix = currentParent->matrix * nodeMatrix; + currentParent = currentParent->parent; + } + // Pass the final matrix to the vertex shader using push constants + vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix); for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) { if (primitive.indexCount > 0) { // Get the texture index for this primitive VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex]; + // Bind the descriptor for the current primitive's texture vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr); vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0); } @@ -392,6 +401,7 @@ public: { title = "glTF model rendering"; camera.type = Camera::CameraType::lookat; + camera.flipY = true; camera.movementSpeed = 2.5f; camera.rotationSpeed = 0.5f; camera.setPosition(glm::vec3(0.1f, 1.1f, -20.0f)); @@ -507,7 +517,6 @@ public: } } else { - // TODO: throw std::cerr << "Could not load gltf file: " << error << std::endl; return; } @@ -616,6 +625,11 @@ public: // Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material) std::array setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures }; VkPipelineLayoutCreateInfo pipelineLayoutCI= vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); + // We will use push constants to push the local matrices of a primitive to the vertex shader + VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0); + // Push constant ranges are part of the pipeline layout + pipelineLayoutCI.pushConstantRangeCount = 1; + pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); // Descriptor set for scene matrices From c5e8c178c208dcdb2c8738ccde0d73d2feab7c02 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Mon, 13 Apr 2020 18:58:02 +0200 Subject: [PATCH 09/20] Free glTF model resources in destructor --- examples/mesh/mesh.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index fb35da1f..0e6713b7 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -122,6 +122,21 @@ public: std::vector materials; std::vector nodes; + ~VulkanglTFModel() + { + // 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 (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); + } + } + /* glTF loading functions @@ -324,7 +339,7 @@ public: */ // Draw a single node including child nodes (if present) - void drawglTFNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node) + void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node) { if (node.mesh.primitives.size() > 0) { // Pass the node's matrix via push constanst @@ -348,7 +363,7 @@ public: } } for (auto& child : node.children) { - drawglTFNode(commandBuffer, pipelineLayout, child); + drawNode(commandBuffer, pipelineLayout, child); } } @@ -361,7 +376,7 @@ public: vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32); // Render all nodes at top-level for (auto& node : nodes) { - drawglTFNode(commandBuffer, pipelineLayout, node); + drawNode(commandBuffer, pipelineLayout, node); } } From bb8d4c6df1435629628391fce064e4275e3f3aaa Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 09:17:53 +0200 Subject: [PATCH 10/20] Add default frame submission function to base class --- base/vulkanexamplebase.cpp | 11 ++++++++++- base/vulkanexamplebase.h | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index 871858de..8873af6b 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -90,6 +90,15 @@ VkResult VulkanExampleBase::createInstance(bool enableValidation) return vkCreateInstance(&instanceCreateInfo, nullptr, &instance); } +void VulkanExampleBase::drawFrame() +{ + VulkanExampleBase::prepareFrame(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VulkanExampleBase::submitFrame(); +} + std::string VulkanExampleBase::getWindowTitle() { std::string device(deviceProperties.deviceName); @@ -298,7 +307,7 @@ void VulkanExampleBase::renderLoop() break; } } - if (!IsIconic(window)) { + if (prepared && !IsIconic(window)) { renderFrame(); } } diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index e28efb10..5c38ea2f 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -319,6 +319,9 @@ public: // Pure virtual render function (override in derived class) virtual void render() = 0; + /** @brief (Virtual) Default image acquire and command buffer submission function */ + virtual void drawFrame(); + // Called when view change occurs // Can be overriden in derived class to e.g. update uniform buffers // Containing view dependant matrices From e9b9332d043f9383941f305b8a74f5bc83eb0258 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 09:18:48 +0200 Subject: [PATCH 11/20] glTF model loading and rendering sample uses default frame submit function from base class --- examples/mesh/mesh.cpp | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 0e6713b7..44b582f6 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -419,7 +419,7 @@ public: camera.flipY = true; camera.movementSpeed = 2.5f; camera.rotationSpeed = 0.5f; - camera.setPosition(glm::vec3(0.1f, 1.1f, -20.0f)); + camera.setPosition(glm::vec3(0.1f, 1.1f, -10.0f)); camera.setRotation(glm::vec3(-0.5f, -112.75f, 0.0f)); camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); settings.overlay = true; @@ -438,9 +438,6 @@ public: vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); - // @todo - //model.destroy(device); - uniformBuffers.scene.destroy(); } @@ -739,20 +736,6 @@ public: memcpy(uniformBuffers.scene.mapped, &uboVS, sizeof(uboVS)); } - void draw() - { - VulkanExampleBase::prepareFrame(); - - // Command buffer to be sumitted to the queue - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - - // Submit to queue - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - VulkanExampleBase::submitFrame(); - } - void prepare() { VulkanExampleBase::prepare(); @@ -766,11 +749,10 @@ public: virtual void render() { - if (!prepared) - return; - draw(); - if (camera.updated) + drawFrame(); + if (camera.updated) { updateUniformBuffers(); + } } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) From cbe751d26e1173716d407a3836704caa17c46778 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 10:05:47 +0200 Subject: [PATCH 12/20] Base class cleanup and restructuring --- base/vulkanexamplebase.cpp | 6 +-- base/vulkanexamplebase.h | 98 ++++++++++++-------------------------- 2 files changed, 33 insertions(+), 71 deletions(-) diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index 8873af6b..da4aae53 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -90,7 +90,7 @@ VkResult VulkanExampleBase::createInstance(bool enableValidation) return vkCreateInstance(&instanceCreateInfo, nullptr, &instance); } -void VulkanExampleBase::drawFrame() +void VulkanExampleBase::renderFrame() { VulkanExampleBase::prepareFrame(); submitInfo.commandBufferCount = 1; @@ -236,7 +236,7 @@ VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileNa return shaderStage; } -void VulkanExampleBase::renderFrame() +void VulkanExampleBase::nextFrame() { auto tStart = std::chrono::high_resolution_clock::now(); if (viewUpdated) @@ -308,7 +308,7 @@ void VulkanExampleBase::renderLoop() } } if (prepared && !IsIconic(window)) { - renderFrame(); + nextFrame(); } } #elif defined(VK_USE_PLATFORM_ANDROID_KHR) diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index 5c38ea2f..2f0336ad 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -38,7 +38,6 @@ #define GLM_ENABLE_EXPERIMENTAL #include #include -#include #include #include "vulkan/vulkan.h" @@ -57,17 +56,20 @@ class VulkanExampleBase { private: - // Get window title with example name, device, et. std::string getWindowTitle(); - /** brief Indicates that the view (position, rotation) has changed and buffers containing camera matrices need to be updated */ bool viewUpdated = false; - // Destination dimensions for resizing the window uint32_t destWidth; uint32_t destHeight; bool resizing = false; - // Called if the window is resized and some resources have to be recreatesd void windowResize(); void handleMouseMove(int32_t x, int32_t y); + void nextFrame(); + void updateOverlay(); + void createPipelineCache(); + void createCommandPool(); + void createSynchronizationPrimitives(); + void initSwapchain(); + void setupSwapChain(); protected: // Frame counter to display fps uint32_t frameCounter = 0; @@ -241,13 +243,9 @@ public: xcb_intern_atom_reply_t *atom_wm_delete_window; #endif - // Default ctor VulkanExampleBase(bool enableValidation = false); - - // dtor virtual ~VulkanExampleBase(); - - // Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) + /** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */ bool initVulkan(); #if defined(_WIN32) @@ -310,95 +308,59 @@ public: void initxcbConnection(); void handleEvent(const xcb_generic_event_t *event); #endif - /** - * Create the application wide Vulkan instance - * - * @note Virtual, can be overriden by derived example class for custom instance creation - */ + /** @brief (Virtual) Creates the application wide Vulkan instance */ virtual VkResult createInstance(bool enableValidation); - - // Pure virtual render function (override in derived class) + /** @brief (Pure virtual) Render function to be implemented by the sample application */ virtual void render() = 0; - /** @brief (Virtual) Default image acquire and command buffer submission function */ - virtual void drawFrame(); - - // Called when view change occurs - // Can be overriden in derived class to e.g. update uniform buffers - // Containing view dependant matrices + /** @brief (Virtual) Called when the camera view has changed */ virtual void viewChanged(); /** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */ virtual void keyPressed(uint32_t); - /** @brief (Virtual) Called after th mouse cursor moved and before internal events (like camera rotation) is handled */ + /** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */ virtual void mouseMoved(double x, double y, bool &handled); - // Called when the window has been resized - // Can be overriden in derived class to recreate or rebuild resources attached to the frame buffer / swapchain + /** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */ virtual void windowResized(); - // Pure virtual function to be overriden by the dervice class - // Called in case of an event where e.g. the framebuffer has to be rebuild and thus - // all command buffers that may reference this + /** @brief (Virtual) Called when resources have been recreated that require a rebuild of the command buffers (e.g. frame buffer), to be implemente by the sample application */ virtual void buildCommandBuffers(); - - void createSynchronizationPrimitives(); - - // Creates a new (graphics) command pool object storing command buffers - void createCommandPool(); - // Setup default depth and stencil views + /** @brief (Virtual) Setup default depth and stencil views */ virtual void setupDepthStencil(); - // Create framebuffers for all requested swap chain images - // Can be overriden in derived class to setup a custom framebuffer (e.g. for MSAA) + /** @brief (Virtual) Setup default framebuffers for all requested swapchain images */ virtual void setupFrameBuffer(); - // Setup a default render pass - // Can be overriden in derived class to setup a custom render pass (e.g. for MSAA) + /** @brief (Virtual) Setup a default renderpass */ virtual void setupRenderPass(); - /** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */ virtual void getEnabledFeatures(); - // Connect and prepare the swap chain - void initSwapchain(); - // Create swap chain images - void setupSwapChain(); - - // Check if command buffers are valid (!= VK_NULL_HANDLE) + /** @brief Checks if command buffers are valid (!= VK_NULL_HANDLE) */ bool checkCommandBuffers(); - // Create command buffers for drawing commands + /** @brief Creates the per-frame command buffers */ void createCommandBuffers(); - // Destroy all command buffers and set their handles to VK_NULL_HANDLE - // May be necessary during runtime if options are toggled + /** @brief Destroy all command buffers and set their handles to VK_NULL_HANDLE */ void destroyCommandBuffers(); - // Command buffer creation - // Creates and returns a new command buffer + /** @brief Creates and returns a new command buffer */ VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin); - // End the command buffer, submit it to the queue and free (if requested) - // Note : Waits for the queue to become idle + /** @brief End the command buffer, submit it to the queue and free (if requested) */ void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free); - // Create a cache pool for rendering pipelines - void createPipelineCache(); - - // Prepare commonly used Vulkan functions + /** @brief Prepares all Vulkan resources and functions required to run the sample */ virtual void prepare(); - // Load a SPIR-V shader + /** @brief Loads a SPIR-V shader file for the given shader stage */ VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage); - // Start the main render loop + /** @brief Entry point for the main render loop */ void renderLoop(); - // Render one frame of a render loop on platforms that sync rendering - void renderFrame(); - - void updateOverlay(); + /** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */ void drawUI(const VkCommandBuffer commandBuffer); - // Prepare the frame for workload submission - // - Acquires the next image from the swap chain - // - Sets the default wait and signal semaphores + /** Prepare the next frame for workload sumbission by acquiring the next swap chain image */ void prepareFrame(); - - // Submit the frames' workload + /** @brief Presents the current image to the swap chain */ void submitFrame(); + /** @brief (Virtual) Default image acquire + submission and command buffer submission function */ + virtual void renderFrame(); /** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */ virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay); From 0ca5f41308e0a7d6de08ae685c8ec88a190f4f2a Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 10:11:50 +0200 Subject: [PATCH 13/20] Added missing include --- base/vulkanexamplebase.h | 1 + 1 file changed, 1 insertion(+) diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index 2f0336ad..8d6ba2b6 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -39,6 +39,7 @@ #include #include #include +#include #include "vulkan/vulkan.h" From 3ae053d005b5195c88d15d0d83a30d5939d4ec3c Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 10:59:16 +0200 Subject: [PATCH 14/20] Code-Cleanup Use flight helmet glTF 2.0 sample model --- examples/mesh/mesh.cpp | 52 +++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/examples/mesh/mesh.cpp b/examples/mesh/mesh.cpp index 44b582f6..f9a2de1b 100644 --- a/examples/mesh/mesh.cpp +++ b/examples/mesh/mesh.cpp @@ -1,13 +1,13 @@ /* -* Vulkan Example - Model loading and rendering +* Vulkan Example - glTF scene loading and rendering * -* Copyright (C) 2016-2020 by Sascha Willems - www.saschawillems.de +* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ /* - * Shows how to load and display a simple mesh from a glTF file + * Shows how to load and display a simple scene from a glTF file * Note that this isn't a complete glTF loader and only basic functions are shown here * This means no complex materials, no animations, no skins, etc. * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 @@ -389,15 +389,14 @@ public: VulkanglTFModel glTFModel; - struct { - vks::Buffer scene; - } uniformBuffers; - - struct { - glm::mat4 projection; - glm::mat4 model; - glm::vec4 lightPos = glm::vec4(25.0f, 5.0f, 5.0f, 1.0f); - } uboVS; + struct ShaderData { + vks::Buffer buffer; + struct Values { + glm::mat4 projection; + glm::mat4 model; + glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, -5.0f, 1.0f); + } values; + } shaderData; struct Pipelines { VkPipeline solid; @@ -417,10 +416,8 @@ public: title = "glTF model rendering"; camera.type = Camera::CameraType::lookat; camera.flipY = true; - camera.movementSpeed = 2.5f; - camera.rotationSpeed = 0.5f; - camera.setPosition(glm::vec3(0.1f, 1.1f, -10.0f)); - camera.setRotation(glm::vec3(-0.5f, -112.75f, 0.0f)); + camera.setPosition(glm::vec3(0.0f, -0.1f, -1.0f)); + camera.setRotation(glm::vec3(0.0f, -135.0f, 0.0f)); camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f); settings.overlay = true; } @@ -438,7 +435,7 @@ public: vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr); - uniformBuffers.scene.destroy(); + shaderData.buffer.destroy(); } virtual void getEnabledFeatures() @@ -486,8 +483,7 @@ public: } } - // @todo - void loadglTF(std::string filename) + void loadglTFFile(std::string filename) { tinygltf::Model glTFInput; tinygltf::TinyGLTF gltfContext; @@ -608,7 +604,7 @@ public: void loadAssets() { - loadglTF(getAssetPath() + "models/voyager/voyager.gltf"); + loadglTFFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf"); } void setupDescriptors() @@ -647,7 +643,7 @@ public: // Descriptor set for scene matrices VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor); vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); // Descriptor sets for materials for (auto& image : glTFModel.images) { @@ -720,20 +716,20 @@ public: VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBuffers.scene, - sizeof(uboVS))); + &shaderData.buffer, + sizeof(shaderData.values))); // Map persistent - VK_CHECK_RESULT(uniformBuffers.scene.map()); + VK_CHECK_RESULT(shaderData.buffer.map()); updateUniformBuffers(); } void updateUniformBuffers() { - uboVS.projection = camera.matrices.perspective; - uboVS.model = camera.matrices.view; - memcpy(uniformBuffers.scene.mapped, &uboVS, sizeof(uboVS)); + shaderData.values.projection = camera.matrices.perspective; + shaderData.values.model = camera.matrices.view; + memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values)); } void prepare() @@ -749,7 +745,7 @@ public: virtual void render() { - drawFrame(); + renderFrame(); if (camera.updated) { updateUniformBuffers(); } From e0c15f62da6361ec862b870dbb005dd6c8118b4c Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 11:50:46 +0200 Subject: [PATCH 15/20] Renamed mesh example to glTF scene --- README.md | 4 ++-- android/examples/{mesh => gltfscene}/CMakeLists.txt | 3 ++- android/examples/{mesh => gltfscene}/build.gradle | 10 +++++----- .../src/main/AndroidManifest.xml | 4 ++-- .../saschawillems/vulkanSample/VulkanActivity.java | 0 data/shaders/{mesh => gltfscene}/mesh.frag | 0 data/shaders/{mesh => gltfscene}/mesh.frag.spv | Bin data/shaders/{mesh => gltfscene}/mesh.vert | 0 data/shaders/{mesh => gltfscene}/mesh.vert.spv | Bin data/shaders/mesh/generate-spirv.bat | 2 -- examples/CMakeLists.txt | 2 +- examples/{mesh/mesh.cpp => gltfscene/gltfscene.cpp} | 5 +++-- 12 files changed, 15 insertions(+), 15 deletions(-) rename android/examples/{mesh => gltfscene}/CMakeLists.txt (93%) rename android/examples/{mesh => gltfscene}/build.gradle (83%) rename android/examples/{mesh => gltfscene}/src/main/AndroidManifest.xml (90%) rename android/examples/{mesh => gltfscene}/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java (100%) rename data/shaders/{mesh => gltfscene}/mesh.frag (100%) rename data/shaders/{mesh => gltfscene}/mesh.frag.spv (100%) rename data/shaders/{mesh => gltfscene}/mesh.vert (100%) rename data/shaders/{mesh => gltfscene}/mesh.vert.spv (100%) delete mode 100644 data/shaders/mesh/generate-spirv.bat rename examples/{mesh/mesh.cpp => gltfscene/gltfscene.cpp} (99%) diff --git a/README.md b/README.md index f1d8073a..da51b273 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,9 @@ Loads a 2D texture array containing multiple 2D texture slices (each with its ow Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions. -#### [11 - Model rendering](examples/mesh/) +#### [11 - glTF scene loading and rendering](examples/gltfscene/) -Loads a 3D model and texture maps from a common file format (using [assimp](https://github.com/assimp/assimp)), uploads the vertex and index buffer data to video memory, sets up a matching vertex layout and renders the 3D model. +Shows how to load the scene from a [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The structure of the glTF 2.0 scene is converted into data structures required to render the scene with Vulkan. #### [12 - Input attachments](examples/inputattachments) diff --git a/android/examples/mesh/CMakeLists.txt b/android/examples/gltfscene/CMakeLists.txt similarity index 93% rename from android/examples/mesh/CMakeLists.txt rename to android/examples/gltfscene/CMakeLists.txt index d90f4fb8..6a77bb64 100644 --- a/android/examples/mesh/CMakeLists.txt +++ b/android/examples/gltfscene/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) -set(NAME mesh) +set(NAME gltfscene) set(SRC_DIR ../../../examples/${NAME}) set(BASE_DIR ../../../base) @@ -24,6 +24,7 @@ include_directories(${EXTERNAL_DIR}/glm) include_directories(${EXTERNAL_DIR}/gli) include_directories(${EXTERNAL_DIR}/imgui) include_directories(${EXTERNAL_DIR}/assimp) +include_directories(${EXTERNAL_DIR}/tinygltf) include_directories(${ANDROID_NDK}/sources/android/native_app_glue) target_link_libraries( diff --git a/android/examples/mesh/build.gradle b/android/examples/gltfscene/build.gradle similarity index 83% rename from android/examples/mesh/build.gradle rename to android/examples/gltfscene/build.gradle index 55879332..64ab443d 100644 --- a/android/examples/mesh/build.gradle +++ b/android/examples/gltfscene/build.gradle @@ -4,7 +4,7 @@ apply from: '../gradle/outputfilename.gradle' android { compileSdkVersion 26 defaultConfig { - applicationId "de.saschawillems.vulkanMesh" + applicationId "de.saschawillems.vulkanglTFScene" minSdkVersion 19 targetSdkVersion 26 versionCode 1 @@ -49,14 +49,14 @@ task copyTask { } copy { - from '../../../data/shaders/mesh' - into 'assets/shaders/mesh' + from '../../../data/shaders/gltfscene' + into 'assets/shaders/gltfscene' include '*.*' } copy { - from '../../../data/models/voyager' - into 'assets/models/voyager' + from '../../../data/models/FlightHelmet/glTF' + into 'assets/models/FlightHelmet/glTF' include '*.*' } diff --git a/android/examples/mesh/src/main/AndroidManifest.xml b/android/examples/gltfscene/src/main/AndroidManifest.xml similarity index 90% rename from android/examples/mesh/src/main/AndroidManifest.xml rename to android/examples/gltfscene/src/main/AndroidManifest.xml index 4e24788b..2c6a3474 100644 --- a/android/examples/mesh/src/main/AndroidManifest.xml +++ b/android/examples/gltfscene/src/main/AndroidManifest.xml @@ -1,9 +1,9 @@ + package="de.saschawillems.vulkanglTFScene"> shaderStages = { - loadShader(getAssetPath() + "shaders/mesh/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), - loadShader(getAssetPath() + "shaders/mesh/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) + loadShader(getAssetPath() + "shaders/gltfscene/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getAssetPath() + "shaders/gltfscene/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) }; VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); From a192a685b7a0f35dc4a707b96c10fa66d2e804dc Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 19 Apr 2020 16:42:14 +0200 Subject: [PATCH 16/20] Update tinyglTF --- external/tinygltf/README.md | 105 +- external/tinygltf/json.hpp | 12482 +++++++++++++++++++++++--------- external/tinygltf/stb_image.h | 2753 ++++--- external/tinygltf/tiny_gltf.h | 5082 ++++++++++--- 4 files changed, 14971 insertions(+), 5451 deletions(-) diff --git a/external/tinygltf/README.md b/external/tinygltf/README.md index aa896b2b..2d9143dd 100644 --- a/external/tinygltf/README.md +++ b/external/tinygltf/README.md @@ -2,12 +2,16 @@ `TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library. +`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler. +If you are looking for old, C++03 version, please use `devel-picojson` branch. + ## Status -Work in process(`devel` branch). Very near to release, but need more tests and examples. - -`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler. -If you are looking for old, C++03 version, please use `devel-picojson` branch. + - v2.4.0 Experimental RapidJSON support. Experimental C++14 support(C++14 may give better performance) + - v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class) + - v2.2.0 release(Support loading 16bit PNG. Sparse accessor support) + - v2.1.0 release(Draco support) + - v2.0.0 release(22 Aug, 2018)! ## Builds @@ -24,45 +28,75 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch. * [x] Windows + MinGW * [x] Windows + Visual Studio 2015 Update 3 or later. * Visual Studio 2013 is not supported since they have limited C++11 support and failed to compile `json.hpp`. + * [x] Android NDK * [x] Android + CrystaX(NDK drop-in replacement) GCC * [x] Web using Emscripten(LLVM) * Moderate parsing time and memory consumption. * glTF specification v2.0.0 * [x] ASCII glTF + * [x] Load + * [x] Save * [x] Binary glTF(GLB) - * [x] PBR material description + * [x] Load + * [x] Save(.bin embedded .glb) * Buffers - * [x] Parse BASE64 encoded embedded buffer fata(DataURI). + * [x] Parse BASE64 encoded embedded buffer data(DataURI). * [x] Load `.bin` file. * Image(Using stb_image) - * [x] Parse BASE64 encoded embedded image fata(DataURI). + * [x] Parse BASE64 encoded embedded image data(DataURI). * [x] Load external image file. - * [x] PNG(8bit only) - * [x] JPEG(8bit only) - * [x] BMP - * [x] GIF + * [x] Load PNG(8bit and 16bit) + * [x] Load JPEG(8bit only) + * [x] Load BMP + * [x] Load GIF + * [x] Custom Image decoder callback(e.g. for decoding OpenEXR image) +* Morph traget + * [x] Sparse accessor +* Load glTF from memory +* Custom callback handler + * [x] Image load + * [x] Image save +* Extensions + * [x] Draco mesh decoding + * [ ] Draco mesh encoding + +## Note on extension property + +In extension(`ExtensionMap`), JSON number value is parsed as int or float(number) and stored as `tinygltf::Value` object. If you want a floating point value from `tinygltf::Value`, use `GetNumberAsDouble()` method. + +`IsNumber()` returns true if the underlying value is an int value or a floating point value. ## Examples * [glview](examples/glview) : Simple glTF geometry viewer. * [validator](examples/validator) : Simple glTF validator with JSON schema. +* [basic](examples/basic) : Basic glTF viewer with texturing support. ## Projects using TinyGLTF +* px_render Single header C++ Libraries for Thread Scheduling, Rendering, and so on... https://github.com/pplux/px * Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR * GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF * [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization. +* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework +* Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2 +* [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf. +* [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux +* [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications. * Your projects here! (Please send PR) ## TODOs -* [ ] Write C++ code generator from jSON schema for robust parsing. -* [x] Serialization -* [ ] Compression/decompression(Open3DGC, etc) -* [ ] Support `extensions` and `extras` property +* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing. +* [ ] Mesh Compression/decompression(Open3DGC, etc) + * [x] Load Draco compressed mesh + * [ ] Save Draco compressed mesh + * [ ] Open3DGC? +* [x] Support `extensions` and `extras` property * [ ] HDR image? * [ ] OpenEXR extension through TinyEXR. -* [ ] Write tests for `animation` and `skin` +* [ ] 16bit PNG support in Serialization +* [ ] Write example and tests for `animation` and `skin` ## Licenses @@ -92,12 +126,18 @@ Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your pr using namespace tinygltf; -Model model; +Model model; TinyGLTF loader; std::string err; - -bool ret = loader.LoadASCIIFromFile(&model, &err, argv[1]); -//bool ret = loader.LoadBinaryFromFile(&model, &err, argv[1]); // for binary glTF(.glb) +std::string warn; + +bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]); +//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb) + +if (!warn.empty()) { + printf("Warn: %s\n", warn.c_str()); +} + if (!err.empty()) { printf("Err: %s\n", err.c_str()); } @@ -113,16 +153,28 @@ if (!ret) { * `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF. * `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images. * `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images. +* `TINYGLTF_NO_EXTERNAL_IMAGE` : Do not try to load external image file. This option would be helpful if you do not want to load image files during glTF parsing. +* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand. +* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file. +* `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. +* `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. +* `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`. +* `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this featrure. +* `TINYGLTF_USE_CPP14` : Use C++14 feature(requires C++14 compiler). This may give better performance than C++11. + ### Saving gltTF 2.0 model -* [ ] Buffers. + +* Buffers. * [x] To file * [x] Embedded * [ ] Draco compressed? * [x] Images * [x] To file * [x] Embedded -* [ ] Binary(.glb) +* Binary(.glb) + * [x] .bin embedded single .glb + * [ ] External .bin ## Running tests. @@ -150,8 +202,17 @@ $ ./tester $ ./tester_noexcept ``` +### Fuzzing tests + +See `tests/fuzzer` for details. + +After running fuzzer on Ryzen9 3950X a week, at least `LoadASCIIFromString` looks safe except for out-of-memory error in Fuzzer. +We may be better to introduce bounded memory size checking when parsing glTF data. + ## Third party licenses * json.hpp : Licensed under the MIT License . Copyright (c) 2013-2017 Niels Lohmann . * stb_image : Public domain. * catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0. +* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/ +* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html diff --git a/external/tinygltf/json.hpp b/external/tinygltf/json.hpp index d7cfc07f..c9af0bed 100644 --- a/external/tinygltf/json.hpp +++ b/external/tinygltf/json.hpp @@ -1,11 +1,12 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 +| | |__ | | | | | | version 3.5.0 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . -Copyright (c) 2013-2017 Niels Lohmann . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,42 +30,104 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include // all_of, copy, fill, find, for_each, generate_n, none_of, remove, reverse, transform -#include // array +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 5 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each #include // assert #include // and, not, or -#include // lconv, localeconv -#include // isfinite, labs, ldexp, signbit #include // nullptr_t, ptrdiff_t, size_t -#include // int64_t, uint64_t -#include // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull -#include // memcpy, strlen -#include // forward_list -#include // function, hash, less +#include // hash, less #include // initializer_list -#include // hex -#include // istream, ostream -#include // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator -#include // numeric_limits -#include // locale -#include // map -#include // addressof, allocator, allocator_traits, unique_ptr +#include // istream, ostream +#include // random_access_iterator_tag #include // accumulate -#include // stringstream -#include // getline, stoi, string, to_string -#include // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type -#include // declval, forward, make_pair, move, pair, swap -#include // valarray +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string #include // vector +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + // exclude unsupported compilers -#if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif -#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif #endif #endif @@ -90,14 +153,36 @@ SOFTWARE. #endif // allow to disable exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif // manual branch prediction @@ -109,7 +194,7 @@ SOFTWARE. #define JSON_UNLIKELY(x) x #endif -// cpp language standard detection +// C++ language standard detection #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 @@ -118,30 +203,38 @@ SOFTWARE. #endif /*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 */ -namespace nlohmann -{ -template -struct adl_serializer; +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } -// forward declaration of basic_json (required to split the class) -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer> -class basic_json; - -// Ugly macros to avoid uglier copy-paste when specializing basic_json -// This is only temporary and will be removed in 3.0 +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ @@ -156,17 +249,590 @@ class basic_json; NumberIntegerType, NumberUnsignedType, NumberFloatType, \ AllocatorType, JSONSerializer> +// #include + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + +// #include + + +#include // random_access_iterator_tag + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} +} + +// #include + +// #include + + +#include + +// #include + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template