/* * Vulkan Example - Model loading and rendering * * 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 #include #include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #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 ENABLE_VALIDATION false 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; glm::vec2 uv; 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; } 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; 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 Pipelines { VkPipeline solid; VkPipeline wireframe = VK_NULL_HANDLE; } pipelines; VkPipelineLayout pipelineLayout; VkDescriptorSet descriptorSet; VkDescriptorSetLayout descriptorSetLayout; 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 = "Model rendering"; settings.overlay = true; } ~VulkanExample() { // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class vkDestroyPipeline(device, pipelines.solid, nullptr); if (pipelines.wireframe != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipelines.wireframe, nullptr); } vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); model.destroy(device); textures.colorMap.destroy(); uniformBuffers.scene.destroy(); } virtual void getEnabledFeatures() { // Fill mode non solid is required for wireframe display if (deviceFeatures.fillModeNonSolid) { enabledFeatures.fillModeNonSolid = VK_TRUE; }; } 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, 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])); } } void drawglTFNode(VkCommandBuffer commandBuffer, ModelNode node) { if (node.indexCount > 0) { vkCmdDrawIndexed(commandBuffer, node.indexCount, 1, node.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, &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); } } /* 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__) // 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); char* fileData = new char[size]; AAsset_read(asset, fileData, size); AAsset_close(asset); std::string baseDir; bool fileLoaded = gltfContext.LoadASCIIFromString(&gltfModel, &error, &warning, fileData, size, baseDir); free(fileData); #else bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename); #endif std::vector indexBuffer; std::vector vertexBuffer; 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); size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); model.indices.count = static_cast(indexBuffer.size()); //assert((vertexBufferSize > 0) && (indexBufferSize > 0)); struct StagingBuffer { 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 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); VkBufferCopy copyRegion = {}; copyRegion.size = vertexBufferSize; vkCmdCopyBuffer( copyCmd, vertexStaging.buffer, model.vertices.buffer, 1, ©Region); copyRegion.size = indexBufferSize; vkCmdCopyBuffer( copyCmd, indexStaging.buffer, model.indices.buffer, 1, ©Region); 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); } 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() { // Example uses one ubo and one combined image sampler std::vector 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( static_cast(poolSizes.size()), poolSizes.data(), 1); 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), }; 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); 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); } 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); 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), }; 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 VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid)); // Wire frame rendering pipeline if (deviceFeatures.fillModeNonSolid) { rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE; rasterizationStateCI.lineWidth = 1.0f; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe)); } } // Prepare and initialize uniform buffer containing shader uniforms void prepareUniformBuffers() { // 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, &uniformBuffers.scene, sizeof(uboVS))); // Map persistent VK_CHECK_RESULT(uniformBuffers.scene.map()); updateUniformBuffers(); } 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)); 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(); loadAssets(); prepareUniformBuffers(); setupDescriptorSetLayout(); preparePipelines(); setupDescriptorPool(); setupDescriptorSet(); buildCommandBuffers(); prepared = true; } virtual void render() { if (!prepared) return; draw(); if (camera.updated) updateUniformBuffers(); } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Settings")) { if (overlay->checkBox("Wireframe", &wireframe)) { buildCommandBuffers(); } } } }; VULKAN_EXAMPLE_MAIN()