diff --git a/data/shaders/negativeviewportheight/quad.frag b/data/shaders/negativeviewportheight/quad.frag new file mode 100644 index 00000000..e0340e54 --- /dev/null +++ b/data/shaders/negativeviewportheight/quad.frag @@ -0,0 +1,12 @@ +#version 450 + +layout (binding = 0) uniform sampler2D samplerColor; + +layout (location = 0) in vec2 inUV; +layout (location = 0) out vec4 outColor; + +void main() +{ + outColor = texture(samplerColor, inUV); + outColor.rgb *= vec3(inUV, 0.0f); +} \ No newline at end of file diff --git a/data/shaders/negativeviewportheight/quad.frag.spv b/data/shaders/negativeviewportheight/quad.frag.spv new file mode 100644 index 00000000..836ff5b4 Binary files /dev/null and b/data/shaders/negativeviewportheight/quad.frag.spv differ diff --git a/data/shaders/negativeviewportheight/quad.vert b/data/shaders/negativeviewportheight/quad.vert new file mode 100644 index 00000000..1f3ad5a6 --- /dev/null +++ b/data/shaders/negativeviewportheight/quad.vert @@ -0,0 +1,14 @@ +#version 450 + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inUV; + +layout (location = 0) out vec2 outUV; + +void main() +{ +// outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); +// gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); + outUV = inUV; + gl_Position = vec4(inPos, 1.0f); +} diff --git a/data/shaders/negativeviewportheight/quad.vert.spv b/data/shaders/negativeviewportheight/quad.vert.spv new file mode 100644 index 00000000..c93f1261 Binary files /dev/null and b/data/shaders/negativeviewportheight/quad.vert.spv differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 77119f5c..7745b90f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -73,6 +73,7 @@ set(EXAMPLES multisampling multithreading multiview + negativeviewportheight occlusionquery offscreen parallaxmapping diff --git a/examples/negativeviewportheight/negativeviewportheight.cpp b/examples/negativeviewportheight/negativeviewportheight.cpp new file mode 100644 index 00000000..7c3c9921 --- /dev/null +++ b/examples/negativeviewportheight/negativeviewportheight.cpp @@ -0,0 +1,309 @@ +/* +* Vulkan Example - Using VK_KHR_MAINTENANCE1 for negative viewport heights +* +* Copyright (C) by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include + +#include +#include "vulkanexamplebase.h" +#include "VulkanTexture.hpp" + +#define ENABLE_VALIDATION false + +class VulkanExample : public VulkanExampleBase +{ +public: + bool negativeViewport; + int32_t offsety = 0; + int32_t offsetx = 0; + int32_t windingOrder = 1; + int32_t cullMode = 0; + int32_t quadType = 0; + + vks::Texture2D texture; + + VkPipelineLayout pipelineLayout; + VkPipeline pipeline; + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + + struct Quad { + vks::Buffer verticesYUp; + vks::Buffer verticesYDown; + vks::Buffer indices; + } quad; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Negative Viewport height"; + settings.overlay = true; + // VK_KHR_MAINTENANCE1 is required for using negative viewport heights + enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME); + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + texture.destroy(); + } + + 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); + + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + VkViewport viewport{}; + if (negativeViewport) { + // When using a negative viewport height, the origin needs to be adjusted too + viewport.x = offsetx; + viewport.y = (float)height - offsety; + viewport.width = (float)width; + viewport.height = -(float)height; + } + else { + viewport.x = offsetx; + viewport.y = offsety; + viewport.width = (float)width; + viewport.height = (float)height; + } + viewport.minDepth = 0.0f; + viewport.maxDepth = 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, nullptr); + + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindIndexBuffer(drawCmdBuffers[i], quad.indices.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, quadType == 0 ? &quad.verticesYDown.buffer : &quad.verticesYUp.buffer, offsets); + vkCmdDrawIndexed(drawCmdBuffers[i], 6, 1, 0, 0, 0); + + drawUI(drawCmdBuffers[i]); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void loadAssets() + { + texture.loadFromFile(getAssetPath() + "textures/texture_orientation_test_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue); + + // Create two quads with different Y orientations + + struct Vertex { + float pos[3]; + float uv[2]; + }; + + const float ar = (float)height / (float)width; + + // OpenGL style (y points upwards) + std::vector verticesYPos = { + { -1.0f * ar, 1.0f, 1.0f, 0.0f, 1.0f }, + { -1.0f * ar, -1.0f, 1.0f, 0.0f, 0.0f }, + { 1.0f * ar, -1.0f, 1.0f, 1.0f, 0.0f }, + { 1.0f * ar, 1.0f, 1.0f, 1.0f, 1.0f }, + }; + + // Vulkan style (y points downwards) + std::vector verticesYNeg = { + { -1.0f * ar, -1.0f, 1.0f, 0.0f, 1.0f }, + { -1.0f * ar, 1.0f, 1.0f, 0.0f, 0.0f }, + { 1.0f * ar, 1.0f, 1.0f, 1.0f, 0.0f }, + { 1.0f * ar, -1.0f, 1.0f, 1.0f, 1.0f }, + }; + std::vector indices = { 2,1,0, 0,3,2 }; + + const VkMemoryPropertyFlags memoryPropertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, memoryPropertyFlags, &quad.verticesYUp, verticesYPos.size() * sizeof(Vertex), verticesYPos.data())); + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, memoryPropertyFlags, &quad.verticesYDown, verticesYNeg.size() * sizeof(Vertex), verticesYNeg.data())); + + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, memoryPropertyFlags, &quad.indices, indices.size() * sizeof(uint32_t), indices.data())); + } + + void setupDescriptors() + { + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0) + }; + VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout)); + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); + + VkDescriptorPoolSize poolSize = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); + VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(1, &poolSize, 1); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool)); + + VkDescriptorSetAllocateInfo descriptorSetAI = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAI, &descriptorSet)); + + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texture.descriptor); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + } + + void recreatePipeline() + { + vkDestroyPipeline(device, pipeline, nullptr); + preparePipelines(); + } + + void preparePipelines() + { + const std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + 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); + VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast(dynamicStateEnables.size()), 0); + + //VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + + VkPipelineRasterizationStateCreateInfo rasterizationStateCI{}; + rasterizationStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL; + rasterizationStateCI.lineWidth = 1.0f; + rasterizationStateCI.cullMode = VK_CULL_MODE_NONE + cullMode; + rasterizationStateCI.frontFace = windingOrder == 0 ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; + + // Vertex bindings and attributes + std::vector vertexInputBindings = { + vks::initializers::vertexInputBindingDescription(0, sizeof(float) * 5, VK_VERTEX_INPUT_RATE_VERTEX), + }; + std::vector vertexInputAttributes = { + vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Position + vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 3), // uv + }; + VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); + vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); + vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); + vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); + vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); + + VkGraphicsPipelineCreateInfo pipelineCreateInfoCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); + //pipelineCreateInfoCI.pVertexInputState = &emptyInputState; + pipelineCreateInfoCI.pVertexInputState = &vertexInputState; + pipelineCreateInfoCI.pInputAssemblyState = &inputAssemblyStateCI; + pipelineCreateInfoCI.pRasterizationState = &rasterizationStateCI; + pipelineCreateInfoCI.pColorBlendState = &colorBlendStateCI; + pipelineCreateInfoCI.pMultisampleState = &multisampleStateCI; + pipelineCreateInfoCI.pViewportState = &viewportStateCI; + pipelineCreateInfoCI.pDepthStencilState = &depthStencilStateCI; + pipelineCreateInfoCI.pDynamicState = &dynamicStateCI; + + const std::array shaderStages = { + loadShader(getAssetPath() + "shaders/negativeviewportheight/quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getAssetPath() + "shaders/negativeviewportheight/quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) + }; + + pipelineCreateInfoCI.stageCount = static_cast(shaderStages.size()); + pipelineCreateInfoCI.pStages = shaderStages.data(); + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfoCI, nullptr, &pipeline)); + } + + 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(); + loadAssets(); + setupDescriptors(); + preparePipelines(); + buildCommandBuffers(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + } + + virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) + { + if (overlay->header("Scene")) { + overlay->text("Quad type"); + if (overlay->comboBox("##quadtype", &quadType, { "VK (y negative)", "GL (y positive)" })) { + buildCommandBuffers(); + } + } + + if (overlay->header("Viewport")) { + if (overlay->checkBox("Negative viewport height", &negativeViewport)) { + buildCommandBuffers(); + } + if (overlay->sliderInt("offfset x", &offsetx, -(int32_t)width, (int32_t)width)) { + buildCommandBuffers(); + } + if (overlay->sliderInt("offfset y", &offsety, -(int32_t)height, (int32_t)height)) { + buildCommandBuffers(); + } + } + if (overlay->header("Pipeline")) { + overlay->text("Winding order"); + if (overlay->comboBox("##windingorder", &windingOrder, { "clock wise", "counter clock wise" })) { + recreatePipeline(); + } + overlay->text("Cull mode"); + if (overlay->comboBox("##cullmode", &cullMode, { "none", "front face", "back face" })) { + recreatePipeline(); + } + } + } +}; + +VULKAN_EXAMPLE_MAIN() \ No newline at end of file