text: /*
  * Vulkan Example - Texture arrays and instanced rendering
  *
  * Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
  *
  * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
  */
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <assert.h>
  #include <time.h> 
  #include <vector>
  
  #define GLM_FORCE_RADIANS
  #define GLM_FORCE_DEPTH_ZERO_TO_ONE
  #include <glm/glm.hpp>
  #include <glm/gtc/matrix_transform.hpp>
  
  #include <vulkan/vulkan.h>
  #include "vulkanexamplebase.h"
  #include "VulkanTexture.hpp"
  #include "VulkanBuffer.hpp"
  
  #define VERTEX_BUFFER_BIND_ID 0
  #define ENABLE_VALIDATION false
  
  // Vertex layout for this example
  struct Vertex {
  	float pos[3];
  	float uv[2];
  };
  
  class VulkanExample : public VulkanExampleBase
  {
  public:
  	// Number of array layers in texture array
  	// Also used as instance count
  	uint32_t layerCount;
  	vks::Texture textureArray;
  
  	struct {
  		VkPipelineVertexInputStateCreateInfo inputState;
  		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
  		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
  	} vertices;
  
  	vks::Buffer vertexBuffer;
  	vks::Buffer indexBuffer;
  	uint32_t indexCount;
  
  	vks::Buffer uniformBufferVS;
  
  	struct UboInstanceData {
  		// Model matrix
  		glm::mat4 model;
  		// Texture array index
  		// Vec4 due to padding
  		glm::vec4 arrayIndex;
  	};
  
  	struct {
  		// Global matrices
  		struct {
  			glm::mat4 projection;
  			glm::mat4 view;
  		} matrices;
  		// Seperate data for each instance
  		UboInstanceData *instance;		
  	} uboVS;
  
  
  	VkPipeline pipeline;
  	VkPipelineLayout pipelineLayout;
  	VkDescriptorSet descriptorSet;
  	VkDescriptorSetLayout descriptorSetLayout;
  
  	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
  	{
  		zoom = -15.0f;
  		rotationSpeed = 0.25f;
  		rotation = { -15.0f, 35.0f, 0.0f };
  		title = "Texture arrays";
  		settings.overlay = true;
  	}
  
  	~VulkanExample()
  	{
  		// Clean up used Vulkan resources 
  		// Note : Inherited destructor cleans up resources stored in base class
  
  		// Clean up texture resources
  		vkDestroyImageView(device, textureArray.view, nullptr);
  		vkDestroyImage(device, textureArray.image, nullptr);
  		vkDestroySampler(device, textureArray.sampler, nullptr);
  		vkFreeMemory(device, textureArray.deviceMemory, nullptr);
  
  		vkDestroyPipeline(device, pipeline, nullptr);
  
  		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
  		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
  
  		vertexBuffer.destroy();
  		indexBuffer.destroy();
  
  		uniformBufferVS.destroy();
  
  		delete[] uboVS.instance;
  	}
  
  	void loadTextureArray(std::string filename, VkFormat format)
  	{
  #if defined(__ANDROID__)
  		// Textures are stored inside the apk on Android (compressed)
  		// So they need to be loaded via 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 *textureData = malloc(size);
  		AAsset_read(asset, textureData, size);
  		AAsset_close(asset);
  
  		gli::texture2d_array tex2DArray(gli::load((const char*)textureData, size));
  #else
  		gli::texture2d_array tex2DArray(gli::load(filename));
  #endif
  
  		assert(!tex2DArray.empty());
  
  		textureArray.width = tex2DArray.extent().x;
  		textureArray.height = tex2DArray.extent().y;
  		layerCount = tex2DArray.layers();
  
  		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
  		VkMemoryRequirements memReqs;
  
  		// Create a host-visible staging buffer that contains the raw image data
  		VkBuffer stagingBuffer;
  		VkDeviceMemory stagingMemory;
  
  		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
  		bufferCreateInfo.size = tex2DArray.size();
  		// This buffer is used as a transfer source for the buffer copy
  		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
  		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
  		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
  
  		// Get memory requirements for the staging buffer (alignment, memory type bits)
  		vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
  
  		memAllocInfo.allocationSize = memReqs.size;
  		// Get memory type index for a host visible buffer
  		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
  
  		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
  		VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
  
  		// Copy texture data into staging buffer
  		uint8_t *data;
  		VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
  		memcpy(data, tex2DArray.data(), tex2DArray.size());
  		vkUnmapMemory(device, stagingMemory);
  
  		// Setup buffer copy regions for array layers
  		std::vector<VkBufferImageCopy> bufferCopyRegions;
  		size_t offset = 0;
  
  		for (uint32_t layer = 0; layer < layerCount; layer++)
  		{
  			VkBufferImageCopy bufferCopyRegion = {};
  			bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  			bufferCopyRegion.imageSubresource.mipLevel = 0;
  			bufferCopyRegion.imageSubresource.baseArrayLayer = layer;
  			bufferCopyRegion.imageSubresource.layerCount = 1;
  			bufferCopyRegion.imageExtent.width = static_cast<uint32_t>(tex2DArray[layer][0].extent().x);
  			bufferCopyRegion.imageExtent.height = static_cast<uint32_t>(tex2DArray[layer][0].extent().y);
  			bufferCopyRegion.imageExtent.depth = 1;
  			bufferCopyRegion.bufferOffset = offset;
  
  			bufferCopyRegions.push_back(bufferCopyRegion);
  
  			// Increase offset into staging buffer for next level / face
  			offset += tex2DArray[layer][0].size();
  		}
  
  		// Create optimal tiled target image
  		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
  		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
  		imageCreateInfo.format = format;
  		imageCreateInfo.mipLevels = 1;
  		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
  		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
  		imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
  		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  		imageCreateInfo.extent = { textureArray.width, textureArray.height, 1 };
  		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
  		imageCreateInfo.arrayLayers = layerCount;
  
  		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &textureArray.image));
  
  		vkGetImageMemoryRequirements(device, textureArray.image, &memReqs);
  
  		memAllocInfo.allocationSize = memReqs.size;
  		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
  
  		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &textureArray.deviceMemory));
  		VK_CHECK_RESULT(vkBindImageMemory(device, textureArray.image, textureArray.deviceMemory, 0));
  
  		VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
  
  		// Image barrier for optimal image (target)
  		// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
  		VkImageSubresourceRange subresourceRange = {};
  		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  		subresourceRange.baseMipLevel = 0;
  		subresourceRange.levelCount = 1;
  		subresourceRange.layerCount = layerCount;
  
  		vks::tools::setImageLayout(
  			copyCmd,
  			textureArray.image,
  			VK_IMAGE_LAYOUT_UNDEFINED,
  			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
  			subresourceRange);
  
  		// Copy the cube map faces from the staging buffer to the optimal tiled image
  		vkCmdCopyBufferToImage(
  			copyCmd,
  			stagingBuffer,
  			textureArray.image,
  			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
  			bufferCopyRegions.size(),
  			bufferCopyRegions.data()
  			);
  
  		// Change texture image layout to shader read after all faces have been copied
  		textureArray.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
  		vks::tools::setImageLayout(
  			copyCmd,
  			textureArray.image,
  			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
  			textureArray.imageLayout,
  			subresourceRange);
  
  		VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true);
  
  		// Create sampler
  		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
  		sampler.magFilter = VK_FILTER_LINEAR;
  		sampler.minFilter = VK_FILTER_LINEAR;
  		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
  		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
  		sampler.addressModeV = sampler.addressModeU;
  		sampler.addressModeW = sampler.addressModeU;
  		sampler.mipLodBias = 0.0f;
  		sampler.maxAnisotropy = 8;
  		sampler.compareOp = VK_COMPARE_OP_NEVER;
  		sampler.minLod = 0.0f;
  		sampler.maxLod = 0.0f;
  		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
  		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &textureArray.sampler));
  
  		// Create image view
  		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
  		view.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
  		view.format = format;
  		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
  		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
  		view.subresourceRange.layerCount = layerCount;
  		view.subresourceRange.levelCount = 1;
  		view.image = textureArray.image;
  		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &textureArray.view));
  
  		// Clean up staging resources
  		vkFreeMemory(device, stagingMemory, nullptr);
  		vkDestroyBuffer(device, stagingBuffer, nullptr);
  	}
  
  	void loadTextures()
  	{
  		// Vulkan core supports three different compressed texture formats
  		// As the support differs between implemementations we need to check device features and select a proper format and file
  		std::string filename;
  		VkFormat format;
  		if (deviceFeatures.textureCompressionBC) {
  			filename = "texturearray_bc3_unorm.ktx";
  			format = VK_FORMAT_BC3_UNORM_BLOCK;
  		}
  		else if (deviceFeatures.textureCompressionASTC_LDR) {
  			filename = "texturearray_astc_8x8_unorm.ktx";
  			format = VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
  		}
  		else if (deviceFeatures.textureCompressionETC2) {
  			filename = "texturearray_etc2_unorm.ktx";
  			format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
  		}
  		else {
  			vks::tools::exitFatal("Device does not support any compressed texture format!", VK_ERROR_FEATURE_NOT_PRESENT);
  		}
  		loadTextureArray(getAssetPath() + "textures/" + filename, format);
  	}
  
  	void buildCommandBuffers()
  	{
  		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
  
  		VkClearValue clearValues[2];
  		clearValues[0].color = defaultClearColor;
  		clearValues[1].depthStencil = { 1.0f, 0 };
  
  		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
  		renderPassBeginInfo.renderPass = renderPass;
  		renderPassBeginInfo.renderArea.offset.x = 0;
  		renderPassBeginInfo.renderArea.offset.y = 0;
  		renderPassBeginInfo.renderArea.extent.width = width;
  		renderPassBeginInfo.renderArea.extent.height = height;
  		renderPassBeginInfo.clearValueCount = 2;
  		renderPassBeginInfo.pClearValues = clearValues;
  
  		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);
  			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
  
  			VkDeviceSize offsets[1] = { 0 };
  			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
  			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
  
  			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, layerCount, 0, 0, 0);
  
  			vkCmdEndRenderPass(drawCmdBuffers[i]);
  
  			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
  		}
  	}
  
  	void generateQuad()
  	{
  		// Setup vertices for a single uv-mapped quad made from two triangles
  		std::vector<Vertex> vertices =
  		{
  			{ {  2.5f,  2.5f, 0.0f }, { 1.0f, 1.0f } },
  			{ { -2.5f,  2.5f, 0.0f }, { 0.0f, 1.0f } },
  			{ { -2.5f, -2.5f, 0.0f }, { 0.0f, 0.0f } },
  			{ {  2.5f, -2.5f, 0.0f }, { 1.0f, 0.0f } }
  		};
  
  		// Setup indices
  		std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
  		indexCount = static_cast<uint32_t>(indices.size());
  
  		// Create buffers
  		// For the sake of simplicity we won't stage the vertex data to the gpu memory
  		// Vertex buffer
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
  			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
  			&vertexBuffer,
  			vertices.size() * sizeof(Vertex),
  			vertices.data()));
  		// Index buffer
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
  			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
  			&indexBuffer,
  			indices.size() * sizeof(uint32_t),
  			indices.data()));
  	}
  
  	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(2);
  		// Location 0 : Position
  		vertices.attributeDescriptions[0] =
  			vks::initializers::vertexInputAttributeDescription(
  				VERTEX_BUFFER_BIND_ID,
  				0,
  				VK_FORMAT_R32G32B32_SFLOAT,
  				0);
  		// Location 1 : Texture coordinates
  		vertices.attributeDescriptions[1] =
  			vks::initializers::vertexInputAttributeDescription(
  				VERTEX_BUFFER_BIND_ID,
  				1,
  				VK_FORMAT_R32G32_SFLOAT,
  				sizeof(float) * 3);
  
  		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
  		vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size();
  		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
  		vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size();
  		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
  	}
  
  	void setupDescriptorPool()
  	{
  		std::vector<VkDescriptorPoolSize> poolSizes =
  		{
  			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
  			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
  		};
  
  		VkDescriptorPoolCreateInfo descriptorPoolInfo =
  			vks::initializers::descriptorPoolCreateInfo(
  				poolSizes.size(),
  				poolSizes.data(),
  				2);
  
  		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
  	}
  
  	void setupDescriptorSetLayout()
  	{
  		std::vector<VkDescriptorSetLayoutBinding> 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 image sampler (texture array)
  			vks::initializers::descriptorSetLayoutBinding(
  				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
  				VK_SHADER_STAGE_FRAGMENT_BIT,
  				1)
  		};
  
  		VkDescriptorSetLayoutCreateInfo descriptorLayout =
  			vks::initializers::descriptorSetLayoutCreateInfo(
  				setLayoutBindings.data(),
  				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);
  
  		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
  
  		// Image descriptor for the texture array
  		VkDescriptorImageInfo textureDescriptor =
  			vks::initializers::descriptorImageInfo(
  				textureArray.sampler,
  				textureArray.view,
  				textureArray.imageLayout);
  
  		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
  		{
  			// Binding 0 : Vertex shader uniform buffer
  			vks::initializers::writeDescriptorSet(
  				descriptorSet,
  				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
  				0,
  				&uniformBufferVS.descriptor),
  			// Binding 1 : Fragment shader cubemap sampler
  			vks::initializers::writeDescriptorSet(
  				descriptorSet,
  				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
  				1,
  				&textureDescriptor)
  		};
  
  		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
  	}
  
  	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_NONE,
  				VK_FRONT_FACE_COUNTER_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<VkDynamicState> dynamicStateEnables = {
  			VK_DYNAMIC_STATE_VIEWPORT,
  			VK_DYNAMIC_STATE_SCISSOR
  		};
  		VkPipelineDynamicStateCreateInfo dynamicState =
  			vks::initializers::pipelineDynamicStateCreateInfo(
  				dynamicStateEnables.data(),
  				dynamicStateEnables.size(),
  				0);
  
  		// Instacing pipeline
  		// Load shaders
  		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
  
  		shaderStages[0] = loadShader(getAssetPath() + "shaders/texturearray/instancing.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
  		shaderStages[1] = loadShader(getAssetPath() + "shaders/texturearray/instancing.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 = shaderStages.size();
  		pipelineCreateInfo.pStages = shaderStages.data();
  
  		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
  	}
  
  	void prepareUniformBuffers()
  	{
  		uboVS.instance = new UboInstanceData[layerCount];
  
  		uint32_t uboSize = sizeof(uboVS.matrices) + (layerCount * sizeof(UboInstanceData));
  
  		// Vertex shader uniform buffer block
  		VK_CHECK_RESULT(vulkanDevice->createBuffer(
  			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
  			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
  			&uniformBufferVS,
  			uboSize));
  
  		// Array indices and model matrices are fixed
  		float offset = -1.5f;
  		float center = (layerCount*offset) / 2;
  		for (int32_t i = 0; i < layerCount; i++)
  		{
  			// Instance model matrix
  			uboVS.instance[i].model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, i * offset - center, 0.0f));
  			uboVS.instance[i].model = glm::rotate(uboVS.instance[i].model, glm::radians(60.0f), glm::vec3(1.0f, 0.0f, 0.0f));
  			// Instance texture array index
  			uboVS.instance[i].arrayIndex.x = i;
  		}
  
  		// Update instanced part of the uniform buffer
  		uint8_t *pData;
  		uint32_t dataOffset = sizeof(uboVS.matrices);
  		uint32_t dataSize = layerCount * sizeof(UboInstanceData);
  		VK_CHECK_RESULT(vkMapMemory(device, uniformBufferVS.memory, dataOffset, dataSize, 0, (void **)&pData));
  		memcpy(pData, uboVS.instance, dataSize);
  		vkUnmapMemory(device, uniformBufferVS.memory);
  
  		// Map persistent
  		VK_CHECK_RESULT(uniformBufferVS.map());
  
  		updateUniformBufferMatrices();
  	}
  
  	void updateUniformBufferMatrices()
  	{
  		// Only updates the uniform buffer block part containing the global matrices
  
  		// Projection
  		uboVS.matrices.projection = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.001f, 256.0f);
  
  		// View
  		uboVS.matrices.view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, -1.0f, zoom));
  		uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
  		uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
  		uboVS.matrices.view = glm::rotate(uboVS.matrices.view, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
  
  		// Only update the matrices part of the uniform buffer
  		memcpy(uniformBufferVS.mapped, &uboVS.matrices, sizeof(uboVS.matrices));
  	}
  
  	void draw()
  	{
  		VulkanExampleBase::prepareFrame();
  
  		submitInfo.commandBufferCount = 1;
  		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
  		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
  
  		VulkanExampleBase::submitFrame();
  	}
  
  	void prepare()
  	{
  		VulkanExampleBase::prepare();
  		loadTextures();
  		setupVertexDescriptions();
  		generateQuad();
  		prepareUniformBuffers();
  		setupDescriptorSetLayout();
  		preparePipelines();
  		setupDescriptorPool();
  		setupDescriptorSet();
  		buildCommandBuffers();
  		prepared = true;
  	}
  
  	virtual void render()
  	{
  		if (!prepared)
  			return;
  		draw();
  	}
  
  	virtual void viewChanged()
  	{
  		updateUniformBufferMatrices();
  	}
  };
  
  VULKAN_EXAMPLE_MAIN()