diff --git a/README.md b/README.md
index c2b3f7d9..cca56828 100644
--- a/README.md
+++ b/README.md
@@ -439,6 +439,10 @@ Shows usage of the VK_KHR_dynamic_rendering extension, which simplifies the rend
#### [Graphics pipeline library (VK_EXT_graphics_pipeline_library)](./examples/graphicspipelinelibrary)
Uses the graphics pipeline library extensions to improve run-time pipeline creation. Instead of creating the whole pipeline at once, this sample pre builds shared pipeline parts like like vertex input state and fragment output state. These are then used to create full pipelines at runtime, reducing build times and possible hick-ups.
+#### [Mesh shaders (VK_EXT_mesh_shader)](./examples/meshshaders)
+
+Basic sample demonstrating how to use the mesh shading pipeline as a replacement for the traditional vertex pipeline.
+
### Misc
#### [Vulkan Gears](examples/gears/)
diff --git a/data/shaders/glsl/meshshader/meshshader.frag b/data/shaders/glsl/meshshader/meshshader.frag
new file mode 100644
index 00000000..efb12b1c
--- /dev/null
+++ b/data/shaders/glsl/meshshader/meshshader.frag
@@ -0,0 +1,19 @@
+/* Copyright (c) 2021, Sascha Willems
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ */
+
+#version 450
+
+layout (location = 0) in VertexInput {
+ vec4 color;
+} vertexInput;
+
+layout(location = 0) out vec4 FragColor;
+
+
+void main()
+{
+ FragColor = vertexInput.color;
+}
\ No newline at end of file
diff --git a/data/shaders/glsl/meshshader/meshshader.frag.spv b/data/shaders/glsl/meshshader/meshshader.frag.spv
new file mode 100644
index 00000000..115402ae
Binary files /dev/null and b/data/shaders/glsl/meshshader/meshshader.frag.spv differ
diff --git a/data/shaders/glsl/meshshader/meshshader.mesh b/data/shaders/glsl/meshshader/meshshader.mesh
new file mode 100644
index 00000000..d75f7dba
--- /dev/null
+++ b/data/shaders/glsl/meshshader/meshshader.mesh
@@ -0,0 +1,36 @@
+/* Copyright (c) 2021, Sascha Willems
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ */
+
+#version 450
+#extension GL_EXT_mesh_shader : require
+
+layout (binding = 0) uniform UBO
+{
+ mat4 projection;
+ mat4 model;
+ mat4 view;
+} ubo;
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+layout(triangles, max_vertices = 3, max_primitives = 1) out;
+
+layout(location = 0) out VertexOutput
+{
+ vec4 color;
+} vertexOutput[];
+
+void main()
+{
+ SetMeshOutputsEXT(3, 1);
+ mat4 mvp = ubo.projection * ubo.view * ubo.model;
+ gl_MeshVerticesEXT[0].gl_Position = mvp * vec4( 0.0, -1.0, 0.0, 1.0);
+ gl_MeshVerticesEXT[1].gl_Position = mvp * vec4(-1.0, 1.0, 0.0, 1.0);
+ gl_MeshVerticesEXT[2].gl_Position = mvp * vec4( 1.0, 1.0, 0.0, 1.0);
+ vertexOutput[0].color = vec4(0.0, 1.0, 0.0, 1.0);
+ vertexOutput[1].color = vec4(0.0, 0.0, 1.0, 1.0);
+ vertexOutput[2].color = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0, 1, 2);
+}
diff --git a/data/shaders/glsl/meshshader/meshshader.mesh.spv b/data/shaders/glsl/meshshader/meshshader.mesh.spv
new file mode 100644
index 00000000..d68603c6
Binary files /dev/null and b/data/shaders/glsl/meshshader/meshshader.mesh.spv differ
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index d24c6fc0..0e8a1271 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -111,6 +111,7 @@ set(EXAMPLES
inlineuniformblocks
inputattachments
instancing
+ meshshader
multisampling
multithreading
multiview
diff --git a/examples/meshshader/meshshader.cpp b/examples/meshshader/meshshader.cpp
new file mode 100644
index 00000000..e8667ceb
--- /dev/null
+++ b/examples/meshshader/meshshader.cpp
@@ -0,0 +1,238 @@
+/*
+ * Vulkan Example - Using mesh shaders
+ *
+ * Copyright (C) 2022 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include "meshoptimizer/meshoptimizer.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+ struct UniformData {
+ glm::mat4 projection;
+ glm::mat4 model;
+ glm::mat4 view;
+ } uniformData;
+ vks::Buffer uniformBuffer;
+
+ uint32_t indexCount;
+
+ VkPipeline pipeline;
+ VkPipelineLayout pipelineLayout;
+ VkDescriptorSet descriptorSet;
+ VkDescriptorSetLayout descriptorSetLayout;
+
+ PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXT;
+
+ VkPhysicalDeviceMeshShaderFeaturesEXT enabledMeshShaderFeatures{};
+
+ VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+ {
+ title = "Mesh shaders";
+ timerSpeed *= 0.25f;
+ camera.type = Camera::CameraType::lookat;
+ camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+ camera.setRotation(glm::vec3(0.0f));
+ camera.setTranslation(glm::vec3(0.0f, 0.0f, -2.0f));
+
+ // Extension require at least Vulkan 1.1
+ apiVersion = VK_API_VERSION_1_1;
+
+ // Extensions required by mesh shading
+ enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+ enabledDeviceExtensions.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME);
+ enabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
+
+ // Required by VK_KHR_spirv_1_4
+ enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
+ }
+
+ ~VulkanExample()
+ {
+ vkDestroyPipeline(device, pipeline, nullptr);
+ vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+ vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+ uniformBuffer.destroy();
+ }
+
+ void getEnabledFeatures()
+ {
+ enabledMeshShaderFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_NV;
+ enabledMeshShaderFeatures.meshShader = VK_TRUE;
+ enabledMeshShaderFeatures.taskShader = VK_FALSE;
+
+ deviceCreatepNextChain = &enabledMeshShaderFeatures;
+ }
+
+
+ void buildCommandBuffers()
+ {
+ VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+ VkClearValue clearValues[2];
+ clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };;
+ 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)
+ {
+ 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);
+
+ VkDeviceSize offsets[1] = { 0 };
+
+ vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+ vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ vkCmdDrawMeshTasksEXT(drawCmdBuffers[i], 1, 1, 1);
+
+ drawUI(drawCmdBuffers[i]);
+
+ vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+ VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+ }
+ }
+
+ void setupDescriptors()
+ {
+ // Pool
+ std::vector poolSizes = {
+ vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+ };
+ VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 1);
+ VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+ // Layout
+ std::vector setLayoutBindings = {
+ vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_MESH_BIT_EXT, 0),
+ };
+ VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+ VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayout));
+
+ // Set
+ VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+ VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+ std::vector modelWriteDescriptorSets = {
+ vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+ };
+ vkUpdateDescriptorSets(device, static_cast(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, nullptr);
+ }
+
+ void preparePipelines()
+ {
+ // Layout
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+ VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
+
+ // Pipeline
+ 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_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 };
+ VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+ std::array shaderStages;
+
+ VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+
+ // Mesh shading doesn't require vertex input state
+ pipelineCI.pInputAssemblyState = nullptr;
+ pipelineCI.pVertexInputState = nullptr;
+
+ pipelineCI.pRasterizationState = &rasterizationState;
+ pipelineCI.pColorBlendState = &colorBlendState;
+ pipelineCI.pMultisampleState = &multisampleState;
+ pipelineCI.pViewportState = &viewportState;
+ pipelineCI.pDepthStencilState = &depthStencilState;
+ pipelineCI.pDynamicState = &dynamicState;
+ pipelineCI.stageCount = static_cast(shaderStages.size());
+ pipelineCI.pStages = shaderStages.data();
+
+ shaderStages[0] = loadShader(getShadersPath() + "meshshader/meshshader.mesh.spv", VK_SHADER_STAGE_MESH_BIT_EXT);
+ shaderStages[1] = loadShader(getShadersPath() + "meshshader/meshshader.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+ VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+ }
+
+ // Prepare and initialize uniform buffer containing shader uniforms
+ void prepareUniformBuffers()
+ {
+ VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffer, sizeof(UniformData)));
+ VK_CHECK_RESULT(uniformBuffer.map());
+ updateUniformBuffers();
+ }
+
+ void updateUniformBuffers()
+ {
+ uniformData.projection = camera.matrices.perspective;
+ uniformData.view = camera.matrices.view;
+ uniformData.model = glm::mat4(1.0f);
+ memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData));
+ }
+
+ 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();
+
+ // Get the function pointer of the mesh shader drawing funtion
+ vkCmdDrawMeshTasksEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdDrawMeshTasksEXT"));
+
+ prepareUniformBuffers();
+ setupDescriptors();
+ preparePipelines();
+ buildCommandBuffers();
+ prepared = true;
+ }
+
+ virtual void render()
+ {
+ if (!prepared)
+ return;
+ draw();
+ }
+
+ virtual void viewChanged()
+ {
+ updateUniformBuffers();
+ }
+};
+
+VULKAN_EXAMPLE_MAIN()