text: /*
  * Vulkan Example - glTF scene loading and rendering
  *
  * Copyright (C) 2020-2021 by Sascha Willems - www.saschawillems.de
  *
  * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
  */
  
  /*
   * This sample shows how to load and display a simple static scene from a glTF 2.0 file
   * glTF is an extensible file format for three-dimensional scenes and models from Khronos and a good match for Vulkan
   * The specification for glTF 2.0 can be found at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
   * The interesting part is in the VulkanglTFModel class, which:
   * - Loads the glTF 2.0 file using the tiny glTF library from https://github.com/syoyo/tinygltf
   * - Converts the glTF structures into Vulkan objects (buffers, images)
   * - Draws the glTF scene graph
   * Note: Other samples will load models using a dedicated model loader with more features (see base/VulkanglTFModel.hpp)
    */
  
  #define TINYGLTF_IMPLEMENTATION
  #define STB_IMAGE_IMPLEMENTATION
  #define TINYGLTF_NO_STB_IMAGE_WRITE
  #ifdef VK_USE_PLATFORM_ANDROID_KHR
  #define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
  #endif
  #include "tiny_gltf.h"
  
  #include "vulkanexamplebase.h"
  
  #define ENABLE_VALIDATION false
  
  // This class loads the glTF data structures into Vulkan buffers (vertices, indices, images) 
  // It's heavily simplified compared to glTF's full 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;
  
  	// The vertex layout for the samples' model
  	struct Vertex {
  		glm::vec3 pos;
  		glm::vec3 normal;
  		glm::vec2 uv;
  		glm::vec3 color;
  	};
  
  	// Vertices and indices will be stored in single buffers for all meshes in the model
  	// This reduces the number of allocations	
  	struct Vertices {
  		VkBuffer buffer;
  		VkDeviceMemory memory;
  	} vertices;
  	struct Indices {
  		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;
  	};
  
  	// A mesh contains the node's geometry defined by arbitrary number of primitives per material
  	struct Mesh {
  		std::vector<Primitive> primitives;
  	};
  
  	// A node represents an object in the glTF scene graph
  	struct Node {
  		Node* parent;
  		// A node can have an arbitrary number of child nodes
  		std::vector<Node> children;
  		Mesh mesh;
  		glm::mat4 matrix;
  	};
  
  	// A glTF material stores information in e.g. the texture that is attached to it and colors
  	struct Material {
  		glm::vec4 baseColorFactor = glm::vec4(1.0f);
  		uint32_t baseColorTextureIndex;
  	};
  
  	// A glTF image contains the actual image data
  	// Images may be reused by texture objects and are as such separated
  	struct Image {
  		vks::Texture2D texture;
  		// We also store (and create) a descriptor set that's used to access this texture from the fragment shader
  		VkDescriptorSet descriptorSet;
  	};
  
  	// A glTF texture stores a reference to the image and a sampler
  	// In this sample, we are only interested in the image
  	struct Texture {
  		int32_t imageIndex;
  	};
  
  	// List of images in this model
  	std::vector<Image> images;
  	// List of images in this model
  	std::vector<Texture> textures;
  	// List of materials in this model
  	std::vector<Material> materials;
  	// Nested list of nodes in this model
  	std::vector<Node> nodes;
  
  	VulkanglTFModel(vks::VulkanDevice* device, VkQueue copyQueue) {
  		this->vulkanDevice = device;
  		this->copyQueue = copyQueue;
  	}
  
  	~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);
  		}
  	}
  
  	// Model loading functions
  
  	// Load the images stored in the glTF file
  	// In the glTF file used by this sample, all images are stored directly in the file so we don't read them from disk, but rather from the buffers provided by tinyglTF
  	void loadImages(tinygltf::Model& input)
  	{
  		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;
  			// If it's an RGB image (3 components), we convert it to RGBA as most Vulkan implementations don't support 8-Bit RGB-only formats
  			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) {
  					memcpy(rgba, rgb, sizeof(unsigned char) * 3);
  					rgba += 4;
  					rgb += 3;
  				}
  				deleteBuffer = true;
  			}
  			else {
  				buffer = &glTFImage.image[0];
  				bufferSize = glTFImage.image.size();
  			}
  			// Load the texture from the image buffer
  			images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue);
  			if (deleteBuffer) {
  				delete[] buffer;
  			}
  		}
  	}
  
  	// Load the images from the glTF file
  	// For our basic sample, we are only interested in the image indices of the images, so we can select the proper images at draw time
  	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;
  		}
  	}
  
  	// Load the materials from the glTF file
  	// For our basic sample, we are only interested in a small set of material properties
  	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();
  			}
  		}
  	}
  
  	// Recursively load a single node and it's child from the glTF file
  	void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFModel::Vertex>& vertexBuffer)
  	{
  		VulkanglTFModel::Node node{};
  		node.matrix = glm::mat4(1.0f);
  
  		// Get the local node matrix, which is either made up from translation, rotation, scale or directly defined as 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.scale.data())));
  		}
  		if (inputNode.matrix.size() == 16) {
  			node.matrix = glm::make_mat4x4(inputNode.matrix.data());
  		};
  
  		// Load this 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 a node has a mesh attached to it, we'll load the vertex and index data and append it to the models' vertex and index buffers
  		// Nodes with no mesh are non-visual
  		// In glTF, vertex and index buffers are read using 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<uint32_t>(indexBuffer.size());
  				uint32_t vertexStart = static_cast<uint32_t>(vertexBuffer.size());
  				uint32_t indexCount = 0;
  				// Load the vertex data
  				// In glTF, vertex components are stored in separate buffers (positions, normals, etc.), so we also interleave them at the end so we can pass them as vertex input attributes
  				const float* positionBuffer = nullptr;
  				const float* normalsBuffer = nullptr;
  				const float* texCoordsBuffer = nullptr;
  				size_t vertexCount = 0;
  
  				// Get buffer information 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<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
  					vertexCount = accessor.count;
  				}
  				// Get buffer information 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<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
  				}
  				// Get buffer information for vertex texture coordinates
  				// glTF supports multiple sets, but in this sample we only use 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<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
  				}
  
  				// Append the vertex 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);
  					vertexBuffer.push_back(vert);
  				}
  				
  				// Load the 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<uint32_t>(accessor.count);
  
  					// glTF supports different component types of indices, so we need to check what index type the mesh is using
  					switch (accessor.componentType) {
  					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
  						const uint32_t* buf = reinterpret_cast<const uint32_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
  						for (size_t index = 0; index < accessor.count; index++) {
  							indexBuffer.push_back(buf[index] + vertexStart);
  						}
  						break;
  					}
  					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
  						const uint16_t* buf = reinterpret_cast<const uint16_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
  						for (size_t index = 0; index < accessor.count; index++) {
  							indexBuffer.push_back(buf[index] + vertexStart);
  						}
  						break;
  					}
  					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
  						const uint8_t* buf = reinterpret_cast<const uint8_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
  						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;
  					}
  				}
  
  				// Append the primitive to the current node
  				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);
  		}
  	}
  
  	// Loads a glTF file from disk using tiny glTF and converts it into our own structure and Vulkan objects
  	void loadFromFile(const std::string& filename)
  	{
  		tinygltf::Model glTFInput;
  		tinygltf::TinyGLTF gltfContext;
  		std::string error, warning;
  
  #if defined(__ANDROID__)
  		// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
  		// We let tinygltf handle this by passing the asset manager of our app
  		tinygltf::asset_manager = androidApp->activity->assetManager;
  #endif
  
  		// Load the glTF file using tinyglTF
  		bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename);
  
  		// We will be using one single vertex buffer and one single index buffer for the whole glTF scene to keep the number of memory allocations low
  		// So we accumulate indices and vertices across all glTF nodes at loading time, from which we well then later on create the single Vulkan buffers
  		std::vector<uint32_t> indexBuffer;
  		std::vector<Vertex> vertexBuffer;
  
  		if (fileLoaded) {
  			// If loading was succcessfull, we load the glTF information required for this sample
  			loadImages(glTFInput);
  			loadMaterials(glTFInput);
  			loadTextures(glTFInput);
  			const tinygltf::Scene& scene = glTFInput.scenes[0];
  			for (size_t i = 0; i < scene.nodes.size(); i++) {
  				const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]];
  				loadNode(node, glTFInput, nullptr, indexBuffer, vertexBuffer);
  			}
  		} else {
  			vks::tools::exitFatal("Could not open the glTF file.\n\nThe file is part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
  			return;
  		}
  
  		// Create and upload vertex and index buffers
  		size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex);
  		size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
  		indices.count = static_cast<uint32_t>(indexBuffer.size());
  		vks::Buffer vertexStaging, indexStaging;
  
  		// Create host visible staging buffers and copy the vertex and index data to them (source)
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
  			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
  			&vertexStaging,
  			vertexBufferSize,
  			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,
  			&indexStaging,
  			indexBufferSize,
  			indexBuffer.data()));
  
  		// Create device local buffers (destination)
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
  			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
  			vertexBufferSize,
  			&vertices.buffer,
  			&vertices.memory));
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
  			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
  			indexBufferSize,
  			&indices.buffer,
  			&indices.memory));
  
  		// Copy data from staging buffers (host) do device local buffer (gpu)
  		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
  		VkBufferCopy copyRegion = {};
  		copyRegion.size = vertexBufferSize;
  		vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertices.buffer, 1, &copyRegion);
  		copyRegion.size = indexBufferSize;
  		vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, &copyRegion);
  		vulkanDevice->flushCommandBuffer(copyCmd, copyQueue, true);
  
  		// Free staging resources
  		vertexStaging.destroy();
  		indexStaging.destroy();
  	}
  
  	// Model drawing functions
  
  	// Recursively draw a node and it's children
  	void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node)
  	{
  		// Only nodes that have a mesh and at least one primitive in that mesh
  		// Nodes withouth a mesh (or zero primitives) are non-visual
  		if (node.mesh.primitives.size() > 0) {
  			// We need to traverse the screne-graph 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;
  			}
  			// We then pass the node's current matrix to the vertex shader using push constants
  			vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
  			// Draw all primitives in this mesh
  			for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) {
  				if (primitive.indexCount > 0) {
  					// Bind the descriptor for the texture of the current primitive, taken from it's material
  					VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex];
  					vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr);
  					// Draw the current primitive from the vertex/index buffer of the model
  					vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
  				}
  			}
  		}
  		for (auto& child : node.children) {
  			drawNode(commandBuffer, pipelineLayout, child);
  		}
  	}
  
  	// Draw the glTF scene starting at the top-level-node
  	void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout)
  	{
  		// All vertices and indices are stored in single buffers, so we only need to bind once and then offset during the draw call
  		VkDeviceSize offsets[1] = { 0 };
  		vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
  		vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
  		// Recursively draw all nodes at top-level
  		for (auto& node : nodes) {
  			drawNode(commandBuffer, pipelineLayout, node);
  		}
  	}
  
  };
  
  class VulkanExample : public VulkanExampleBase
  {
  public:
  	bool wireframe = false;
  
  	VulkanglTFModel* glTFModel;
  
  	struct UniformData {
  		glm::mat4 projection;
  		glm::mat4 model;
  		glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, -5.0f, 1.0f);
  	} uniformData;
  	struct FrameObjects : public VulkanFrameObjects {
  		vks::Buffer uniformBuffer;
  		VkDescriptorSet descriptorSet;
  	};
  	std::vector<FrameObjects> frameObjects;
  
  	struct Pipelines {
  		VkPipeline solid;
  		VkPipeline wireframe = VK_NULL_HANDLE;
  	} pipelines;
  	VkPipelineLayout pipelineLayout;
  
  	struct DescriptorSetLayouts {
  		VkDescriptorSetLayout uniformbuffers;
  		VkDescriptorSetLayout images;
  	} descriptorSetLayouts;
  
  	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
  	{
  		title = "glTF model rendering";
  		camera.setType(Camera::CameraType::lookat);
  		camera.setFlipY(true);
  		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);
  	}
  
  	~VulkanExample()
  	{
  		if (device) {
  			delete glTFModel;
  			vkDestroyPipeline(device, pipelines.solid, nullptr);
  			if (pipelines.wireframe != VK_NULL_HANDLE) {
  				vkDestroyPipeline(device, pipelines.wireframe, nullptr);
  			}
  			vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
  			vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.uniformbuffers, nullptr);
  			vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.images, nullptr);
  			for (FrameObjects& frame : frameObjects) {
  				frame.uniformBuffer.destroy();
  				destroyBaseFrameObjects(frame);
  			}
  		}
  	}
  
  	virtual void getEnabledFeatures()
  	{
  		// Fill mode non solid is required for wireframe display
  		enabledFeatures.fillModeNonSolid = deviceFeatures.fillModeNonSolid;
  	}
  
  	void loadAssets()
  	{
  		glTFModel = new VulkanglTFModel(vulkanDevice, queue);
  		glTFModel->loadFromFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf");
  	}
  
  	void createDescriptors()
  	{
  		// This sample uses separate descriptor sets (and layouts) for the scene's matrices and materials (images)
  		// The uniform buffers for passing dynamic data like the matrices are duplicated per-frame, so we can have multiple frames in flight
  
  		// One ubo per frame to pass dynamic data to the shader
  		// One combined image sampler per material texture
  		std::vector<VkDescriptorPoolSize> poolSizes = {
  			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, getFrameCount()),
  			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(glTFModel->images.size())),
  		};
  		// One set for matrices and one set per scene image
  		const uint32_t maxSetCount = static_cast<uint32_t>(glTFModel->images.size()) + getFrameCount();
  		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount);
  		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
  
  		// 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.uniformbuffers));
  		// Descriptor set layout for passing material images
  		setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
  		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.images));
  
  		// Descriptor sets for scene matrices
  		for (FrameObjects& frame : frameObjects) {
  			VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.uniformbuffers, 1);
  			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &frame.descriptorSet));
  			VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(frame.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &frame.uniformBuffer.descriptor);
  			vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
  		}
  
  		// Descriptor sets for the glTF model's images
  		for (auto& image : glTFModel->images) {
  			const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.images, 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 createPipelines()
  	{
  		// Layout using both descriptor sets (set 0 = matrices, set 1 = material)
  		std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.uniformbuffers, descriptorSetLayouts.images };
  		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(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);
  		pipelineLayoutCI.pushConstantRangeCount = 1;
  		pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
  		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
  
  		// Setup vertex input bindings and attributes that match the vertex buffer layout of the glTF model
  		const VkVertexInputBindingDescription vertexInputBinding = vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX);
  		const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
  			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 = 1;
  		vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding;
  		vertexInputStateCI.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
  		vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data();
  
  		// Pipelines
  		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_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);
  		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
  		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
  		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
  		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
  		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
  
  		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<uint32_t>(shaderStages.size());
  		pipelineCI.pStages = shaderStages.data();
  
  		// Solid rendering pipeline
  		shaderStages[0] = loadShader(getShadersPath() + "gltfloading/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
  		shaderStages[1] = loadShader(getShadersPath() + "gltfloading/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
  		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
  
  		// Wire frame rendering pipeline (if supported)
  		if (deviceFeatures.fillModeNonSolid) {
  			rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
  			rasterizationStateCI.lineWidth = 1.0f;
  			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
  		}
  	}
  
  	void prepare()
  	{
  		VulkanExampleBase::prepare();
  		// Prepare per-frame ressources
  		frameObjects.resize(getFrameCount());
  		for (FrameObjects& frame : frameObjects) {
  			createBaseFrameObjects(frame);
  			// Uniform buffers
  			VK_CHECK_RESULT(vulkanDevice->createAndMapBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &frame.uniformBuffer, sizeof(UniformData)));
  		}
  		loadAssets();
  		createDescriptors();
  		createPipelines();
  		prepared = true;
  	}
  
  	virtual void render()
  	{
  		FrameObjects currentFrame = frameObjects[getCurrentFrameIndex()];
  
  		VulkanExampleBase::prepareFrame(currentFrame);
  
  		// Update uniform-buffers for the next frame
  		uniformData.projection = camera.matrices.perspective;
  		uniformData.model = camera.matrices.view;
  		memcpy(currentFrame.uniformBuffer.mapped, &uniformData, sizeof(UniformData));
  
  		// Build the command buffer
  		const VkCommandBuffer commandBuffer = currentFrame.commandBuffer;
  		const VkCommandBufferBeginInfo commandBufferBeginInfo = getCommandBufferBeginInfo();
  		const VkRect2D renderArea = getRenderArea();
  		const VkViewport viewport = getViewport();
  		const VkRenderPassBeginInfo renderPassBeginInfo = getRenderPassBeginInfo(renderPass, defaultClearValues);
  		VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo));
  		vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
  		vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
  		vkCmdSetScissor(commandBuffer, 0, 1, &renderArea);
  		vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid);
  		// Bind global scene matrices descriptor to set 0
  		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &currentFrame.descriptorSet, 0, nullptr);
  		// Draw the glTF model, this also takes care of binding image descriptors to set 1
  		glTFModel->draw(commandBuffer, pipelineLayout);
  
  		drawUI(commandBuffer);
  		vkCmdEndRenderPass(commandBuffer);
  		VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
  
  		VulkanExampleBase::submitFrame(currentFrame);
  	}
  
  	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
  	{
  		if (overlay->header("Settings")) {
  			overlay->checkBox("Wireframe", &wireframe);
  		}
  	}
  };
  
  VULKAN_EXAMPLE_MAIN()