/*------------------------------------------------------------------------ * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2014 The Android Open Source Project * Copyright (c) 2016 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Tessellation Winding Tests *//*--------------------------------------------------------------------*/ #include "vktTessellationWindingTests.hpp" #include "vktTestCaseUtil.hpp" #include "vktTessellationUtil.hpp" #include "vktTestGroupUtil.hpp" #include "tcuTestLog.hpp" #include "tcuRGBA.hpp" #include "tcuMaybe.hpp" #include "vkDefs.hpp" #include "vkBarrierUtil.hpp" #include "vkQueryUtil.hpp" #include "vkBuilderUtil.hpp" #include "vkImageUtil.hpp" #include "vkTypeUtil.hpp" #include "vkStrUtil.hpp" #include "vkCmdUtil.hpp" #include "vkObjUtil.hpp" #include "vkBufferWithMemory.hpp" #include "vkImageWithMemory.hpp" #include "deUniquePtr.hpp" namespace vkt { namespace tessellation { using namespace vk; namespace { std::string getCaseName (const TessPrimitiveType primitiveType, const ShaderLanguage shaderLanguage, const Winding winding, bool yFlip) { std::ostringstream str; str << getShaderLanguageName(shaderLanguage) << "_" << getTessPrimitiveTypeShaderName(primitiveType) << "_" << getWindingShaderName(winding); if (yFlip) str << "_yflip"; return str.str(); } inline VkFrontFace mapFrontFace (const Winding winding) { switch (winding) { case WINDING_CCW: return VK_FRONT_FACE_COUNTER_CLOCKWISE; case WINDING_CW: return VK_FRONT_FACE_CLOCKWISE; default: DE_ASSERT(false); return VK_FRONT_FACE_LAST; } } //! Returns true when the image passes the verification. bool verifyResultImage (tcu::TestLog& log, const tcu::ConstPixelBufferAccess image, const TessPrimitiveType primitiveType, const VkTessellationDomainOrigin domainOrigin, const Winding winding, bool yFlip, const Winding frontFaceWinding) { const bool expectVisiblePrimitive = ((frontFaceWinding == winding) == (domainOrigin == VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT)) != yFlip; const int totalNumPixels = image.getWidth()*image.getHeight(); const tcu::Vec4 white = tcu::RGBA::white().toVec(); const tcu::Vec4 red = tcu::RGBA::red().toVec(); int numWhitePixels = 0; int numRedPixels = 0; // Count red and white pixels for (int y = 0; y < image.getHeight(); y++) for (int x = 0; x < image.getWidth(); x++) { numWhitePixels += image.getPixel(x, y) == white ? 1 : 0; numRedPixels += image.getPixel(x, y) == red ? 1 : 0; } DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels); log << tcu::TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels << " red pixels" << tcu::TestLog::EndMessage; { const int otherPixels = totalNumPixels - numWhitePixels - numRedPixels; if (otherPixels > 0) { log << tcu::TestLog::Message << "Failure: Got " << otherPixels << " other than white or red pixels" << tcu::TestLog::EndMessage; return false; } } if (expectVisiblePrimitive) { if (primitiveType == TESSPRIMITIVETYPE_TRIANGLES) { const int badPixelTolerance = (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 5*de::max(image.getWidth(), image.getHeight()) : 0); if (de::abs(numWhitePixels - totalNumPixels/2) > badPixelTolerance) { log << tcu::TestLog::Message << "Failure: wrong number of white pixels; expected approximately " << totalNumPixels/2 << tcu::TestLog::EndMessage; return false; } // Check number of filled pixels (from left) in top and bottom rows to // determine if triangle is in right orientation. { const tcu::IVec2 expectedStart (0, 1); const tcu::IVec2 expectedEnd (image.getWidth()-1, image.getWidth()); const tcu::IVec2 expectedTop = yFlip ? expectedStart : expectedEnd; const tcu::IVec2 expectedBottom = yFlip ? expectedEnd : expectedStart; int numTopFilled = 0; int numBottomFilled = 0; for (int x = 0; x < image.getWidth(); ++x) { if (image.getPixel(x, 0) == white) numTopFilled += 1; else break; } for (int x = 0; x < image.getWidth(); ++x) { if (image.getPixel(x, image.getHeight()-1) == white) numBottomFilled += 1; else break; } if (!de::inBounds(numTopFilled, expectedTop[0], expectedTop[1]) || !de::inBounds(numBottomFilled, expectedBottom[0], expectedBottom[1])) { log << tcu::TestLog::Message << "Failure: triangle orientation is incorrect" << tcu::TestLog::EndMessage; return false; } } } else if (primitiveType == TESSPRIMITIVETYPE_QUADS) { if (numWhitePixels != totalNumPixels) { log << tcu::TestLog::Message << "Failure: expected only white pixels (full-viewport quad)" << tcu::TestLog::EndMessage; return false; } } else DE_ASSERT(false); } else { if (numWhitePixels != 0) { log << tcu::TestLog::Message << "Failure: expected only red pixels (everything culled)" << tcu::TestLog::EndMessage; return false; } } return true; } typedef tcu::Maybe MaybeDomainOrigin; class WindingTest : public TestCase { public: WindingTest (tcu::TestContext& testCtx, const TessPrimitiveType primitiveType, const MaybeDomainOrigin& domainOrigin, const ShaderLanguage shaderLanguage, const Winding winding, bool yFlip); void initPrograms (SourceCollections& programCollection) const; TestInstance* createInstance (Context& context) const; private: const TessPrimitiveType m_primitiveType; const MaybeDomainOrigin m_domainOrigin; const ShaderLanguage m_shaderLanguage; const Winding m_winding; const bool m_yFlip; }; WindingTest::WindingTest (tcu::TestContext& testCtx, const TessPrimitiveType primitiveType, const MaybeDomainOrigin& domainOrigin, const ShaderLanguage shaderLanguage, const Winding winding, bool yFlip) : TestCase (testCtx, getCaseName(primitiveType, shaderLanguage, winding, yFlip), "") , m_primitiveType (primitiveType) , m_domainOrigin (domainOrigin) , m_shaderLanguage (shaderLanguage) , m_winding (winding) , m_yFlip (yFlip) { } void WindingTest::initPrograms (SourceCollections& programCollection) const { if (m_shaderLanguage == SHADER_LANGUAGE_GLSL) { // Vertex shader - no inputs { std::ostringstream src; src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" << "\n" << "void main (void)\n" << "{\n" << "}\n"; programCollection.glslSources.add("vert") << glu::VertexSource(src.str()); } // Tessellation control shader { std::ostringstream src; src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" << "#extension GL_EXT_tessellation_shader : require\n" << "\n" << "layout(vertices = 1) out;\n" << "\n" << "void main (void)\n" << "{\n" << " gl_TessLevelInner[0] = 5.0;\n" << " gl_TessLevelInner[1] = 5.0;\n" << "\n" << " gl_TessLevelOuter[0] = 5.0;\n" << " gl_TessLevelOuter[1] = 5.0;\n" << " gl_TessLevelOuter[2] = 5.0;\n" << " gl_TessLevelOuter[3] = 5.0;\n" << "}\n"; programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); } // Tessellation evaluation shader { std::ostringstream src; src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" << "#extension GL_EXT_tessellation_shader : require\n" << "\n" << "layout(" << getTessPrimitiveTypeShaderName(m_primitiveType) << ", " << getWindingShaderName(m_winding) << ") in;\n" << "\n" << "void main (void)\n" << "{\n" << " gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n" << "}\n"; programCollection.glslSources.add("tese") << glu::TessellationEvaluationSource(src.str()); } // Fragment shader { std::ostringstream src; src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" << "\n" << "layout(location = 0) out mediump vec4 o_color;\n" << "\n" << "void main (void)\n" << "{\n" << " o_color = vec4(1.0);\n" << "}\n"; programCollection.glslSources.add("frag") << glu::FragmentSource(src.str()); } } else { // Vertex shader - no inputs { std::ostringstream src; src << "void main (void)\n" << "{\n" << "}\n"; programCollection.hlslSources.add("vert") << glu::VertexSource(src.str()); } // Tessellation control shader { std::ostringstream src; src << "struct HS_CONSTANT_OUT\n" << "{\n" << " float tessLevelsOuter[4] : SV_TessFactor;\n" << " float tessLevelsInner[2] : SV_InsideTessFactor;\n" << "};\n" << "\n" << "[domain(\"" << getDomainName(m_primitiveType) << "\")]\n" << "[partitioning(\"integer\")]\n" << "[outputtopology(\"" << getOutputTopologyName (m_primitiveType, m_winding, false) << "\")]\n" << "[outputcontrolpoints(1)]\n" << "[patchconstantfunc(\"PCF\")]\n" << "void main()\n" << "{\n" << "}\n" << "\n" << "HS_CONSTANT_OUT PCF()\n" << "{\n" << " HS_CONSTANT_OUT output;\n" << " output.tessLevelsInner[0] = 5.0;\n" << " output.tessLevelsInner[1] = 5.0;\n" << " output.tessLevelsOuter[0] = 5.0;\n" << " output.tessLevelsOuter[1] = 5.0;\n" << " output.tessLevelsOuter[2] = 5.0;\n" << " output.tessLevelsOuter[3] = 5.0;\n" << " return output;\n" << "}\n"; programCollection.hlslSources.add("tesc") << glu::TessellationControlSource(src.str()); } // Tessellation evaluation shader { std::ostringstream src; src << "float4 main(" << (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "float3" : "float2") << " tessCoords : SV_DOMAINLOCATION) : SV_POSITION\n" << "{\n" << " return float4(tessCoords.xy*2.0 - 1, 0.0, 1.0);\n" << "}\n"; programCollection.hlslSources.add("tese") << glu::TessellationEvaluationSource(src.str()); } // Fragment shader { std::ostringstream src; src << "float4 main (void) : COLOR0\n" << "{\n" << " return float4(1.0);\n" << "}\n"; programCollection.hlslSources.add("frag") << glu::FragmentSource(src.str()); } } } class WindingTestInstance : public TestInstance { public: WindingTestInstance (Context& context, const TessPrimitiveType primitiveType, const MaybeDomainOrigin& domainOrigin, const Winding winding, bool yFlip); tcu::TestStatus iterate (void); private: void requireExtension (const char* name) const; const TessPrimitiveType m_primitiveType; const MaybeDomainOrigin m_domainOrigin; const Winding m_winding; const bool m_yFlip; }; WindingTestInstance::WindingTestInstance (Context& context, const TessPrimitiveType primitiveType, const MaybeDomainOrigin& domainOrigin, const Winding winding, bool yFlip) : TestInstance (context) , m_primitiveType (primitiveType) , m_domainOrigin (domainOrigin) , m_winding (winding) , m_yFlip (yFlip) { if (m_yFlip) requireExtension("VK_KHR_maintenance1"); if ((bool)m_domainOrigin) requireExtension("VK_KHR_maintenance2"); } void WindingTestInstance::requireExtension (const char* name) const { if(!m_context.isDeviceFunctionalitySupported(name)) TCU_THROW(NotSupportedError, (std::string(name) + " is not supported").c_str()); } tcu::TestStatus WindingTestInstance::iterate (void) { const DeviceInterface& vk = m_context.getDeviceInterface(); const VkDevice device = m_context.getDevice(); const VkQueue queue = m_context.getUniversalQueue(); const deUint32 queueFamilyIndex = m_context.getUniversalQueueFamilyIndex(); Allocator& allocator = m_context.getDefaultAllocator(); // Color attachment const tcu::IVec2 renderSize = tcu::IVec2(64, 64); const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; const VkImageSubresourceRange colorImageSubresourceRange = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); const ImageWithMemory colorAttachmentImage (vk, device, allocator, makeImageCreateInfo(renderSize, colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 1u), MemoryRequirement::Any); // Color output buffer: image will be copied here for verification const VkDeviceSize colorBufferSizeBytes = renderSize.x()*renderSize.y() * tcu::getPixelSize(mapVkFormat(colorFormat)); const BufferWithMemory colorBuffer (vk, device, allocator, makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible); // Pipeline const Unique colorAttachmentView (makeImageView (vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange)); const Unique renderPass (makeRenderPass (vk, device, colorFormat)); const Unique framebuffer (makeFramebuffer (vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y())); const Unique pipelineLayout (makePipelineLayout (vk, device)); const VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT; // Front face is static state, so we have to create two pipelines. const Unique pipelineCounterClockwise(GraphicsPipelineBuilder() .setCullModeFlags (cullMode) .setFrontFace (VK_FRONT_FACE_COUNTER_CLOCKWISE) .setShader (vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, m_context.getBinaryCollection().get("tesc"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, m_context.getBinaryCollection().get("tese"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("frag"), DE_NULL) .setTessellationDomainOrigin (m_domainOrigin) .build (vk, device, *pipelineLayout, *renderPass)); const Unique pipelineClockwise(GraphicsPipelineBuilder() .setCullModeFlags (cullMode) .setFrontFace (VK_FRONT_FACE_CLOCKWISE) .setShader (vk, device, VK_SHADER_STAGE_VERTEX_BIT, m_context.getBinaryCollection().get("vert"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, m_context.getBinaryCollection().get("tesc"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, m_context.getBinaryCollection().get("tese"), DE_NULL) .setShader (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, m_context.getBinaryCollection().get("frag"), DE_NULL) .setTessellationDomainOrigin (m_domainOrigin) .build (vk, device, *pipelineLayout, *renderPass)); const struct // not static { Winding frontFaceWinding; VkPipeline pipeline; } testCases[] = { { WINDING_CCW, *pipelineCounterClockwise }, { WINDING_CW, *pipelineClockwise }, }; tcu::TestLog& log = m_context.getTestContext().getLog(); log << tcu::TestLog::Message << "Pipeline uses " << getCullModeFlagsStr(cullMode) << tcu::TestLog::EndMessage; bool success = true; // Draw commands const Unique cmdPool (makeCommandPool (vk, device, queueFamilyIndex)); const Unique cmdBuffer(allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(testCases); ++caseNdx) { const Winding frontFaceWinding = testCases[caseNdx].frontFaceWinding; log << tcu::TestLog::Message << "Setting " << getFrontFaceName(mapFrontFace(frontFaceWinding)) << tcu::TestLog::EndMessage; // Reset the command buffer and begin. beginCommandBuffer(vk, *cmdBuffer); // Change color attachment image layout { // State is slightly different on the first iteration. const VkImageLayout currentLayout = (caseNdx == 0 ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); const VkAccessFlags srcFlags = (caseNdx == 0 ? (VkAccessFlags)0 : (VkAccessFlags)VK_ACCESS_TRANSFER_READ_BIT); const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier( srcFlags, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, currentLayout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, *colorAttachmentImage, colorImageSubresourceRange); vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0u, 0u, DE_NULL, 0u, DE_NULL, 1u, &colorAttachmentLayoutBarrier); } // Begin render pass { const VkRect2D renderArea = makeRect2D(renderSize); const tcu::Vec4 clearColor = tcu::RGBA::red().toVec(); beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor); } const VkViewport viewport = { 0.0f, // float x; m_yFlip ? static_cast(renderSize.y()) : 0.0f, // float y; static_cast(renderSize.x()), // float width; static_cast(m_yFlip ? -renderSize.y() : renderSize.y()), // float height; 0.0f, // float minDepth; 1.0f, // float maxDepth; }; vk.cmdSetViewport(*cmdBuffer, 0, 1, &viewport); const VkRect2D scissor = makeRect2D(renderSize); vk.cmdSetScissor(*cmdBuffer, 0, 1, &scissor); vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, testCases[caseNdx].pipeline); // Process a single abstract vertex. vk.cmdDraw(*cmdBuffer, 1u, 1u, 0u, 0u); endRenderPass(vk, *cmdBuffer); // Copy render result to a host-visible buffer copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize); endCommandBuffer(vk, *cmdBuffer); submitCommandsAndWait(vk, device, queue, *cmdBuffer); { // Log rendered image const Allocation& colorBufferAlloc = colorBuffer.getAllocation(); invalidateAlloc(vk, device, colorBufferAlloc); const tcu::ConstPixelBufferAccess imagePixelAccess (mapVkFormat(colorFormat), renderSize.x(), renderSize.y(), 1, colorBufferAlloc.getHostPtr()); log << tcu::TestLog::Image("color0", "Rendered image", imagePixelAccess); // Verify case result success = verifyResultImage(log, imagePixelAccess, m_primitiveType, !m_domainOrigin ? VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT : *m_domainOrigin, m_winding, m_yFlip, frontFaceWinding) && success; } } // for windingNdx return (success ? tcu::TestStatus::pass("OK") : tcu::TestStatus::fail("Failure")); } TestInstance* WindingTest::createInstance (Context& context) const { requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(), FEATURE_TESSELLATION_SHADER); return new WindingTestInstance(context, m_primitiveType, m_domainOrigin, m_winding, m_yFlip); } void populateWindingGroup (tcu::TestCaseGroup* group, tcu::Maybe domainOrigin) { static const TessPrimitiveType primitivesNoIsolines[] = { TESSPRIMITIVETYPE_TRIANGLES, TESSPRIMITIVETYPE_QUADS, }; static const ShaderLanguage shaderLanguage[] = { SHADER_LANGUAGE_GLSL, SHADER_LANGUAGE_HLSL, }; for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitivesNoIsolines); ++primitiveTypeNdx) for (int shaderLanguageNdx = 0; shaderLanguageNdx < DE_LENGTH_OF_ARRAY(shaderLanguage); ++shaderLanguageNdx) for (int windingNdx = 0; windingNdx < WINDING_LAST; ++windingNdx) { group->addChild(new WindingTest(group->getTestContext(), primitivesNoIsolines[primitiveTypeNdx], domainOrigin, shaderLanguage[shaderLanguageNdx], (Winding)windingNdx, false)); group->addChild(new WindingTest(group->getTestContext(), primitivesNoIsolines[primitiveTypeNdx], domainOrigin, shaderLanguage[shaderLanguageNdx], (Winding)windingNdx, true)); } } } // anonymous //! These tests correspond to dEQP-GLES31.functional.tessellation.winding.* tcu::TestCaseGroup* createWindingTests (tcu::TestContext& testCtx) { de::MovePtr group (new tcu::TestCaseGroup(testCtx, "winding", "Test the cw and ccw input layout qualifiers")); addTestGroup(group.get(), "default_domain", "No tessellation domain specified", populateWindingGroup, tcu::nothing()); addTestGroup(group.get(), "lower_left_domain", "Lower left tessellation domain", populateWindingGroup, tcu::just(VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT)); addTestGroup(group.get(), "upper_left_domain", "Upper left tessellation domain", populateWindingGroup, tcu::just(VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT)); return group.release(); } } // tessellation } // vkt