diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6eb15f84..742edcba 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -139,6 +139,7 @@ set(EXAMPLES raytracingtextures renderheadless screenshot + shaderobjects shadowmapping shadowmappingomni shadowmappingcascade diff --git a/examples/shaderobjects/shaderobjects.cpp b/examples/shaderobjects/shaderobjects.cpp new file mode 100644 index 00000000..c37d09e9 --- /dev/null +++ b/examples/shaderobjects/shaderobjects.cpp @@ -0,0 +1,288 @@ +/* + * Vulkan Example - Using shader objects via VK_EXT_shader_object + * + * Copyright (C) 2023 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" + +#define ENABLE_VALIDATION false + +class VulkanExample: public VulkanExampleBase +{ +public: + vkglTF::Model scene; + + // Same uniform buffer layout as shader + struct UBOVS { + glm::mat4 projection; + glm::mat4 modelView; + glm::vec4 lightPos = glm::vec4(0.0f, 2.0f, 1.0f, 0.0f); + } uboVS; + vks::Buffer uniformBuffer; + + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSet; + VkDescriptorSetLayout descriptorSetLayout; + + VkShaderEXT shaders[2]; + + VkPhysicalDeviceShaderObjectFeaturesEXT enabledDeviceShaderObjectFeaturesEXT{}; + + PFN_vkCreateShadersEXT vkCreateShadersEXT; + PFN_vkCmdBindShadersEXT vkCmdBindShadersEXT; + // With VK_EXT_shader_object pipeline state must be set at command buffer creation using these functions + PFN_vkCmdSetViewportWithCount vkCmdSetViewportWithCount; + PFN_vkCmdSetScissorWithCount vkCmdSetScissorWithCount; + PFN_vkCmdSetPolygonModeEXT vkCmdSetPolygonModeEXT; + PFN_vkCmdSetDepthCompareOp vkCmdSetDepthCompareOp; + PFN_vkCmdSetCullMode vkCmdSetCullMode; + PFN_vkCmdSetDepthTestEnable vkCmdSetDepthTestEnable; + PFN_vkCmdSetDepthWriteEnable vkCmdSetDepthWriteEnable; + PFN_vkCmdSetFrontFace vkCmdSetFrontFace; + PFN_vkCmdSetPrimitiveTopology vkCmdSetPrimitiveTopology; + PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Shader objects (VK_EXT_shader_object)"; + camera.type = Camera::CameraType::lookat; + camera.setPosition(glm::vec3(0.0f, 0.0f, -10.5f)); + camera.setRotation(glm::vec3(-25.0f, 15.0f, 0.0f)); + camera.setRotationSpeed(0.5f); + camera.setPerspective(60.0f, (float)(width) / (float)height, 0.1f, 256.0f); + + enabledDeviceExtensions.push_back(VK_EXT_SHADER_OBJECT_EXTENSION_NAME); + + enabledDeviceShaderObjectFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; + enabledDeviceShaderObjectFeaturesEXT.shaderObject = VK_TRUE; + + deviceCreatepNextChain = &enabledDeviceShaderObjectFeaturesEXT; + } + + ~VulkanExample() + { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + // @todo: destroy shaders + uniformBuffer.destroy(); + } + + void loadAssets() + { + const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY; + scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags); + } + + void setupDescriptors() + { + // Pool + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1) + }; + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + // Layout + std::vector setLayoutBindings = { + // Binding 0 : Vertex shader uniform buffer + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0) + }; + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); + // Sets + 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, &uniformBuffer.descriptor) + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + } + + void _loadShader(std::string filename, char* &code, size_t &size) { + // @todo: Android + std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate); + if (is.is_open()) + { + size = is.tellg(); + is.seekg(0, std::ios::beg); + code = new char[size]; + is.read(code, size); + is.close(); + assert(size > 0); + } + else + { + vks::tools::exitFatal("Error: Could not open shader " + filename, VK_ERROR_UNKNOWN); + } + } + + void createShaderObjects() + { + size_t shaderCodeSizes[2]{}; + char* shaderCodes[2]{}; + + VkShaderCreateInfoEXT shaderCreateInfos[2]{}; + + // VS + _loadShader(getShadersPath() + "pipelines/phong.vert.spv", shaderCodes[0], shaderCodeSizes[0]); + shaderCreateInfos[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; + shaderCreateInfos[0].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; + shaderCreateInfos[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderCreateInfos[0].nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; + shaderCreateInfos[0].codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfos[0].pCode = shaderCodes[0]; + shaderCreateInfos[0].codeSize = shaderCodeSizes[0]; + shaderCreateInfos[0].pName = "main"; + shaderCreateInfos[0].setLayoutCount = 1; + shaderCreateInfos[0].pSetLayouts = &descriptorSetLayout; + + // FS + _loadShader(getShadersPath() + "pipelines/phong.frag.spv", shaderCodes[1], shaderCodeSizes[1]); + shaderCreateInfos[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; + shaderCreateInfos[1].flags = VK_SHADER_CREATE_LINK_STAGE_BIT_EXT; + shaderCreateInfos[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + shaderCreateInfos[1].nextStage = 0; + shaderCreateInfos[1].codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; + shaderCreateInfos[1].pCode = shaderCodes[1]; + shaderCreateInfos[1].codeSize = shaderCodeSizes[1]; + shaderCreateInfos[1].pName = "main"; + shaderCreateInfos[1].setLayoutCount = 1; + shaderCreateInfos[1].pSetLayouts = &descriptorSetLayout; + + vkCreateShadersEXT(device, 2, shaderCreateInfos, nullptr, shaders); + } + + 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) + { + 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); + VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); + + // No more pipelines required, everything is bound at command buffer level + + vkCmdSetViewportWithCount(drawCmdBuffers[i], 1, &viewport); + vkCmdSetScissorWithCount(drawCmdBuffers[i], 1, &scissor); + vkCmdSetCullMode(drawCmdBuffers[i], VK_CULL_MODE_BACK_BIT); + vkCmdSetFrontFace(drawCmdBuffers[i], VK_FRONT_FACE_COUNTER_CLOCKWISE); + vkCmdSetPolygonModeEXT(drawCmdBuffers[i], VK_POLYGON_MODE_FILL); + vkCmdSetDepthTestEnable(drawCmdBuffers[i], VK_TRUE); + vkCmdSetDepthWriteEnable(drawCmdBuffers[i], VK_TRUE); + vkCmdSetDepthCompareOp(drawCmdBuffers[i], VK_COMPARE_OP_LESS_OR_EQUAL); + vkCmdSetPrimitiveTopology(drawCmdBuffers[i], VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + + VkVertexInputBindingDescription2EXT vertexInputBinding{}; + vertexInputBinding.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; + vertexInputBinding.binding = 0; + vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + vertexInputBinding.stride = sizeof(vkglTF::Vertex); + + std::vector vertexAttributes = { + { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, pos) }, + { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, normal) }, + { VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT, nullptr, 2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(vkglTF::Vertex, color) } + }; + + vkCmdSetVertexInputEXT(drawCmdBuffers[i], 1, &vertexInputBinding, 3, vertexAttributes.data()); + + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + scene.bindBuffers(drawCmdBuffers[i]); + + // Binding the shaders + VkShaderStageFlagBits stages[2] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT }; + vkCmdBindShadersEXT(drawCmdBuffers[i], 2, stages, shaders); + scene.draw(drawCmdBuffers[i]); + + drawUI(drawCmdBuffers[i]); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + // Create the 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, &uniformBuffer, sizeof(uboVS))); + VK_CHECK_RESULT(uniformBuffer.map()); + updateUniformBuffers(); + } + + void updateUniformBuffers() + { + uboVS.projection = camera.matrices.perspective; + uboVS.modelView = camera.matrices.view; + memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS)); + } + + 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(); + + vkCreateShadersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCreateShadersEXT")); + vkCmdBindShadersEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindShadersEXT")); + + vkCmdSetViewportWithCount = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetViewportWithCountEXT"));; + vkCmdSetScissorWithCount = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetScissorWithCountEXT")); + vkCmdSetPolygonModeEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetPolygonModeEXT")); + vkCmdSetDepthCompareOp = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthCompareOp")); + vkCmdSetCullMode = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetCullModeEXT")); + vkCmdSetDepthTestEnable = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthTestEnableEXT")); + vkCmdSetDepthWriteEnable = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetDepthWriteEnableEXT")); + vkCmdSetFrontFace = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetFrontFaceEXT")); + vkCmdSetPrimitiveTopology = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetPrimitiveTopologyEXT")); + vkCmdSetVertexInputEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdSetVertexInputEXT")); + + loadAssets(); + prepareUniformBuffers(); + setupDescriptors(); + createShaderObjects(); + buildCommandBuffers(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + updateUniformBuffers(); + } +}; + +VULKAN_EXAMPLE_MAIN()