| /*------------------------------------------------------------------------ |
| * 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 Primitive Discard Tests |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktTessellationPrimitiveDiscardTests.hpp" |
| #include "vktTestCaseUtil.hpp" |
| #include "vktTessellationUtil.hpp" |
| |
| #include "tcuTestLog.hpp" |
| |
| #include "vkDefs.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkBuilderUtil.hpp" |
| #include "vkImageUtil.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| #include "vkObjUtil.hpp" |
| #include "vkBarrierUtil.hpp" |
| |
| #include "deUniquePtr.hpp" |
| #include "deStringUtil.hpp" |
| |
| #include <string> |
| #include <vector> |
| |
| namespace vkt |
| { |
| namespace tessellation |
| { |
| |
| using namespace vk; |
| |
| namespace |
| { |
| |
| struct CaseDefinition |
| { |
| TessPrimitiveType primitiveType; |
| SpacingMode spacingMode; |
| Winding winding; |
| bool usePointMode; |
| bool useLessThanOneInnerLevels; |
| }; |
| |
| bool lessThanOneInnerLevelsDefined (const CaseDefinition& caseDef) |
| { |
| // From Vulkan API specification: |
| // >> When tessellating triangles or quads (with/without point mode) with fractional odd spacing, the tessellator |
| // >> ***may*** produce interior vertices that are positioned on the edge of the patch if an inner |
| // >> tessellation level is less than or equal to one. |
| return !((caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_QUADS || |
| caseDef.primitiveType == vkt::tessellation::TESSPRIMITIVETYPE_TRIANGLES) && |
| caseDef.spacingMode == vkt::tessellation::SPACINGMODE_FRACTIONAL_ODD); |
| } |
| |
| int intPow (int base, int exp) |
| { |
| DE_ASSERT(exp >= 0); |
| if (exp == 0) |
| return 1; |
| else |
| { |
| const int sub = intPow(base, exp/2); |
| if (exp % 2 == 0) |
| return sub*sub; |
| else |
| return sub*sub*base; |
| } |
| } |
| |
| std::vector<float> genAttributes (bool useLessThanOneInnerLevels) |
| { |
| // Generate input attributes (tessellation levels, and position scale and |
| // offset) for a number of primitives. Each primitive has a different |
| // combination of tessellatio levels; each level is either a valid |
| // value or an "invalid" value (negative or zero, chosen from |
| // invalidTessLevelChoices). |
| |
| // \note The attributes are generated in such an order that all of the |
| // valid attribute tuples come before the first invalid one both |
| // in the result vector, and when scanning the resulting 2d grid |
| // of primitives is scanned in y-major order. This makes |
| // verification somewhat simpler. |
| |
| static const float baseTessLevels[6] = { 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f }; |
| static const float invalidTessLevelChoices[] = { -0.42f, 0.0f }; |
| const int numChoices = 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices); |
| float choices[6][numChoices]; |
| std::vector<float> result; |
| |
| for (int levelNdx = 0; levelNdx < 6; levelNdx++) |
| for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++) |
| choices[levelNdx][choiceNdx] = (choiceNdx == 0 || !useLessThanOneInnerLevels) ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx-1]; |
| |
| { |
| const int numCols = intPow(numChoices, 6/2); // sqrt(numChoices**6) == sqrt(number of primitives) |
| const int numRows = numCols; |
| int index = 0; |
| int i[6]; |
| // We could do this with some generic combination-generation function, but meh, it's not that bad. |
| for (i[2] = 0; i[2] < numChoices; i[2]++) // First outer |
| for (i[3] = 0; i[3] < numChoices; i[3]++) // Second outer |
| for (i[4] = 0; i[4] < numChoices; i[4]++) // Third outer |
| for (i[5] = 0; i[5] < numChoices; i[5]++) // Fourth outer |
| for (i[0] = 0; i[0] < numChoices; i[0]++) // First inner |
| for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner |
| { |
| for (int j = 0; j < 6; j++) |
| result.push_back(choices[j][i[j]]); |
| |
| { |
| const int col = index % numCols; |
| const int row = index / numCols; |
| // Position scale. |
| result.push_back((float)2.0f / (float)numCols); |
| result.push_back((float)2.0f / (float)numRows); |
| // Position offset. |
| result.push_back((float)col / (float)numCols * 2.0f - 1.0f); |
| result.push_back((float)row / (float)numRows * 2.0f - 1.0f); |
| } |
| |
| index++; |
| } |
| } |
| |
| return result; |
| } |
| |
| //! Check that white pixels are found around every non-discarded patch, |
| //! and that only black pixels are found after the last non-discarded patch. |
| //! Returns true on successful comparison. |
| bool verifyResultImage (tcu::TestLog& log, |
| const int numPrimitives, |
| const int numAttribsPerPrimitive, |
| const TessPrimitiveType primitiveType, |
| const std::vector<float>& attributes, |
| const tcu::ConstPixelBufferAccess pixels) |
| { |
| const tcu::Vec4 black(0.0f, 0.0f, 0.0f, 1.0f); |
| const tcu::Vec4 white(1.0f, 1.0f, 1.0f, 1.0f); |
| |
| int lastWhitePixelRow = 0; |
| int secondToLastWhitePixelRow = 0; |
| int lastWhitePixelColumnOnSecondToLastWhitePixelRow = 0; |
| |
| for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx) |
| { |
| const float* const attr = &attributes[numAttribsPerPrimitive*patchNdx]; |
| const bool validLevels = !isPatchDiscarded(primitiveType, &attr[2]); |
| |
| if (validLevels) |
| { |
| // Not a discarded patch; check that at least one white pixel is found in its area. |
| |
| const float* const scale = &attr[6]; |
| const float* const offset = &attr[8]; |
| const int x0 = (int)(( offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) - 1; |
| const int x1 = (int)((scale[0] + offset[0] + 1.0f) * 0.5f * (float)pixels.getWidth()) + 1; |
| const int y0 = (int)(( offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) - 1; |
| const int y1 = (int)((scale[1] + offset[1] + 1.0f) * 0.5f * (float)pixels.getHeight()) + 1; |
| bool pixelOk = false; |
| |
| if (y1 > lastWhitePixelRow) |
| { |
| secondToLastWhitePixelRow = lastWhitePixelRow; |
| lastWhitePixelRow = y1; |
| } |
| lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1; |
| |
| for (int y = y0; y <= y1 && !pixelOk; y++) |
| for (int x = x0; x <= x1 && !pixelOk; x++) |
| { |
| if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight())) |
| continue; |
| |
| if (pixels.getPixel(x, y) == white) |
| pixelOk = true; |
| } |
| |
| if (!pixelOk) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: expected at least one white pixel in the rectangle " |
| << "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]" |
| << tcu::TestLog::EndMessage |
| << tcu::TestLog::Message |
| << "Note: the rectangle approximately corresponds to the patch with these tessellation levels: " |
| << getTessellationLevelsString(&attr[0], &attr[1]) |
| << tcu::TestLog::EndMessage; |
| |
| return false; |
| } |
| } |
| else |
| { |
| // First discarded primitive patch; the remaining are guaranteed to be discarded ones as well. |
| |
| for (int y = 0; y < pixels.getHeight(); y++) |
| for (int x = 0; x < pixels.getWidth(); x++) |
| { |
| if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow && x > lastWhitePixelColumnOnSecondToLastWhitePixelRow)) |
| { |
| if (pixels.getPixel(x, y) != black) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: expected all pixels to be black in the area " |
| << (lastWhitePixelColumnOnSecondToLastWhitePixelRow < pixels.getWidth()-1 |
| ? std::string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " + de::toString(secondToLastWhitePixelRow) |
| + " && x > " + de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + ")" |
| : std::string() + "y > " + de::toString(lastWhitePixelRow)) |
| << " (they all correspond to patches that should be discarded)" |
| << tcu::TestLog::EndMessage |
| << tcu::TestLog::Message << "Note: pixel " << tcu::IVec2(x, y) << " isn't black" << tcu::TestLog::EndMessage; |
| |
| return false; |
| } |
| } |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| int expectedVertexCount (const int numPrimitives, |
| const int numAttribsPerPrimitive, |
| const TessPrimitiveType primitiveType, |
| const SpacingMode spacingMode, |
| const std::vector<float>& attributes) |
| { |
| int count = 0; |
| for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx) |
| count += referenceVertexCount(primitiveType, spacingMode, true, &attributes[numAttribsPerPrimitive*patchNdx+0], &attributes[numAttribsPerPrimitive*patchNdx+2]); |
| return count; |
| } |
| |
| void initPrograms (vk::SourceCollections& programCollection, const CaseDefinition caseDef) |
| { |
| // Vertex shader |
| { |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "\n" |
| << "layout(location = 0) in highp float in_v_attr;\n" |
| << "layout(location = 0) out highp float in_tc_attr;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " in_tc_attr = in_v_attr;\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" |
| << "layout(location = 0) in highp float in_tc_attr[];\n" |
| << "\n" |
| << "layout(location = 0) patch out highp vec2 in_te_positionScale;\n" |
| << "layout(location = 1) patch out highp vec2 in_te_positionOffset;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " in_te_positionScale = vec2(in_tc_attr[6], in_tc_attr[7]);\n" |
| << " in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n" |
| << "\n" |
| << " gl_TessLevelInner[0] = in_tc_attr[0];\n" |
| << " gl_TessLevelInner[1] = in_tc_attr[1];\n" |
| << "\n" |
| << " gl_TessLevelOuter[0] = in_tc_attr[2];\n" |
| << " gl_TessLevelOuter[1] = in_tc_attr[3];\n" |
| << " gl_TessLevelOuter[2] = in_tc_attr[4];\n" |
| << " gl_TessLevelOuter[3] = in_tc_attr[5];\n" |
| << "}\n"; |
| |
| programCollection.glslSources.add("tesc") << glu::TessellationControlSource(src.str()); |
| } |
| |
| // Tessellation evaluation shader |
| // When using point mode we need two variants of the shader, one for the case where |
| // shaderTessellationAndGeometryPointSize is enabled (in which the tessellation evaluation |
| // shader needs to write to gl_PointSize for it to be defined) and one for the case where |
| // it is disabled, in which we can't write to gl_PointSize but it has a default value |
| // of 1.0 |
| { |
| const deUint32 numVariants = caseDef.usePointMode ? 2 : 1; |
| for (deUint32 variant = 0; variant < numVariants; variant++) |
| { |
| const bool needPointSizeWrite = caseDef.usePointMode && variant == 1; |
| |
| std::ostringstream src; |
| src << glu::getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES) << "\n" |
| << "#extension GL_EXT_tessellation_shader : require\n"; |
| if (needPointSizeWrite) |
| { |
| src << "#extension GL_EXT_tessellation_point_size : require\n"; |
| } |
| src << "\n" |
| << "layout(" << getTessPrimitiveTypeShaderName(caseDef.primitiveType) << ", " |
| << getSpacingModeShaderName(caseDef.spacingMode) << ", " |
| << getWindingShaderName(caseDef.winding) |
| << (caseDef.usePointMode ? ", point_mode" : "") << ") in;\n" |
| << "\n" |
| << "layout(location = 0) patch in highp vec2 in_te_positionScale;\n" |
| << "layout(location = 1) patch in highp vec2 in_te_positionOffset;\n" |
| << "\n" |
| << "layout(set = 0, binding = 0, std430) coherent restrict buffer Output {\n" |
| << " int numInvocations;\n" |
| << "} sb_out;\n" |
| << "\n" |
| << "void main (void)\n" |
| << "{\n" |
| << " atomicAdd(sb_out.numInvocations, 1);\n" |
| << "\n" |
| << " gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"; |
| if (needPointSizeWrite) |
| { |
| src << " gl_PointSize = 1.0;\n"; |
| } |
| src << "}\n"; |
| |
| programCollection.glslSources.add(needPointSizeWrite ? "tese_psw" : "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()); |
| } |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Test that patch is discarded if relevant outer level <= 0.0 |
| * |
| * Draws patches with different combinations of tessellation levels, |
| * varying which levels are negative. Verifies by checking that white |
| * pixels exist inside the area of valid primitives, and only black pixels |
| * exist inside the area of discarded primitives. An additional sanity |
| * test is done, checking that the number of primitives written by shader is |
| * correct. |
| *//*--------------------------------------------------------------------*/ |
| tcu::TestStatus test (Context& context, const CaseDefinition caseDef) |
| { |
| requireFeatures(context.getInstanceInterface(), context.getPhysicalDevice(), FEATURE_TESSELLATION_SHADER | FEATURE_VERTEX_PIPELINE_STORES_AND_ATOMICS); |
| |
| const DeviceInterface& vk = context.getDeviceInterface(); |
| const VkDevice device = context.getDevice(); |
| const VkQueue queue = context.getUniversalQueue(); |
| const deUint32 queueFamilyIndex = context.getUniversalQueueFamilyIndex(); |
| Allocator& allocator = context.getDefaultAllocator(); |
| |
| const std::vector<float> attributes = genAttributes(caseDef.useLessThanOneInnerLevels); |
| const int numAttribsPerPrimitive = 6 + 2 + 2; // Tess levels, scale, offset. |
| const int numPrimitives = static_cast<int>(attributes.size() / numAttribsPerPrimitive); |
| const int numExpectedVertices = expectedVertexCount(numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType, caseDef.spacingMode, attributes); |
| |
| // Check the convenience assertion that all discarded patches come after the last non-discarded patch. |
| { |
| bool discardedPatchEncountered = false; |
| for (int patchNdx = 0; patchNdx < numPrimitives; ++patchNdx) |
| { |
| const bool discard = isPatchDiscarded(caseDef.primitiveType, &attributes[numAttribsPerPrimitive*patchNdx + 2]); |
| DE_ASSERT(discard || !discardedPatchEncountered); |
| discardedPatchEncountered = discard; |
| } |
| DE_UNREF(discardedPatchEncountered); |
| } |
| |
| // Vertex input attributes buffer |
| |
| const VkFormat vertexFormat = VK_FORMAT_R32_SFLOAT; |
| const deUint32 vertexStride = tcu::getPixelSize(mapVkFormat(vertexFormat)); |
| const VkDeviceSize vertexDataSizeBytes = sizeInBytes(attributes); |
| const Buffer vertexBuffer (vk, device, allocator, makeBufferCreateInfo(vertexDataSizeBytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT), MemoryRequirement::HostVisible); |
| |
| DE_ASSERT(static_cast<int>(attributes.size()) == numPrimitives * numAttribsPerPrimitive); |
| DE_ASSERT(sizeof(attributes[0]) == vertexStride); |
| |
| { |
| const Allocation& alloc = vertexBuffer.getAllocation(); |
| |
| deMemcpy(alloc.getHostPtr(), &attributes[0], static_cast<std::size_t>(vertexDataSizeBytes)); |
| flushAlloc(vk, device, alloc); |
| // No barrier needed, flushed memory is automatically visible |
| } |
| |
| // Output buffer: number of invocations |
| |
| const VkDeviceSize resultBufferSizeBytes = sizeof(deInt32); |
| const Buffer resultBuffer (vk, device, allocator, makeBufferCreateInfo(resultBufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), MemoryRequirement::HostVisible); |
| |
| { |
| const Allocation& alloc = resultBuffer.getAllocation(); |
| |
| deMemset(alloc.getHostPtr(), 0, static_cast<std::size_t>(resultBufferSizeBytes)); |
| flushAlloc(vk, device, alloc); |
| } |
| |
| // Color attachment |
| |
| const tcu::IVec2 renderSize = tcu::IVec2(256, 256); |
| const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; |
| const VkImageSubresourceRange colorImageSubresourceRange = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); |
| const Image 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 Buffer colorBuffer(vk, device, allocator, |
| makeBufferCreateInfo(colorBufferSizeBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT), MemoryRequirement::HostVisible); |
| |
| // Descriptors |
| |
| const Unique<VkDescriptorSetLayout> descriptorSetLayout(DescriptorSetLayoutBuilder() |
| .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) |
| .build(vk, device)); |
| |
| const Unique<VkDescriptorPool> descriptorPool(DescriptorPoolBuilder() |
| .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) |
| .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u)); |
| |
| const Unique<VkDescriptorSet> descriptorSet (makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout)); |
| const VkDescriptorBufferInfo resultBufferInfo = makeDescriptorBufferInfo(resultBuffer.get(), 0ull, resultBufferSizeBytes); |
| |
| DescriptorSetUpdateBuilder() |
| .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &resultBufferInfo) |
| .update(vk, device); |
| |
| // Pipeline |
| |
| const Unique<VkImageView> colorAttachmentView (makeImageView(vk, device, *colorAttachmentImage, VK_IMAGE_VIEW_TYPE_2D, colorFormat, colorImageSubresourceRange)); |
| const Unique<VkRenderPass> renderPass (makeRenderPass(vk, device, colorFormat)); |
| const Unique<VkFramebuffer> framebuffer (makeFramebuffer(vk, device, *renderPass, *colorAttachmentView, renderSize.x(), renderSize.y())); |
| const Unique<VkPipelineLayout> pipelineLayout (makePipelineLayout(vk, device, *descriptorSetLayout)); |
| const Unique<VkCommandPool> cmdPool (makeCommandPool(vk, device, queueFamilyIndex)); |
| const Unique<VkCommandBuffer> cmdBuffer (allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY)); |
| const bool needPointSizeWrite = getPhysicalDeviceFeatures(context.getInstanceInterface(), context.getPhysicalDevice()).shaderTessellationAndGeometryPointSize && caseDef.usePointMode; |
| |
| const Unique<VkPipeline> pipeline(GraphicsPipelineBuilder() |
| .setRenderSize (renderSize) |
| .setPatchControlPoints (numAttribsPerPrimitive) |
| .setVertexInputSingleAttribute(vertexFormat, vertexStride) |
| .setShader (vk, device, VK_SHADER_STAGE_VERTEX_BIT, context.getBinaryCollection().get("vert"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, context.getBinaryCollection().get("tesc"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, context.getBinaryCollection().get(needPointSizeWrite ? "tese_psw" : "tese"), DE_NULL) |
| .setShader (vk, device, VK_SHADER_STAGE_FRAGMENT_BIT, context.getBinaryCollection().get("frag"), DE_NULL) |
| .build (vk, device, *pipelineLayout, *renderPass)); |
| |
| context.getTestContext().getLog() |
| << tcu::TestLog::Message |
| << "Note: rendering " << numPrimitives << " patches; first patches have valid relevant outer levels, " |
| << "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels" |
| << tcu::TestLog::EndMessage; |
| |
| // Draw commands |
| |
| beginCommandBuffer(vk, *cmdBuffer); |
| |
| // Change color attachment image layout |
| { |
| const VkImageMemoryBarrier colorAttachmentLayoutBarrier = makeImageMemoryBarrier( |
| (VkAccessFlags)0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| *colorAttachmentImage, colorImageSubresourceRange); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_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::Vec4(0.0f, 0.0f, 0.0f, 1.0f); |
| |
| beginRenderPass(vk, *cmdBuffer, *renderPass, *framebuffer, renderArea, clearColor); |
| } |
| |
| vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipelineLayout, 0u, 1u, &descriptorSet.get(), 0u, DE_NULL); |
| { |
| const VkDeviceSize vertexBufferOffset = 0ull; |
| vk.cmdBindVertexBuffers(*cmdBuffer, 0u, 1u, &vertexBuffer.get(), &vertexBufferOffset); |
| } |
| |
| vk.cmdDraw(*cmdBuffer, static_cast<deUint32>(attributes.size()), 1u, 0u, 0u); |
| endRenderPass(vk, *cmdBuffer); |
| |
| // Copy render result to a host-visible buffer |
| copyImageToBuffer(vk, *cmdBuffer, *colorAttachmentImage, *colorBuffer, renderSize); |
| { |
| const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier( |
| VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *resultBuffer, 0ull, resultBufferSizeBytes); |
| |
| vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, |
| 0u, DE_NULL, 1u, &shaderWriteBarrier, 0u, DE_NULL); |
| } |
| |
| 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()); |
| tcu::TestLog& log = context.getTestContext().getLog(); |
| |
| log << tcu::TestLog::Image("color0", "Rendered image", imagePixelAccess); |
| |
| // Verify case result |
| const Allocation& resultAlloc = resultBuffer.getAllocation(); |
| |
| invalidateAlloc(vk, device, resultAlloc); |
| |
| const deInt32 numResultVertices = *static_cast<deInt32*>(resultAlloc.getHostPtr()); |
| |
| if (!lessThanOneInnerLevelsDefined(caseDef) && caseDef.useLessThanOneInnerLevels) |
| { |
| // Since we cannot explicitly determine whether or not such interior vertices are going to be |
| // generated, we will not verify the number of generated vertices for fractional odd + quads/triangles |
| // tessellation configurations. |
| log << tcu::TestLog::Message |
| << "Note: shader invocations generated " << numResultVertices << " vertices (not verified as number of vertices is implementation-dependent)" |
| << tcu::TestLog::EndMessage; |
| } |
| else if (numResultVertices < numExpectedVertices) |
| { |
| log << tcu::TestLog::Message |
| << "Failure: expected " << numExpectedVertices << " vertices from shader invocations, but got only " << numResultVertices |
| << tcu::TestLog::EndMessage; |
| return tcu::TestStatus::fail("Wrong number of tessellation coordinates"); |
| } |
| else if (numResultVertices == numExpectedVertices) |
| { |
| log << tcu::TestLog::Message |
| << "Note: shader invocations generated " << numResultVertices << " vertices" |
| << tcu::TestLog::EndMessage; |
| } |
| else |
| { |
| log << tcu::TestLog::Message |
| << "Note: shader invocations generated " << numResultVertices << " vertices (expected " << numExpectedVertices << ", got " |
| << (numResultVertices - numExpectedVertices) << " extra)" |
| << tcu::TestLog::EndMessage; |
| } |
| |
| return (verifyResultImage(log, numPrimitives, numAttribsPerPrimitive, caseDef.primitiveType, attributes, imagePixelAccess) |
| ? tcu::TestStatus::pass("OK") : tcu::TestStatus::fail("Image verification failed")); |
| } |
| } |
| |
| } // anonymous |
| |
| //! These tests correspond to dEQP-GLES31.functional.tessellation.primitive_discard.* |
| //! \note Original test used transform feedback (TF) to capture the number of output vertices. The behavior of TF differs significantly from SSBO approach, |
| //! especially for non-point_mode rendering. TF returned all coordinates, while SSBO computes the count based on the number of shader invocations |
| //! which yields a much smaller number because invocations for duplicate coordinates are often eliminated. |
| //! Because of this, the test was changed to: |
| //! - always compute the number of expected coordinates as if point_mode was enabled |
| //! - not fail if implementation returned more coordinates than expected |
| tcu::TestCaseGroup* createPrimitiveDiscardTests (tcu::TestContext& testCtx) |
| { |
| de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0")); |
| |
| for (int primitiveTypeNdx = 0; primitiveTypeNdx < TESSPRIMITIVETYPE_LAST; primitiveTypeNdx++) |
| for (int spacingModeNdx = 0; spacingModeNdx < SPACINGMODE_LAST; spacingModeNdx++) |
| for (int windingNdx = 0; windingNdx < WINDING_LAST; windingNdx++) |
| for (int usePointModeNdx = 0; usePointModeNdx <= 1; usePointModeNdx++) |
| for (int lessThanOneInnerLevelsNdx = 0; lessThanOneInnerLevelsNdx <= 1; lessThanOneInnerLevelsNdx++) |
| { |
| const CaseDefinition caseDef = |
| { |
| (TessPrimitiveType)primitiveTypeNdx, |
| (SpacingMode)spacingModeNdx, |
| (Winding)windingNdx, |
| (usePointModeNdx != 0), |
| (lessThanOneInnerLevelsNdx != 0) |
| }; |
| |
| if (lessThanOneInnerLevelsDefined(caseDef) && !caseDef.useLessThanOneInnerLevels) |
| continue; // No point generating a separate case as <= 1 inner level behavior is well-defined |
| |
| const std::string caseName = std::string() + getTessPrimitiveTypeShaderName(caseDef.primitiveType) |
| + "_" + getSpacingModeShaderName(caseDef.spacingMode) |
| + "_" + getWindingShaderName(caseDef.winding) |
| + (caseDef.usePointMode ? "_point_mode" : "") |
| + (caseDef.useLessThanOneInnerLevels ? "" : "_valid_levels"); |
| |
| addFunctionCaseWithPrograms(group.get(), caseName, "", checkSupportCase, initPrograms, test, caseDef); |
| } |
| |
| return group.release(); |
| } |
| |
| } // tessellation |
| } // vkt |