| /*------------------------------------------------------------------------ |
| * Vulkan Conformance Tests |
| * ------------------------ |
| * |
| * Copyright (c) 2021 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 Tests for Color Write Enable |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "vktPipelineColorWriteEnableTests.hpp" |
| #include "vktPipelineImageUtil.hpp" |
| #include "vktTestCase.hpp" |
| |
| #include "vkDefs.hpp" |
| #include "vkTypeUtil.hpp" |
| #include "vkQueryUtil.hpp" |
| #include "vkObjUtil.hpp" |
| #include "vkBufferWithMemory.hpp" |
| #include "vkImageWithMemory.hpp" |
| #include "vkBarrierUtil.hpp" |
| #include "vkBuilderUtil.hpp" |
| #include "vkCmdUtil.hpp" |
| #include "vkImageUtil.hpp" |
| |
| #include "tcuVector.hpp" |
| #include "tcuMaybe.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuVectorUtil.hpp" |
| |
| #include "deUniquePtr.hpp" |
| #include "deStringUtil.hpp" |
| |
| #include <vector> |
| #include <sstream> |
| #include <algorithm> |
| #include <utility> |
| #include <iterator> |
| #include <string> |
| #include <limits> |
| #include <memory> |
| #include <functional> |
| |
| namespace vkt |
| { |
| namespace pipeline |
| { |
| |
| namespace |
| { |
| |
| // Framebuffer size. |
| constexpr deUint32 kFramebufferWidth = 64u; |
| constexpr deUint32 kFramebufferHeight = 64u; |
| |
| // Image formats. |
| constexpr vk::VkFormat kColorFormat = vk::VK_FORMAT_R8G8B8A8_UNORM; |
| const tcu::Vec4 kColorThreshold (0.005f); // 1/255 < 0.005 < 2/255. |
| |
| constexpr deUint32 kNumColorAttachments = 3u; |
| |
| const vk::VkFormat kDepthStencilFormats[] = |
| { |
| vk::VK_FORMAT_D32_SFLOAT_S8_UINT, |
| vk::VK_FORMAT_D24_UNORM_S8_UINT |
| }; |
| |
| constexpr auto kCoordsSize = static_cast<deUint32>(2 * sizeof(float)); |
| |
| using Bool32Vec = std::vector<vk::VkBool32>; |
| |
| // Generic, to be used with any state than can be set statically and, as an option, dynamically. |
| template<typename T> |
| struct StaticAndDynamicPair |
| { |
| T staticValue; |
| tcu::Maybe<T> dynamicValue; |
| |
| // Helper constructor to set a static value and no dynamic value. |
| StaticAndDynamicPair (const T& value) |
| : staticValue (value) |
| , dynamicValue (tcu::Nothing) |
| { |
| } |
| |
| // Helper constructor to set both. |
| StaticAndDynamicPair (const T& sVal, const T& dVal) |
| : staticValue (sVal) |
| , dynamicValue (tcu::just<T>(dVal)) |
| { |
| } |
| |
| // If the dynamic value is present, swap static and dynamic values. |
| void swapValues (void) |
| { |
| if (!dynamicValue) |
| return; |
| std::swap(staticValue, dynamicValue.get()); |
| } |
| }; |
| |
| const tcu::Vec4 kDefaultTriangleColor (0.0f, 0.0f, 1.0f, 1.0f); // Opaque blue. |
| const tcu::Vec4 kDefaultClearColor (0.0f, 0.0f, 0.0f, 1.0f); // Opaque black. |
| |
| struct MeshParams |
| { |
| tcu::Vec4 color; |
| float depth; |
| float scaleX; |
| float scaleY; |
| float offsetX; |
| float offsetY; |
| |
| MeshParams (const tcu::Vec4& color_ = kDefaultTriangleColor, |
| float depth_ = 0.0f, |
| float scaleX_ = 1.0f, |
| float scaleY_ = 1.0f, |
| float offsetX_ = 0.0f, |
| float offsetY_ = 0.0f) |
| : color (color_) |
| , depth (depth_) |
| , scaleX (scaleX_) |
| , scaleY (scaleY_) |
| , offsetX (offsetX_) |
| , offsetY (offsetY_) |
| {} |
| }; |
| |
| enum class SequenceOrdering |
| { |
| CMD_BUFFER_START = 0, // Set state at the start of the command buffer. |
| BEFORE_DRAW = 1, // After binding dynamic pipeline and just before drawing. |
| BETWEEN_PIPELINES = 2, // After a static state pipeline has been bound but before the dynamic state pipeline has been bound. |
| AFTER_PIPELINES = 3, // After a static state pipeline and a second dynamic state pipeline have been bound. |
| BEFORE_GOOD_STATIC = 4, // Before a static state pipeline with the correct values has been bound. |
| TWO_DRAWS_DYNAMIC = 5, // Bind bad static pipeline and draw, followed by binding correct dynamic pipeline and drawing again. |
| TWO_DRAWS_STATIC = 6, // Bind bad dynamic pipeline and draw, followed by binding correct static pipeline and drawing again. |
| }; |
| |
| struct TestConfig |
| { |
| vk::PipelineConstructionType pipelineConstructionType; |
| |
| // Main sequence ordering. |
| SequenceOrdering sequenceOrdering; |
| |
| // Drawing parameters. |
| MeshParams meshParams; |
| |
| // Clearing parameters for the framebuffer. |
| tcu::Vec4 clearColorValue; |
| float clearDepthValue; |
| |
| // Channels to enable |
| tcu::BVec4 channelMask; |
| |
| // Expected output in the attachments. |
| std::vector<tcu::Vec4> expectedColor; |
| float expectedDepth; |
| |
| // Static and dynamic pipeline configuration. |
| StaticAndDynamicPair<Bool32Vec> colorWriteEnableConfig; |
| |
| // Sane defaults. |
| TestConfig (vk::PipelineConstructionType constructionType, SequenceOrdering ordering) |
| : pipelineConstructionType (constructionType) |
| , sequenceOrdering (ordering) |
| , clearColorValue (kDefaultClearColor) |
| , clearDepthValue (1.0f) |
| , expectedColor (kNumColorAttachments, kDefaultTriangleColor) |
| , expectedDepth (1.0f) |
| , colorWriteEnableConfig (Bool32Vec(1u, VK_TRUE)) |
| , m_swappedValues (false) |
| { |
| } |
| |
| // Returns true if we should use the static and dynamic values exchanged. |
| // This makes the static part of the pipeline have the actual expected values. |
| bool isReversed () const |
| { |
| return (sequenceOrdering == SequenceOrdering::BEFORE_GOOD_STATIC || |
| sequenceOrdering == SequenceOrdering::TWO_DRAWS_STATIC); |
| } |
| |
| // Swaps static and dynamic configuration values. |
| void swapValues () |
| { |
| colorWriteEnableConfig.swapValues(); |
| m_swappedValues = !m_swappedValues; |
| } |
| |
| // Returns the number of iterations when recording commands. |
| deUint32 numIterations () const |
| { |
| deUint32 iterations = 0u; |
| |
| switch (sequenceOrdering) |
| { |
| case SequenceOrdering::TWO_DRAWS_DYNAMIC: |
| case SequenceOrdering::TWO_DRAWS_STATIC: |
| iterations = 2u; |
| break; |
| default: |
| iterations = 1u; |
| break; |
| } |
| |
| return iterations; |
| } |
| |
| private: |
| // Color Write Enable cases as created by createColorWriteEnableTests() are based on the assumption that, when a state |
| // has a static and a dynamic value configured at the same time, the static value is wrong and the dynamic value will give |
| // expected results. That's appropriate for most test variants, but in some others we want to reverse the situation: a dynamic |
| // pipeline with wrong values and a static one with good values. |
| // |
| // Instead of modifying how tests are created, we use isReversed() and swapValues() above, allowing us to swap static and |
| // dynamic values and to know if we should do it for a given test case. However, we need to know were the good value is at any |
| // given point in time in order to correctly answer some questions while running the test. m_swappedValues tracks that state. |
| bool m_swappedValues; |
| }; |
| |
| struct PushConstants |
| { |
| tcu::Vec4 triangleColor; |
| float meshDepth; |
| float scaleX; |
| float scaleY; |
| float offsetX; |
| float offsetY; |
| }; |
| |
| class ColorWriteEnableTest : public vkt::TestCase |
| { |
| public: |
| ColorWriteEnableTest (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const TestConfig& testConfig); |
| virtual ~ColorWriteEnableTest (void) {} |
| |
| virtual void checkSupport (Context& context) const; |
| virtual void initPrograms (vk::SourceCollections& programCollection) const; |
| virtual TestInstance* createInstance (Context& context) const; |
| |
| private: |
| TestConfig m_testConfig; |
| }; |
| |
| class ColorWriteEnableInstance : public vkt::TestInstance |
| { |
| public: |
| ColorWriteEnableInstance (Context& context, const TestConfig& testConfig); |
| virtual ~ColorWriteEnableInstance (void) {} |
| |
| virtual tcu::TestStatus iterate (void); |
| |
| private: |
| TestConfig m_testConfig; |
| }; |
| |
| ColorWriteEnableTest::ColorWriteEnableTest (tcu::TestContext& testCtx, const std::string& name, const std::string& description, const TestConfig& testConfig) |
| : vkt::TestCase (testCtx, name, description) |
| , m_testConfig (testConfig) |
| { |
| } |
| |
| void ColorWriteEnableTest::checkSupport (Context& context) const |
| { |
| const auto& vki = context.getInstanceInterface(); |
| const auto physicalDevice = context.getPhysicalDevice(); |
| |
| // This is always required. |
| context.requireDeviceFunctionality("VK_EXT_color_write_enable"); |
| |
| // Check color image format support (depth/stencil will be chosen at runtime). |
| const vk::VkFormatFeatureFlags kColorFeatures = (vk::VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | vk::VK_FORMAT_FEATURE_TRANSFER_SRC_BIT); |
| const auto colorProperties = vk::getPhysicalDeviceFormatProperties(vki, physicalDevice, kColorFormat); |
| |
| if ((colorProperties.optimalTilingFeatures & kColorFeatures) != kColorFeatures) |
| TCU_THROW(NotSupportedError, "Required color image features not supported"); |
| |
| checkPipelineLibraryRequirements(vki, physicalDevice, m_testConfig.pipelineConstructionType); |
| } |
| |
| void ColorWriteEnableTest::initPrograms (vk::SourceCollections& programCollection) const |
| { |
| std::ostringstream pushSource; |
| pushSource |
| << "layout(push_constant, std430) uniform PushConstantsBlock {\n" |
| << " vec4 triangleColor;\n" |
| << " float depthValue;\n" |
| << " float scaleX;\n" |
| << " float scaleY;\n" |
| << " float offsetX;\n" |
| << " float offsetY;\n" |
| << "} pushConstants;\n" |
| ; |
| |
| std::ostringstream vertSource; |
| vertSource |
| << "#version 450\n" |
| << pushSource.str() |
| << "layout(location=0) in vec2 position;\n" |
| << "out gl_PerVertex\n" |
| << "{\n" |
| << " vec4 gl_Position;\n" |
| << "};\n" |
| << "void main() {\n" |
| << " vec2 vertexCoords = position;\n" |
| << " gl_Position = vec4(vertexCoords.x * pushConstants.scaleX + pushConstants.offsetX, vertexCoords.y * pushConstants.scaleY + pushConstants.offsetY, pushConstants.depthValue, 1.0);\n" |
| << "}\n" |
| ; |
| |
| std::ostringstream fragOutputs; |
| std::ostringstream colorWrite; |
| for (deUint32 i = 0u; i < kNumColorAttachments; ++i) |
| { |
| fragOutputs << "layout(location=" << i << ") out vec4 color" << i << ";\n"; |
| colorWrite << " color" << i << " = pushConstants.triangleColor * " << powf(0.5f, static_cast<float>(i)) << ";\n"; |
| } |
| |
| std::ostringstream fragSource; |
| fragSource |
| << "#version 450\n" |
| << pushSource.str() |
| << fragOutputs.str() |
| << "void main() {\n" |
| << colorWrite.str() |
| << "}\n" |
| ; |
| |
| programCollection.glslSources.add("vert") << glu::VertexSource(vertSource.str()); |
| programCollection.glslSources.add("frag") << glu::FragmentSource(fragSource.str()); |
| } |
| |
| TestInstance* ColorWriteEnableTest::createInstance (Context& context) const |
| { |
| return new ColorWriteEnableInstance(context, m_testConfig); |
| } |
| |
| ColorWriteEnableInstance::ColorWriteEnableInstance(Context& context, const TestConfig& testConfig) |
| : vkt::TestInstance (context) |
| , m_testConfig (testConfig) |
| { |
| } |
| |
| void logErrors(tcu::TestLog& log, const std::string& setName, const std::string& setDesc, const tcu::ConstPixelBufferAccess& result, const tcu::ConstPixelBufferAccess& errorMask) |
| { |
| log << tcu::TestLog::ImageSet(setName, setDesc) |
| << tcu::TestLog::Image(setName + "Result", "Result image", result) |
| << tcu::TestLog::Image(setName + "ErrorMask", "Error mask with errors marked in red", errorMask) |
| << tcu::TestLog::EndImageSet; |
| } |
| |
| // Sets values for dynamic states if needed according to the test configuration. |
| void setDynamicStates(const TestConfig& testConfig, const vk::DeviceInterface& vkd, vk::VkCommandBuffer cmdBuffer) |
| { |
| if (testConfig.colorWriteEnableConfig.dynamicValue) |
| { |
| const auto& colorWriteEnables = testConfig.colorWriteEnableConfig.dynamicValue.get(); |
| vkd.cmdSetColorWriteEnableEXT(cmdBuffer, static_cast<deUint32>(colorWriteEnables.size()), colorWriteEnables.data()); |
| } |
| } |
| |
| tcu::TestStatus ColorWriteEnableInstance::iterate (void) |
| { |
| using ImageWithMemoryVec = std::vector<std::unique_ptr<vk::ImageWithMemory>>; |
| using ImageViewVec = std::vector<vk::Move<vk::VkImageView>>; |
| using FramebufferVec = std::vector<vk::Move<vk::VkFramebuffer>>; |
| |
| const auto& vki = m_context.getInstanceInterface(); |
| const auto& vkd = m_context.getDeviceInterface(); |
| const auto physicalDevice = m_context.getPhysicalDevice(); |
| const auto device = m_context.getDevice(); |
| auto& allocator = m_context.getDefaultAllocator(); |
| const auto queue = m_context.getUniversalQueue(); |
| const auto queueIndex = m_context.getUniversalQueueFamilyIndex(); |
| auto& log = m_context.getTestContext().getLog(); |
| |
| const auto kReversed = m_testConfig.isReversed(); |
| const auto kNumIterations = m_testConfig.numIterations(); |
| const auto kSequenceOrdering = m_testConfig.sequenceOrdering; |
| |
| const auto kFramebufferExtent = vk::makeExtent3D(kFramebufferWidth, kFramebufferHeight, 1u); |
| const vk::VkImageUsageFlags kColorUsage = (vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT); |
| const vk::VkImageUsageFlags kDSUsage = (vk::VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT); |
| const vk::VkFormatFeatureFlags kDSFeatures = (vk::VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | vk::VK_FORMAT_FEATURE_TRANSFER_SRC_BIT); |
| |
| // Choose depth/stencil format. |
| vk::VkFormat dsFormat = vk::VK_FORMAT_UNDEFINED; |
| |
| for (int formatIdx = 0; formatIdx < DE_LENGTH_OF_ARRAY(kDepthStencilFormats); ++formatIdx) |
| { |
| const auto dsProperties = vk::getPhysicalDeviceFormatProperties(vki, physicalDevice, kDepthStencilFormats[formatIdx]); |
| if ((dsProperties.optimalTilingFeatures & kDSFeatures) == kDSFeatures) |
| { |
| dsFormat = kDepthStencilFormats[formatIdx]; |
| break; |
| } |
| } |
| |
| // Note: Not Supported insted of Fail because the transfer feature is not mandatory. |
| if (dsFormat == vk::VK_FORMAT_UNDEFINED) |
| TCU_THROW(NotSupportedError, "Required depth/stencil image features not supported"); |
| log << tcu::TestLog::Message << "Chosen depth/stencil format: " << dsFormat << tcu::TestLog::EndMessage; |
| |
| // Swap static and dynamic values in the test configuration so the static pipeline ends up with the expected values for cases |
| // where we will bind the static pipeline last before drawing. |
| if (kReversed) |
| m_testConfig.swapValues(); |
| |
| // Create color and depth/stencil images. |
| ImageWithMemoryVec colorImages; |
| ImageWithMemoryVec dsImages; |
| |
| const vk::VkImageCreateInfo colorImageInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkImageCreateFlags flags; |
| vk::VK_IMAGE_TYPE_2D, // VkImageType imageType; |
| kColorFormat, // VkFormat format; |
| kFramebufferExtent, // VkExtent3D extent; |
| 1u, // deUint32 mipLevels; |
| 1u, // deUint32 arrayLayers; |
| vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| vk::VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling; |
| kColorUsage, // VkImageUsageFlags usage; |
| vk::VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; |
| 1u, // deUint32 queueFamilyIndexCount; |
| &queueIndex, // const deUint32* pQueueFamilyIndices; |
| vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| }; |
| for (deUint32 i = 0u; i < kNumIterations * kNumColorAttachments; ++i) |
| colorImages.emplace_back(new vk::ImageWithMemory(vkd, device, allocator, colorImageInfo, vk::MemoryRequirement::Any)); |
| |
| const vk::VkImageCreateInfo dsImageInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkImageCreateFlags flags; |
| vk::VK_IMAGE_TYPE_2D, // VkImageType imageType; |
| dsFormat, // VkFormat format; |
| kFramebufferExtent, // VkExtent3D extent; |
| 1u, // deUint32 mipLevels; |
| 1u, // deUint32 arrayLayers; |
| vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| vk::VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling; |
| kDSUsage, // VkImageUsageFlags usage; |
| vk::VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; |
| 1u, // deUint32 queueFamilyIndexCount; |
| &queueIndex, // const deUint32* pQueueFamilyIndices; |
| vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| }; |
| for (deUint32 i = 0u; i < kNumIterations; ++i) |
| dsImages.emplace_back(new vk::ImageWithMemory(vkd, device, allocator, dsImageInfo, vk::MemoryRequirement::Any)); |
| |
| const auto colorSubresourceRange = vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); |
| const auto dsSubresourceRange = vk::makeImageSubresourceRange((vk::VK_IMAGE_ASPECT_DEPTH_BIT | vk::VK_IMAGE_ASPECT_STENCIL_BIT), 0u, 1u, 0u, 1u); |
| |
| ImageViewVec colorImageViews; |
| ImageViewVec dsImageViews; |
| |
| for (const auto& img : colorImages) |
| colorImageViews.emplace_back(vk::makeImageView(vkd, device, img->get(), vk::VK_IMAGE_VIEW_TYPE_2D, kColorFormat, colorSubresourceRange)); |
| |
| for (const auto& img : dsImages) |
| dsImageViews.emplace_back(vk::makeImageView(vkd, device, img->get(), vk::VK_IMAGE_VIEW_TYPE_2D, dsFormat, dsSubresourceRange)); |
| |
| // Vertex buffer. |
| // Full-screen triangle fan with 6 vertices. |
| // |
| // 4 3 2 |
| // +-------+-------+ |
| // |X X X| |
| // | X X X | |
| // | X X X | |
| // | X X X | |
| // | X X X | |
| // | X X X | |
| // | XXX | |
| // +-------+-------+ |
| // 5 0 1 |
| std::vector<float> vertices = { |
| 0.0f, 1.0f, |
| 1.0f, 1.0f, |
| 1.0f, -1.0f, |
| 0.0f, -1.0f, |
| -1.0f, -1.0f, |
| -1.0f, 1.0f, |
| }; |
| |
| const auto vertDataSize = vertices.size() * sizeof(float); |
| const auto vertBufferInfo = vk::makeBufferCreateInfo(static_cast<vk::VkDeviceSize>(vertDataSize), vk::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); |
| vk::BufferWithMemory vertBuffer (vkd, device, allocator, vertBufferInfo, vk::MemoryRequirement::HostVisible); |
| auto& alloc = vertBuffer.getAllocation(); |
| |
| deMemcpy(reinterpret_cast<char*>(alloc.getHostPtr()), vertices.data(), vertDataSize); |
| vk::flushAlloc(vkd, device, alloc); |
| |
| // Descriptor set layout. |
| vk::DescriptorSetLayoutBuilder layoutBuilder; |
| const auto descriptorSetLayout = layoutBuilder.build(vkd, device); |
| |
| // Pipeline layout. |
| vk::VkShaderStageFlags pushConstantStageFlags = (vk::VK_SHADER_STAGE_VERTEX_BIT | vk::VK_SHADER_STAGE_FRAGMENT_BIT); |
| |
| const vk::VkPushConstantRange pushConstantRange = |
| { |
| pushConstantStageFlags, // VkShaderStageFlags stageFlags; |
| 0u, // deUint32 offset; |
| static_cast<deUint32>(sizeof(PushConstants)), // deUint32 size; |
| }; |
| |
| const vk::VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineLayoutCreateFlags flags; |
| 1u, // deUint32 setLayoutCount; |
| &descriptorSetLayout.get(), // const VkDescriptorSetLayout* pSetLayouts; |
| 1u, // deUint32 pushConstantRangeCount; |
| &pushConstantRange, // const VkPushConstantRange* pPushConstantRanges; |
| }; |
| const auto pipelineLayout = vk::createPipelineLayout(vkd, device, &pipelineLayoutCreateInfo); |
| |
| // Render pass with single subpass. |
| std::vector<vk::VkAttachmentReference> colorAttachmentReference; |
| for (deUint32 i = 0u; i < kNumColorAttachments; ++i) |
| { |
| colorAttachmentReference.push_back(vk::VkAttachmentReference |
| { |
| i, // deUint32 attachment; |
| vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // VkImageLayout layout; |
| } |
| ); |
| } |
| |
| const vk::VkAttachmentReference dsAttachmentReference = |
| { |
| kNumColorAttachments, // deUint32 attachment; |
| vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, // VkImageLayout layout; |
| }; |
| |
| const vk::VkSubpassDescription subpassDescription = |
| { |
| 0u, // VkSubpassDescriptionFlags flags; |
| vk::VK_PIPELINE_BIND_POINT_GRAPHICS, // VkPipelineBindPoint pipelineBindPoint; |
| 0u, // deUint32 inputAttachmentCount; |
| nullptr, // const VkAttachmentReference* pInputAttachments; |
| kNumColorAttachments, // deUint32 colorAttachmentCount; |
| colorAttachmentReference.data(), // const VkAttachmentReference* pColorAttachments; |
| nullptr, // const VkAttachmentReference* pResolveAttachments; |
| &dsAttachmentReference, // const VkAttachmentReference* pDepthStencilAttachment; |
| 0u, // deUint32 preserveAttachmentCount; |
| nullptr, // const deUint32* pPreserveAttachments; |
| }; |
| |
| std::vector<vk::VkAttachmentDescription> attachmentDescriptions( |
| kNumColorAttachments, |
| vk::VkAttachmentDescription |
| { |
| 0u, // VkAttachmentDescriptionFlags flags; |
| kColorFormat, // VkFormat format; |
| vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| vk::VK_ATTACHMENT_LOAD_OP_CLEAR, // VkAttachmentLoadOp loadOp; |
| vk::VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp storeOp; |
| vk::VK_ATTACHMENT_LOAD_OP_DONT_CARE, // VkAttachmentLoadOp stencilLoadOp; |
| vk::VK_ATTACHMENT_STORE_OP_DONT_CARE, // VkAttachmentStoreOp stencilStoreOp; |
| vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| vk::VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // VkImageLayout finalLayout; |
| } |
| ); |
| |
| attachmentDescriptions.push_back(vk::VkAttachmentDescription |
| { |
| 0u, // VkAttachmentDescriptionFlags flags; |
| dsFormat, // VkFormat format; |
| vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| vk::VK_ATTACHMENT_LOAD_OP_CLEAR, // VkAttachmentLoadOp loadOp; |
| vk::VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp storeOp; |
| vk::VK_ATTACHMENT_LOAD_OP_CLEAR, // VkAttachmentLoadOp stencilLoadOp; |
| vk::VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp stencilStoreOp; |
| vk::VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| vk::VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, // VkImageLayout finalLayout; |
| }); |
| |
| const vk::VkRenderPassCreateInfo renderPassCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkRenderPassCreateFlags flags; |
| static_cast<deUint32>(attachmentDescriptions.size()), // deUint32 attachmentCount; |
| attachmentDescriptions.data(), // const VkAttachmentDescription* pAttachments; |
| 1u, // deUint32 subpassCount; |
| &subpassDescription, // const VkSubpassDescription* pSubpasses; |
| 0u, // deUint32 dependencyCount; |
| nullptr, // const VkSubpassDependency* pDependencies; |
| }; |
| const auto renderPass = vk::createRenderPass(vkd, device, &renderPassCreateInfo); |
| |
| // Framebuffers. |
| FramebufferVec framebuffers; |
| |
| DE_ASSERT(colorImageViews.size() == dsImageViews.size() * kNumColorAttachments); |
| for (size_t imgIdx = 0; imgIdx < dsImageViews.size(); ++imgIdx) |
| { |
| std::vector<vk::VkImageView> attachments; |
| for (deUint32 i = 0u; i < kNumColorAttachments; ++i) |
| attachments.push_back(colorImageViews[imgIdx * kNumColorAttachments + i].get()); |
| |
| attachments.push_back(dsImageViews[imgIdx].get()); |
| |
| const vk::VkFramebufferCreateInfo framebufferCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkFramebufferCreateFlags flags; |
| renderPass.get(), // VkRenderPass renderPass; |
| static_cast<deUint32>(attachments.size()), // deUint32 attachmentCount; |
| attachments.data(), // const VkImageView* pAttachments; |
| kFramebufferWidth, // deUint32 width; |
| kFramebufferHeight, // deUint32 height; |
| 1u, // deUint32 layers; |
| }; |
| |
| framebuffers.emplace_back(vk::createFramebuffer(vkd, device, &framebufferCreateInfo)); |
| } |
| |
| // Shader modules. |
| const auto vertModule = vk::createShaderModule(vkd, device, m_context.getBinaryCollection().get("vert"), 0u); |
| const auto fragModule = vk::createShaderModule(vkd, device, m_context.getBinaryCollection().get("frag"), 0u); |
| |
| // Input state. |
| const auto vertexBinding = vk::makeVertexInputBindingDescription(0u, kCoordsSize, vk::VK_VERTEX_INPUT_RATE_VERTEX); |
| const std::vector<vk::VkVertexInputAttributeDescription> vertexAttributes = { |
| vk::makeVertexInputAttributeDescription(0u, 0u, vk::VK_FORMAT_R32G32_SFLOAT, 0u) |
| }; |
| |
| const vk::VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineVertexInputStateCreateFlags flags; |
| 1u, // deUint32 vertexBindingDescriptionCount; |
| &vertexBinding, // const VkVertexInputBindingDescription* pVertexBindingDescriptions; |
| static_cast<deUint32>(vertexAttributes.size()), // deUint32 vertexAttributeDescriptionCount; |
| vertexAttributes.data(), // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions; |
| }; |
| |
| // Input assembly. |
| const vk::VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineInputAssemblyStateCreateFlags flags; |
| vk::VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, // VkPrimitiveTopology topology; |
| VK_FALSE, // VkBool32 primitiveRestartEnable; |
| }; |
| |
| // Viewport state. |
| const std::vector<vk::VkViewport> viewport { vk::makeViewport(kFramebufferWidth, kFramebufferHeight) }; |
| const std::vector<vk::VkRect2D> scissor { vk::makeRect2D(kFramebufferWidth, kFramebufferHeight) }; |
| |
| // Rasterization state. |
| const vk::VkPipelineRasterizationStateCreateInfo rasterizationStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineRasterizationStateCreateFlags flags; |
| VK_FALSE, // VkBool32 depthClampEnable; |
| VK_FALSE, // VkBool32 rasterizerDiscardEnable; |
| vk::VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode; |
| vk::VK_CULL_MODE_NONE, // VkCullModeFlags cullMode; |
| vk::VK_FRONT_FACE_COUNTER_CLOCKWISE, // VkFrontFace frontFace; |
| VK_FALSE, // VkBool32 depthBiasEnable; |
| 0.0f, // float depthBiasConstantFactor; |
| 0.0f, // float depthBiasClamp; |
| 0.0f, // float depthBiasSlopeFactor; |
| 1.0f, // float lineWidth; |
| }; |
| |
| // Multisample state. |
| const vk::VkPipelineMultisampleStateCreateInfo multisampleStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineMultisampleStateCreateFlags flags; |
| vk::VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits rasterizationSamples; |
| VK_FALSE, // VkBool32 sampleShadingEnable; |
| 0.0f, // float minSampleShading; |
| nullptr, // const VkSampleMask* pSampleMask; |
| VK_FALSE, // VkBool32 alphaToCoverageEnable; |
| VK_FALSE, // VkBool32 alphaToOneEnable; |
| }; |
| |
| // Depth/stencil state. |
| const vk::VkStencilOpState stencil = |
| { |
| vk::VK_STENCIL_OP_KEEP, // VkStencilOp failOp; |
| vk::VK_STENCIL_OP_KEEP, // VkStencilOp passOp; |
| vk::VK_STENCIL_OP_KEEP, // VkStencilOp depthFailOp; |
| vk::VK_COMPARE_OP_ALWAYS, // VkCompareOp compareOp; |
| 0xFFu, // deUint32 compareMask; |
| 0xFFu, // deUint32 writeMask; |
| 0u, // deUint32 reference; |
| }; |
| |
| const vk::VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineDepthStencilStateCreateFlags flags; |
| VK_TRUE, // VkBool32 depthTestEnable; |
| VK_TRUE, // VkBool32 depthWriteEnable; |
| vk::VK_COMPARE_OP_LESS, // VkCompareOp depthCompareOp; |
| VK_FALSE, // VkBool32 depthBoundsTestEnable; |
| VK_FALSE, // VkBool32 stencilTestEnable; |
| stencil, // VkStencilOpState front; |
| stencil, // VkStencilOpState back; |
| 0.0f, // float minDepthBounds; |
| 1.0f, // float maxDepthBounds; |
| }; |
| |
| // Dynamic state. Here we will set all states which have a dynamic value. |
| std::vector<vk::VkDynamicState> dynamicStates; |
| |
| if (m_testConfig.colorWriteEnableConfig.dynamicValue) |
| dynamicStates.push_back(vk::VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT); |
| |
| const vk::VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineDynamicStateCreateFlags flags; |
| static_cast<deUint32>(dynamicStates.size()), // deUint32 dynamicStateCount; |
| dynamicStates.data(), // const VkDynamicState* pDynamicStates; |
| }; |
| |
| std::vector<vk::VkPipelineColorBlendAttachmentState> colorBlendAttachmentState( |
| kNumColorAttachments, |
| vk::VkPipelineColorBlendAttachmentState |
| { |
| VK_FALSE, // VkBool32 blendEnable |
| vk::VK_BLEND_FACTOR_ZERO, // VkBlendFactor srcColorBlendFactor |
| vk::VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstColorBlendFactor |
| vk::VK_BLEND_OP_ADD, // VkBlendOp colorBlendOp |
| vk::VK_BLEND_FACTOR_ZERO, // VkBlendFactor srcAlphaBlendFactor |
| vk::VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstAlphaBlendFactor |
| vk::VK_BLEND_OP_ADD, // VkBlendOp alphaBlendOp |
| static_cast<vk::VkColorComponentFlags>( // VkColorComponentFlags colorWriteMask |
| (m_testConfig.channelMask.x() ? vk::VK_COLOR_COMPONENT_R_BIT : 0) |
| | (m_testConfig.channelMask.y() ? vk::VK_COLOR_COMPONENT_G_BIT : 0) |
| | (m_testConfig.channelMask.z() ? vk::VK_COLOR_COMPONENT_B_BIT : 0) |
| | (m_testConfig.channelMask.w() ? vk::VK_COLOR_COMPONENT_A_BIT : 0) |
| ) |
| } |
| ); |
| |
| const vk::VkPipelineColorWriteCreateInfoEXT colorWriteCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_COLOR_WRITE_CREATE_INFO_EXT, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| kNumColorAttachments, // deUint32 attachmentCount; |
| m_testConfig.colorWriteEnableConfig.staticValue.data() // const VkBool32* pColorWriteEnables; |
| }; |
| |
| const vk::VkPipelineColorBlendStateCreateInfo colorBlendStateCreateInfo = |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType |
| &colorWriteCreateInfo, // const void* pNext |
| 0u, // VkPipelineColorBlendStateCreateFlags flags |
| VK_FALSE, // VkBool32 logicOpEnable |
| vk::VK_LOGIC_OP_CLEAR, // VkLogicOp logicOp |
| kNumColorAttachments, // deUint32 attachmentCount |
| colorBlendAttachmentState.data(), // const VkPipelineColorBlendAttachmentState* pAttachments |
| { 0.0f, 0.0f, 0.0f, 0.0f } // float blendConstants[4] |
| }; |
| |
| vk::GraphicsPipelineWrapper staticPipeline (vkd, device, m_testConfig.pipelineConstructionType); |
| const bool bindStaticFirst = (kSequenceOrdering == SequenceOrdering::BETWEEN_PIPELINES || |
| kSequenceOrdering == SequenceOrdering::AFTER_PIPELINES || |
| kSequenceOrdering == SequenceOrdering::TWO_DRAWS_DYNAMIC); |
| const bool useStaticPipeline = (bindStaticFirst || kReversed); |
| |
| // Create static pipeline when needed. |
| if (useStaticPipeline) |
| { |
| staticPipeline.setupVertexInputStete(&vertexInputStateCreateInfo, &inputAssemblyStateCreateInfo) |
| .setupPreRasterizationShaderState(viewport, |
| scissor, |
| *pipelineLayout, |
| *renderPass, |
| 0u, |
| *vertModule, |
| &rasterizationStateCreateInfo) |
| .setupFragmentShaderState(*pipelineLayout, |
| *renderPass, |
| 0u, |
| *fragModule, |
| &depthStencilStateCreateInfo, |
| &multisampleStateCreateInfo) |
| .setupFragmentOutputState(*renderPass, 0u, &colorBlendStateCreateInfo, &multisampleStateCreateInfo) |
| .setMonolithicPipelineLayout(*pipelineLayout) |
| .buildPipeline(); |
| } |
| |
| // Create dynamic pipeline. |
| vk::GraphicsPipelineWrapper graphicsPipeline(vkd, device, m_testConfig.pipelineConstructionType);; |
| graphicsPipeline.setDynamicState(&dynamicStateCreateInfo) |
| .setupVertexInputStete(&vertexInputStateCreateInfo, &inputAssemblyStateCreateInfo) |
| .setupPreRasterizationShaderState(viewport, |
| scissor, |
| *pipelineLayout, |
| *renderPass, |
| 0u, |
| *vertModule, |
| &rasterizationStateCreateInfo) |
| .setupFragmentShaderState(*pipelineLayout, |
| *renderPass, |
| 0u, |
| *fragModule, |
| &depthStencilStateCreateInfo, |
| &multisampleStateCreateInfo) |
| .setupFragmentOutputState(*renderPass, 0u, &colorBlendStateCreateInfo, &multisampleStateCreateInfo) |
| .setMonolithicPipelineLayout(*pipelineLayout) |
| .buildPipeline(); |
| |
| // Command buffer. |
| const auto cmdPool = vk::makeCommandPool(vkd, device, queueIndex); |
| const auto cmdBufferPtr = vk::allocateCommandBuffer(vkd , device, cmdPool.get(), vk::VK_COMMAND_BUFFER_LEVEL_PRIMARY); |
| const auto cmdBuffer = cmdBufferPtr.get(); |
| |
| // Clear values. |
| std::vector<vk::VkClearValue> clearValues; |
| auto colorClearValue = vk::makeClearValueColor(m_testConfig.clearColorValue); |
| for (deUint32 i = 0u; i < kNumColorAttachments; ++i) |
| clearValues.push_back(colorClearValue); |
| clearValues.push_back(vk::makeClearValueDepthStencil(m_testConfig.clearDepthValue, 0u)); |
| |
| // Record command buffer. |
| vk::beginCommandBuffer(vkd, cmdBuffer); |
| |
| for (deUint32 iteration = 0u; iteration < kNumIterations; ++iteration) |
| { |
| // Maybe set dynamic state here. |
| if (kSequenceOrdering == SequenceOrdering::CMD_BUFFER_START) |
| { |
| setDynamicStates(m_testConfig, vkd, cmdBuffer); |
| } |
| |
| // Begin render pass. |
| vk::beginRenderPass(vkd, cmdBuffer, renderPass.get(), framebuffers[iteration].get(), vk::makeRect2D(kFramebufferWidth, kFramebufferHeight), static_cast<deUint32>(clearValues.size()), clearValues.data()); |
| |
| // Bind a static pipeline first if needed. |
| if (bindStaticFirst && iteration == 0u) |
| { |
| vkd.cmdBindPipeline(cmdBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, staticPipeline.getPipeline()); |
| } |
| |
| // Maybe set dynamic state here. |
| if (kSequenceOrdering == SequenceOrdering::BETWEEN_PIPELINES) |
| { |
| setDynamicStates(m_testConfig, vkd, cmdBuffer); |
| } |
| |
| // Bind dynamic pipeline. |
| if ((kSequenceOrdering != SequenceOrdering::TWO_DRAWS_DYNAMIC && |
| kSequenceOrdering != SequenceOrdering::TWO_DRAWS_STATIC) || |
| (kSequenceOrdering == SequenceOrdering::TWO_DRAWS_DYNAMIC && iteration > 0u) || |
| (kSequenceOrdering == SequenceOrdering::TWO_DRAWS_STATIC && iteration == 0u)) |
| { |
| vkd.cmdBindPipeline(cmdBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline.getPipeline()); |
| } |
| |
| if (kSequenceOrdering == SequenceOrdering::BEFORE_GOOD_STATIC || |
| (kSequenceOrdering == SequenceOrdering::TWO_DRAWS_DYNAMIC && iteration > 0u) || |
| (kSequenceOrdering == SequenceOrdering::TWO_DRAWS_STATIC && iteration == 0u)) |
| { |
| setDynamicStates(m_testConfig, vkd, cmdBuffer); |
| } |
| |
| // Bind a static pipeline last if needed. |
| if (kSequenceOrdering == SequenceOrdering::BEFORE_GOOD_STATIC || |
| (kSequenceOrdering == SequenceOrdering::TWO_DRAWS_STATIC && iteration > 0u)) |
| { |
| vkd.cmdBindPipeline(cmdBuffer, vk::VK_PIPELINE_BIND_POINT_GRAPHICS, staticPipeline.getPipeline()); |
| } |
| |
| // Push constants. |
| PushConstants pushConstants = |
| { |
| m_testConfig.meshParams.color, // tcu::Vec4 triangleColor; |
| m_testConfig.meshParams.depth, // float meshDepth; |
| m_testConfig.meshParams.scaleX, // float scaleX; |
| m_testConfig.meshParams.scaleY, // float scaleY; |
| m_testConfig.meshParams.offsetX, // float offsetX; |
| m_testConfig.meshParams.offsetY, // float offsetY; |
| }; |
| vkd.cmdPushConstants(cmdBuffer, pipelineLayout.get(), pushConstantStageFlags, 0u, static_cast<deUint32>(sizeof(pushConstants)), &pushConstants); |
| |
| // Maybe set dynamic state here. |
| if (kSequenceOrdering == SequenceOrdering::BEFORE_DRAW || kSequenceOrdering == SequenceOrdering::AFTER_PIPELINES) |
| { |
| setDynamicStates(m_testConfig, vkd, cmdBuffer); |
| } |
| |
| // Bind vertex buffer and draw. |
| vk::VkDeviceSize offset = 0ull; |
| vkd.cmdBindVertexBuffers(cmdBuffer, 0u, 1u, &vertBuffer.get(), &offset); |
| vkd.cmdDraw(cmdBuffer, 6u, 1u, 0u, 0u); |
| |
| vk::endRenderPass(vkd, cmdBuffer); |
| } |
| |
| vk::endCommandBuffer(vkd, cmdBuffer); |
| |
| // Submit commands. |
| vk::submitCommandsAndWait(vkd, device, queue, cmdBuffer); |
| |
| // Read result image aspects from the last used framebuffer. |
| const tcu::UVec2 renderSize(kFramebufferWidth, kFramebufferHeight); |
| |
| const int kWidth = static_cast<int>(kFramebufferWidth); |
| const int kHeight = static_cast<int>(kFramebufferHeight); |
| |
| const tcu::Vec4 kGood(0.0f, 1.0f, 0.0f, 1.0f); |
| const tcu::Vec4 kBad(1.0f, 0.0f, 0.0f, 1.0f); |
| |
| const tcu::TextureFormat errorFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8); |
| |
| bool colorMatchAll = true; |
| |
| // Check expected values. |
| auto nextAttachmentImage = colorImages.end() - kNumColorAttachments; |
| for (deUint32 attachmentIndex = 0u; attachmentIndex < kNumColorAttachments; ++attachmentIndex, ++nextAttachmentImage) |
| { |
| const auto colorBuffer = readColorAttachment(vkd, device, queue, queueIndex, allocator, (*nextAttachmentImage)->get(), kColorFormat, renderSize); |
| const auto colorAccess = colorBuffer->getAccess(); |
| |
| tcu::TextureLevel colorError (errorFormat, kWidth, kHeight); |
| const auto colorErrorAccess = colorError.getAccess(); |
| |
| bool colorMatch = true; |
| |
| for (int y = 0; y < kHeight; ++y) |
| for (int x = 0; x < kWidth; ++x) |
| { |
| const auto colorPixel = colorAccess.getPixel(x, y); |
| |
| bool match = tcu::boolAll(tcu::lessThan(tcu::absDiff(colorPixel, m_testConfig.expectedColor[attachmentIndex]), kColorThreshold)); |
| colorErrorAccess.setPixel((match ? kGood : kBad), x, y); |
| if (!match) |
| colorMatch = false; |
| } |
| |
| if (!colorMatch) |
| { |
| std::ostringstream desc; |
| desc << "Result color image and error mask for attachment #" << attachmentIndex; |
| logErrors(log, "Color", desc.str(), colorAccess, colorErrorAccess); |
| colorMatchAll = false; |
| } |
| } |
| |
| const auto depthBuffer = readDepthAttachment(vkd, device, queue, queueIndex, allocator, dsImages.back()->get(), dsFormat, renderSize); |
| const auto depthAccess = depthBuffer->getAccess(); |
| tcu::TextureLevel depthError(errorFormat, kWidth, kHeight); |
| const auto depthErrorAccess = depthError.getAccess(); |
| |
| const auto minDepth = m_testConfig.expectedDepth - 1.0e-07f; |
| const auto maxDepth = m_testConfig.expectedDepth + 1.0e-07f; |
| bool depthMatch = true; |
| |
| for (int y = 0; y < kHeight; ++y) |
| for (int x = 0; x < kWidth; ++x) |
| { |
| const auto depthPixel = depthAccess.getPixDepth(x, y); |
| bool match = de::inRange(depthPixel, minDepth, maxDepth); |
| depthErrorAccess.setPixel((match ? kGood : kBad), x, y); |
| if (!match) |
| depthMatch = false; |
| } |
| |
| if (!depthMatch) |
| { |
| logErrors(log, "Depth", "Result depth image and error mask", depthAccess, depthErrorAccess); |
| } |
| |
| if (!(colorMatchAll && depthMatch)) |
| { |
| return tcu::TestStatus::fail("Incorrect value found in attachments; please check logged images"); |
| } |
| |
| return tcu::TestStatus::pass("Pass"); |
| } |
| |
| template <typename VectorType> |
| VectorType MaskVector(const VectorType& valueIfMaskIsFalse, const VectorType& valueIfMaskIsTrue, const std::vector<bool>& mask, bool inverse = false) |
| { |
| DE_ASSERT(valueIfMaskIsFalse.size() == valueIfMaskIsTrue.size() && valueIfMaskIsFalse.size() == mask.size()); |
| |
| VectorType ret(mask.size()); |
| |
| for (size_t i = 0; i < mask.size(); ++i) |
| { |
| bool m = mask[i]; |
| if (inverse) |
| m = !m; |
| ret[i] = m ? valueIfMaskIsTrue[i] : valueIfMaskIsFalse[i]; |
| } |
| |
| return ret; |
| } |
| |
| void ApplyChannelMask(std::vector<tcu::Vec4>& meshColors, const tcu::BVec4& channelMask, const tcu::Vec4& clearColor) |
| { |
| for (auto&& attachmentColor : meshColors) |
| attachmentColor = tcu::Vec4( |
| channelMask.x() ? attachmentColor.x() : clearColor.x(), |
| channelMask.y() ? attachmentColor.y() : clearColor.y(), |
| channelMask.z() ? attachmentColor.z() : clearColor.z(), |
| channelMask.w() ? attachmentColor.w() : clearColor.w() |
| ); |
| } |
| |
| void AddSingleTestCaseStatic(const std::string& name, |
| const std::string& description, |
| vk::PipelineConstructionType pipelineConstructionType, |
| const std::vector<bool> mask, |
| const tcu::BVec4 channelMask, |
| bool inverse, |
| tcu::TestCaseGroup* orderingGroup, |
| tcu::TestContext& testCtx) |
| { |
| TestConfig config(pipelineConstructionType, SequenceOrdering::CMD_BUFFER_START); |
| |
| // Enable writes and expect the mesh color, or disable writes and expect the clear color. |
| |
| config.clearColorValue = tcu::Vec4(0.25f, 0.5f, 0.75f, 0.5f); |
| config.meshParams.color = tcu::Vec4(1.0f, 0.75f, 0.5f, 0.25f); |
| |
| const auto allVkFalse = Bool32Vec(kNumColorAttachments, VK_FALSE); |
| const auto allVkTrue = Bool32Vec(kNumColorAttachments, VK_TRUE); |
| |
| config.channelMask = channelMask; |
| |
| config.colorWriteEnableConfig.staticValue = MaskVector(allVkFalse, allVkTrue, mask, inverse); |
| |
| // Note colorWriteEnableConfig.dynamicValue is unset, defaults to an empty Maybe<T> |
| |
| std::vector<tcu::Vec4> meshColorsPerAttachment(kNumColorAttachments); |
| meshColorsPerAttachment[0] = config.meshParams.color; |
| for (deUint32 i = 1u; i < kNumColorAttachments; ++i) |
| meshColorsPerAttachment[i] = meshColorsPerAttachment[i - 1] * 0.5f; |
| |
| std::vector<tcu::Vec4> clearColorsPerAttachment(kNumColorAttachments, config.clearColorValue); |
| |
| ApplyChannelMask(meshColorsPerAttachment, channelMask, config.clearColorValue); |
| |
| config.expectedColor = MaskVector(clearColorsPerAttachment, meshColorsPerAttachment, mask, inverse); |
| |
| // Depth should always be written even when color is not |
| config.clearDepthValue = 0.5f; |
| config.meshParams.depth = 0.25f; |
| config.expectedDepth = 0.25f; |
| |
| orderingGroup->addChild(new ColorWriteEnableTest(testCtx, name, description, config)); |
| } |
| |
| void AddSingleTestCaseDynamic(const std::string& name, |
| const std::string& description, |
| vk::PipelineConstructionType pipelineConstructionType, |
| const std::vector<bool> mask, |
| const tcu::BVec4 channelMask, |
| bool inverse, |
| tcu::TestCaseGroup* orderingGroup, |
| tcu::TestContext& testCtx, |
| SequenceOrdering ordering) |
| { |
| TestConfig config(pipelineConstructionType, ordering); |
| |
| // Enable writes and expect the mesh color, or disable writes and expect the clear color. |
| |
| config.clearColorValue = tcu::Vec4(0.25f, 0.5f, 0.75f, 0.5f); |
| config.meshParams.color = tcu::Vec4(1.0f, 0.75f, 0.5f, 0.25f); |
| |
| const auto allVkFalse = Bool32Vec(kNumColorAttachments, VK_FALSE); |
| const auto allVkTrue = Bool32Vec(kNumColorAttachments, VK_TRUE); |
| |
| config.channelMask = channelMask; |
| |
| config.colorWriteEnableConfig.staticValue = inverse ? allVkTrue : allVkFalse; |
| config.colorWriteEnableConfig.dynamicValue = MaskVector(allVkFalse, allVkTrue, mask, inverse); |
| |
| std::vector<tcu::Vec4> meshColorsPerAttachment(kNumColorAttachments); |
| meshColorsPerAttachment[0] = config.meshParams.color; |
| for (deUint32 i = 1u; i < kNumColorAttachments; ++i) |
| meshColorsPerAttachment[i] = meshColorsPerAttachment[i - 1] * 0.5f; |
| |
| std::vector<tcu::Vec4> clearColorsPerAttachment(kNumColorAttachments, config.clearColorValue); |
| |
| ApplyChannelMask(meshColorsPerAttachment, channelMask, config.clearColorValue); |
| |
| config.expectedColor = MaskVector(clearColorsPerAttachment, meshColorsPerAttachment, mask, inverse); |
| |
| // Depth should always be written even when color is not |
| config.clearDepthValue = 0.5f; |
| config.meshParams.depth = 0.25f; |
| config.expectedDepth = 0.25f; |
| |
| orderingGroup->addChild(new ColorWriteEnableTest(testCtx, name, description, config)); |
| } |
| |
| } // anonymous namespace |
| |
| namespace |
| { |
| using namespace vk; |
| using namespace tcu; |
| |
| struct TestParams |
| { |
| deUint32 width; |
| deUint32 height; |
| VkFormat format; |
| deUint32 attachmentCount; |
| deUint32 attachmentMore; |
| bool setCweBeforePlBind; |
| bool colorWriteEnables; |
| PipelineConstructionType pct; |
| bool selectOptimalBlendableFormat (const InstanceInterface&, VkPhysicalDevice); |
| }; |
| |
| class ColorWriteEnable2Test : public vkt::TestCase |
| { |
| public: |
| ColorWriteEnable2Test (TestContext& testCtx, |
| const std::string& name, |
| const std::string& description, |
| const TestParams& testParams) |
| : vkt::TestCase (testCtx, name, description) |
| , m_params (testParams) { } |
| |
| virtual ~ColorWriteEnable2Test () = default; |
| |
| virtual void checkSupport (Context& context) const override; |
| virtual void initPrograms (SourceCollections& programCollection) const override; |
| virtual vkt::TestInstance* createInstance (Context& context) const override; |
| |
| private: |
| mutable TestParams m_params; |
| |
| }; |
| |
| class ColorWriteEnable2Instance : public vkt::TestInstance |
| { |
| public: |
| typedef std::vector<VkBool32> ColorWriteEnables; |
| struct Attachment |
| { |
| de::MovePtr<ImageWithMemory> image; |
| Move<VkImageView> view; |
| Attachment () = default; |
| DE_UNUSED_FUNCTION Attachment (Attachment&& other); |
| }; |
| struct Framebuffer |
| { |
| std::vector<Attachment> attachments; |
| Move<VkFramebuffer> framebuffer; |
| Framebuffer () = default; |
| Framebuffer (Framebuffer&& other); |
| }; |
| struct GraphicsPipelineWrapperEx : public GraphicsPipelineWrapper |
| { |
| GraphicsPipelineWrapperEx (const DeviceInterface& vkd, |
| const VkDevice dev, |
| const PipelineConstructionType pct) |
| : GraphicsPipelineWrapper (vkd, dev, pct) |
| , m_isDynamicColorWriteEnable (false) {} |
| bool isDynamicColorWriteEnable () const { return m_isDynamicColorWriteEnable; } |
| private: |
| friend class ColorWriteEnable2Instance; |
| bool m_isDynamicColorWriteEnable; |
| }; |
| ColorWriteEnable2Instance (Context& context, |
| const TestParams& testParams); |
| virtual ~ColorWriteEnable2Instance () = default; |
| |
| de::MovePtr<BufferWithMemory> createVerrtexBuffer () const; |
| Move<VkRenderPass> createRenderPass (deUint32 colorAttachmentCount) const; |
| Framebuffer createFramebuffer (VkRenderPass renderPass, |
| deUint32 colorAttachmentCount) const; |
| void setupAndBuildPipeline (GraphicsPipelineWrapperEx& owner, |
| VkPipelineLayout pipelineLayout, |
| VkRenderPass renderPass, |
| deUint32 colorAttachmentCount, |
| const ColorWriteEnables& colorWriteEnables, |
| float blendComp, |
| bool dynamic) const; |
| virtual TestStatus iterate () override; |
| bool verifyAttachment (const deUint32 attachmentIndex, |
| const deUint32 attachmentCount, |
| const ConstPixelBufferAccess& attachmentContent, |
| const ColorWriteEnables& colorWriteEnables, |
| const Vec4& background, |
| const float blendComp) const; |
| private: |
| const TestParams m_params; |
| const DeviceInterface& m_vkd; |
| const VkDevice m_device; |
| Allocator& m_allocator; |
| const Move<VkShaderModule> m_vertex; |
| const Move<VkShaderModule> m_fragment; |
| }; |
| |
| ColorWriteEnable2Instance::Attachment::Attachment (Attachment&& other) |
| : image (std::move(other.image)) |
| , view (std::move(other.view)) |
| { |
| } |
| ColorWriteEnable2Instance::Framebuffer::Framebuffer (Framebuffer&& other) |
| : attachments (std::move(other.attachments)) |
| , framebuffer (std::move(other.framebuffer)) |
| { |
| } |
| |
| bool TestParams::selectOptimalBlendableFormat (const InstanceInterface& vk, VkPhysicalDevice dev) |
| { |
| auto doesFormatMatch = [](const VkFormat fmt) -> bool |
| { |
| const auto tcuFmt = mapVkFormat(fmt); |
| return tcuFmt.order == TextureFormat::ChannelOrder::RGBA |
| || tcuFmt.order == TextureFormat::ChannelOrder::sRGBA; |
| }; |
| |
| VkFormatProperties2 props{}; |
| const VkFormatFeatureFlags flags = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
| | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
| | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; |
| for (int f = VK_FORMAT_R64G64B64A64_SFLOAT; f > 0; --f) |
| { |
| props.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; |
| props.pNext = nullptr; |
| props.formatProperties = {}; |
| const VkFormat fmt = static_cast<VkFormat>(f); |
| vk.getPhysicalDeviceFormatProperties2(dev, fmt, &props); |
| if (doesFormatMatch(fmt) && ((props.formatProperties.optimalTilingFeatures & flags) == flags)) |
| { |
| this->format = fmt; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void ColorWriteEnable2Test::checkSupport (Context& context) const |
| { |
| const auto& vki = context.getInstanceInterface(); |
| const auto physicalDevice = context.getPhysicalDevice(); |
| |
| if (m_params.colorWriteEnables) |
| { |
| context.requireDeviceFunctionality("VK_EXT_color_write_enable"); |
| } |
| |
| DE_ASSERT(m_params.attachmentCount >= 1); |
| auto maxColorAttachments = context.getDeviceProperties().limits.maxColorAttachments; |
| if ((m_params.attachmentCount + m_params.attachmentMore) > maxColorAttachments) |
| { |
| std::stringstream ss; |
| if (m_params.attachmentMore) |
| { |
| ss << "Sum of color attachments (" << m_params.attachmentCount << " + " << m_params.attachmentMore << ")"; |
| } |
| else |
| { |
| ss << "Color attachment count of " << m_params.attachmentCount; |
| } |
| ss << " exceeds maximum number of color attachments supported by device which is " << maxColorAttachments; |
| ss.flush(); |
| TCU_THROW(NotSupportedError, ss.str()); |
| } |
| |
| if ( ! m_params.selectOptimalBlendableFormat(vki, physicalDevice)) |
| TCU_THROW(NotSupportedError, "Required color image features not supported"); |
| |
| checkPipelineLibraryRequirements(vki, physicalDevice, m_params.pct); |
| } |
| |
| void ColorWriteEnable2Test::initPrograms (SourceCollections& programCollection) const |
| { |
| const char nl = '\n'; |
| const deUint32 ac = m_params.attachmentCount; |
| std::ostringstream vs; |
| std::ostringstream fs; |
| |
| vs << "#version 450" << nl |
| << "layout(location = 0) in vec4 position;" << nl |
| << "layout(location = 0) out flat int instance;" << nl |
| << "void main() {" << nl |
| << " gl_Position = vec4(position.xy, 0.0, 1.0);" << nl |
| << " instance = gl_InstanceIndex;" << nl |
| << "}" << nl; |
| programCollection.glslSources.add("vert") << glu::VertexSource(vs.str()); |
| |
| fs << "#version 450" << nl |
| << "layout(location = 0) in flat int attachments;" << nl |
| << "layout(location = 0) out vec4 colors[" << ac << "];" << nl |
| << "void main() {" << nl |
| << " for (int a = 0; a < attachments; ++a) {" << nl |
| << " float c = float(attachments - a);" << nl |
| << " colors[a] = vec4(pow(0.5, c));" << nl |
| << "}}" << nl; |
| programCollection.glslSources.add("frag") << glu::FragmentSource(fs.str()); |
| } |
| |
| TestInstance* ColorWriteEnable2Test::createInstance (Context& context) const |
| { |
| return new ColorWriteEnable2Instance(context, m_params); |
| } |
| |
| ColorWriteEnable2Instance::ColorWriteEnable2Instance (Context& context, const TestParams& testParams) |
| : vkt::TestInstance (context) |
| , m_params (testParams) |
| , m_vkd (context.getDeviceInterface()) |
| , m_device (context.getDevice()) |
| , m_allocator (context.getDefaultAllocator()) |
| , m_vertex (createShaderModule(m_vkd, m_device, context.getBinaryCollection().get("vert"))) |
| , m_fragment (createShaderModule(m_vkd, m_device, context.getBinaryCollection().get("frag"))) |
| { |
| } |
| |
| Move<VkRenderPass> ColorWriteEnable2Instance::createRenderPass (deUint32 colorAttachmentCount) const |
| { |
| const std::vector<VkAttachmentDescription> attachmentDescriptions( |
| colorAttachmentCount, |
| VkAttachmentDescription |
| { |
| 0u, // VkAttachmentDescriptionFlags flags; |
| m_params.format, // VkFormat format; |
| VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| VK_ATTACHMENT_LOAD_OP_CLEAR, // VkAttachmentLoadOp loadOp; |
| VK_ATTACHMENT_STORE_OP_STORE, // VkAttachmentStoreOp storeOp; |
| VK_ATTACHMENT_LOAD_OP_DONT_CARE, // VkAttachmentLoadOp stencilLoadOp; |
| VK_ATTACHMENT_STORE_OP_DONT_CARE, // VkAttachmentStoreOp stencilStoreOp; |
| VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // VkImageLayout finalLayout; |
| } |
| ); |
| |
| std::vector<VkAttachmentReference> colorAttachmentReference; |
| for (deUint32 i = 0u; i < colorAttachmentCount; ++i) |
| { |
| colorAttachmentReference.push_back(VkAttachmentReference |
| { |
| i, // deUint32 attachment; |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL // VkImageLayout layout; |
| } |
| ); |
| } |
| |
| const VkSubpassDescription subpassDescription |
| { |
| 0u, // VkSubpassDescriptionFlags flags; |
| VK_PIPELINE_BIND_POINT_GRAPHICS, // VkPipelineBindPoint pipelineBindPoint; |
| 0u, // deUint32 inputAttachmentCount; |
| nullptr, // const VkAttachmentReference* pInputAttachments; |
| colorAttachmentCount, // deUint32 colorAttachmentCount; |
| colorAttachmentReference.data(), // const VkAttachmentReference* pColorAttachments; |
| nullptr, // const VkAttachmentReference* pResolveAttachments; |
| nullptr, // const VkAttachmentReference* pDepthStencilAttachment; |
| 0u, // deUint32 preserveAttachmentCount; |
| nullptr, // const deUint32* pPreserveAttachments; |
| }; |
| |
| const VkRenderPassCreateInfo renderPassCreateInfo |
| { |
| VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkRenderPassCreateFlags flags; |
| colorAttachmentCount, // deUint32 attachmentCount; |
| attachmentDescriptions.data(), // const VkAttachmentDescription* pAttachments; |
| 1u, // deUint32 subpassCount; |
| &subpassDescription, // const VkSubpassDescription* pSubpasses; |
| 0u, // deUint32 dependencyCount; |
| nullptr, // const VkSubpassDependency* pDependencies; |
| }; |
| |
| return vk::createRenderPass(m_vkd, m_device, &renderPassCreateInfo); |
| } |
| |
| de::MovePtr<BufferWithMemory> ColorWriteEnable2Instance::createVerrtexBuffer () const |
| { |
| const std::vector<float> quad |
| { |
| -1.0f, -1.0f, 0.0f, 0.0f, |
| +1.0f, -1.0f, 0.0f, 0.0f, |
| -1.0f, +1.0f, 0.0f, 0.0f, |
| -1.0f, +1.0f, 0.0f, 0.0f, |
| +1.0f, -1.0f, 0.0f, 0.0f, |
| +1.0f, +1.0f, 0.0f, 0.0f |
| }; |
| |
| const auto vertDataSize = quad.size() * sizeof(float); |
| const auto vertBufferInfo = makeBufferCreateInfo(static_cast<VkDeviceSize>(vertDataSize), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); |
| de::MovePtr<BufferWithMemory> vertBuffer (new BufferWithMemory(m_vkd, m_device, m_allocator, vertBufferInfo, vk::MemoryRequirement::HostVisible)); |
| auto& alloc = vertBuffer->getAllocation(); |
| |
| deMemcpy(reinterpret_cast<char*>(alloc.getHostPtr()), quad.data(), vertDataSize); |
| flushAlloc(m_vkd, m_device, alloc); |
| |
| return vertBuffer; |
| } |
| |
| ColorWriteEnable2Instance::Framebuffer ColorWriteEnable2Instance::createFramebuffer (VkRenderPass renderPass, deUint32 colorAttachmentCount) const |
| { |
| const VkExtent3D extent { m_params.width, m_params.height, 1u }; |
| const VkImageUsageFlags imageUsage = (vk::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | vk::VK_IMAGE_USAGE_TRANSFER_SRC_BIT); |
| const auto imageSubresource = vk::makeImageSubresourceRange(vk::VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); |
| const deUint32 queueIndex = m_context.getUniversalQueueFamilyIndex(); |
| Allocator& allocator = m_context.getDefaultAllocator(); |
| |
| std::vector<Attachment> attachments (colorAttachmentCount); |
| std::vector<VkImageView> views (colorAttachmentCount); |
| |
| for (deUint32 i = 0; i < colorAttachmentCount; ++i) |
| { |
| auto& attachment = attachments[i]; |
| |
| const VkImageCreateInfo imageCreateInfo |
| { |
| VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkImageCreateFlags flags; |
| VK_IMAGE_TYPE_2D, // VkImageType imageType; |
| m_params.format, // VkFormat format; |
| extent, // VkExtent3D extent; |
| 1u, // deUint32 mipLevels; |
| 1u, // deUint32 arrayLayers; |
| VK_SAMPLE_COUNT_1_BIT, // VkSampleCountFlagBits samples; |
| VK_IMAGE_TILING_OPTIMAL, // VkImageTiling tiling; |
| imageUsage, // VkImageUsageFlags usage; |
| VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode; |
| 1u, // deUint32 queueFamilyIndexCount; |
| &queueIndex, // const deUint32* pQueueFamilyIndices; |
| VK_IMAGE_LAYOUT_UNDEFINED, // VkImageLayout initialLayout; |
| }; |
| attachment.image = de::MovePtr<ImageWithMemory>(new ImageWithMemory(m_vkd, m_device, allocator, imageCreateInfo, MemoryRequirement::Any)); |
| |
| attachment.view = makeImageView(m_vkd, m_device, **attachment.image, VK_IMAGE_VIEW_TYPE_2D, m_params.format, imageSubresource); |
| |
| views[i] = *attachment.view; |
| } |
| |
| const VkFramebufferCreateInfo framebufferCreateInfo |
| { |
| VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkFramebufferCreateFlags flags; |
| renderPass, // VkRenderPass renderPass; |
| colorAttachmentCount, // deUint32 attachmentCount; |
| views.data(), // const VkImageView* pAttachments; |
| m_params.width, // deUint32 width; |
| m_params.height, // deUint32 height; |
| 1u, // deUint32 layers; |
| }; |
| |
| Framebuffer result; |
| result.attachments = std::move(attachments); |
| result.framebuffer = ::vk::createFramebuffer(m_vkd, m_device, &framebufferCreateInfo); |
| |
| return result; |
| } |
| |
| void ColorWriteEnable2Instance::setupAndBuildPipeline (GraphicsPipelineWrapperEx& owner, |
| VkPipelineLayout pipelineLayout, |
| VkRenderPass renderPass, |
| deUint32 colorAttachmentCount, |
| const ColorWriteEnables& colorWriteEnables, |
| float blendComp, |
| bool dynamic) const |
| { |
| const std::vector<VkViewport> viewports { makeViewport(m_params.width, m_params.height) }; |
| const std::vector<VkRect2D> scissors { makeRect2D(m_params.width, m_params.height) }; |
| |
| const auto vertexBinding = makeVertexInputBindingDescription(0u, static_cast<deUint32>(4 * sizeof(float)), VK_VERTEX_INPUT_RATE_VERTEX); |
| const auto vertexAttrib = makeVertexInputAttributeDescription(0u, 0u, VK_FORMAT_R32G32B32A32_SFLOAT, 0u); |
| |
| const VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo |
| { |
| vk::VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineVertexInputStateCreateFlags flags; |
| 1u, // deUint32 vertexBindingDescriptionCount; |
| &vertexBinding, // const VkVertexInputBindingDescription* pVertexBindingDescriptions; |
| 1u, // deUint32 vertexAttributeDescriptionCount; |
| &vertexAttrib // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions; |
| }; |
| |
| const vk::VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo |
| { |
| VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineInputAssemblyStateCreateFlags flags; |
| VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, // VkPrimitiveTopology topology; |
| VK_FALSE, // VkBool32 primitiveRestartEnable; |
| }; |
| |
| const VkDynamicState cweDynamicStates[1] { VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT }; |
| const VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo |
| { |
| VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0u, // VkPipelineDynamicStateCreateFlags flags; |
| 1u, // deUint32 dynamicStateCount; |
| cweDynamicStates // const VkDynamicState* pDynamicStates; |
| }; |
| |
| DE_ASSERT(colorAttachmentCount <= colorWriteEnables.size()); |
| |
| std::vector<vk::VkPipelineColorBlendAttachmentState> colorBlendAttachmentStates( |
| colorAttachmentCount, |
| VkPipelineColorBlendAttachmentState |
| { |
| VK_TRUE, // VkBool32 blendEnable |
| VK_BLEND_FACTOR_CONSTANT_COLOR, // VkBlendFactor srcColorBlendFactor |
| VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstColorBlendFactor |
| VK_BLEND_OP_ADD, // VkBlendOp colorBlendOp |
| VK_BLEND_FACTOR_CONSTANT_ALPHA, // VkBlendFactor srcAlphaBlendFactor |
| VK_BLEND_FACTOR_ZERO, // VkBlendFactor dstAlphaBlendFactor |
| VK_BLEND_OP_ADD, // VkBlendOp alphaBlendOp |
| VkColorComponentFlags(0) // VkColorComponentFlags colorWriteMask |
| } |
| ); |
| for (deUint32 i = 0; i < colorAttachmentCount; ++i) |
| { |
| VkColorComponentFlags colorWriteMask( VK_COLOR_COMPONENT_R_BIT |
| | VK_COLOR_COMPONENT_G_BIT |
| | VK_COLOR_COMPONENT_B_BIT |
| | VK_COLOR_COMPONENT_A_BIT); |
| switch (i % 4) |
| { |
| case 0: colorWriteMask &= (~(VK_COLOR_COMPONENT_R_BIT)); break; |
| case 1: colorWriteMask &= (~(VK_COLOR_COMPONENT_G_BIT)); break; |
| case 2: colorWriteMask &= (~(VK_COLOR_COMPONENT_B_BIT)); break; |
| case 3: colorWriteMask &= (~(VK_COLOR_COMPONENT_A_BIT)); break; |
| } |
| colorBlendAttachmentStates[i].colorWriteMask = colorWriteMask; |
| colorBlendAttachmentStates[i].blendEnable = colorWriteEnables[i]; |
| } |
| |
| const VkPipelineColorWriteCreateInfoEXT colorWriteCreateInfo |
| { |
| VK_STRUCTURE_TYPE_PIPELINE_COLOR_WRITE_CREATE_INFO_EXT, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| colorAttachmentCount, // deUint32 attachmentCount; |
| colorWriteEnables.data() // const VkBool32* pColorWriteEnables; |
| }; |
| |
| const VkPipelineColorBlendStateCreateInfo colorBlendStateCreateInfo |
| { |
| VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType |
| &colorWriteCreateInfo, // const void* pNext |
| 0u, // VkPipelineColorBlendStateCreateFlags flags |
| VK_FALSE, // VkBool32 logicOpEnable |
| VK_LOGIC_OP_CLEAR, // VkLogicOp logicOp |
| colorAttachmentCount, // deUint32 attachmentCount |
| colorBlendAttachmentStates.data(), // const VkPipelineColorBlendAttachmentState* pAttachments |
| { blendComp, blendComp, blendComp, blendComp } // float blendConstants[4] |
| }; |
| |
| const bool cweAllowed = (dynamic && m_params.colorWriteEnables); |
| owner.m_isDynamicColorWriteEnable = cweAllowed; |
| |
| owner |
| .setDefaultRasterizationState() |
| .setDefaultDepthStencilState() |
| .setDefaultMultisampleState() |
| .setDynamicState(cweAllowed ? &dynamicStateCreateInfo : nullptr) |
| .setupVertexInputStete(&vertexInputStateCreateInfo, &inputAssemblyStateCreateInfo) |
| .setupPreRasterizationShaderState(viewports, scissors, pipelineLayout, renderPass, 0u, *m_vertex) |
| .setupFragmentShaderState(pipelineLayout, renderPass, 0u, *m_fragment) |
| .setupFragmentOutputState(renderPass, 0u, &colorBlendStateCreateInfo) |
| .setMonolithicPipelineLayout(pipelineLayout) |
| .buildPipeline(); |
| } |
| |
| bool ColorWriteEnable2Instance::verifyAttachment (const deUint32 attachmentIndex, |
| const deUint32 attachmentCount, |
| const ConstPixelBufferAccess& attachmentContent, |
| const ColorWriteEnables& colorWriteEnables, |
| const Vec4& background, |
| const float blendComp) const |
| { |
| const auto maskColor = [&](Vec4 color) -> Vec4 { |
| color[attachmentIndex % 4] = background[attachmentIndex % 4]; |
| return color; |
| }; |
| const Vec4 source (powf(0.5f, static_cast<float>(attachmentCount - attachmentIndex))); |
| const Vec4 expected = colorWriteEnables[attachmentIndex] ? maskColor(source * blendComp) : background; |
| deUint32 failures = 0; |
| |
| for (deUint32 y = 0; y < m_params.height; ++y) |
| { |
| for (deUint32 x = 0; x < m_params.width; ++x) |
| { |
| const auto result = attachmentContent.getPixel(x, y); |
| const float er = expected.x(); const float rr = result.x(); |
| const float eg = expected.y(); const float rg = result.y(); |
| const float eb = expected.z(); const float rb = result.z(); |
| const float ea = expected.w(); const float ra = result.w(); |
| if (rr != er || rg != eg || rb != eb || ra != ea) ++failures; |
| } |
| } |
| |
| return (0 == failures); |
| } |
| |
| TestStatus ColorWriteEnable2Instance::iterate (void) |
| { |
| const VkQueue queue = m_context.getUniversalQueue(); |
| const deUint32 queueIndex = m_context.getUniversalQueueFamilyIndex(); |
| const VkRect2D renderArea = makeRect2D(m_params.width, m_params.height); |
| const deUint32 attachmentCount = m_params.attachmentCount; |
| |
| const float blendComp = 0.5f; |
| const Vec4 background (0.75f, 0.75f, 0.75f, 0.75f); |
| std::vector<VkClearValue> clearValues (attachmentCount, makeClearValueColor(background)); |
| de::MovePtr<BufferWithMemory> vertexBuffer = createVerrtexBuffer(); |
| ColorWriteEnables writeEnables (attachmentCount + m_params.attachmentMore, VK_TRUE); |
| for (deUint32 i = 0; i < attachmentCount; ++i) writeEnables[i] = (i % 2) ? VK_TRUE : VK_FALSE; |
| |
| Move<VkPipelineLayout> pipelineLayout = makePipelineLayout(m_vkd, m_device, 0u, nullptr, 0u, nullptr); |
| std::vector<Move<VkRenderPass>> renderPasses; |
| std::vector<Framebuffer> framebuffers; |
| std::vector<GraphicsPipelineWrapperEx> pipelines; |
| for (deUint32 i = 0; i < attachmentCount; ++i) |
| { |
| renderPasses.emplace_back(createRenderPass(i+1)); |
| framebuffers.emplace_back(createFramebuffer(*renderPasses.back(), (i+1))); |
| |
| const bool dynamicColorWriteEnable = (((attachmentCount - i) % 2) == 1); |
| |
| // build dynamics and statics pipelines alternately in reverse order |
| pipelines.emplace_back(m_vkd, m_device, m_params.pct); |
| setupAndBuildPipeline(pipelines.back(), *pipelineLayout, *renderPasses[i], (i+1), writeEnables, blendComp, dynamicColorWriteEnable); |
| } |
| |
| |
| const VkImageSubresourceRange attachmentResource = makeImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u); |
| VkImageMemoryBarrier attachmentReady = makeImageMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
| VkImage(0), attachmentResource); |
| |
| Move<VkCommandPool> cmdPool = makeCommandPool(m_vkd, m_device, queueIndex); |
| Move<VkCommandBuffer> cmdBuff = allocateCommandBuffer(m_vkd, m_device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY); |
| |
| beginCommandBuffer(m_vkd, *cmdBuff); |
| m_vkd.cmdBindVertexBuffers(*cmdBuff, 0u, 1u, &vertexBuffer->get(), &static_cast<const VkDeviceSize&>(0)); |
| |
| for (deUint32 a = 0; a < attachmentCount; ++a) |
| { |
| if (m_params.setCweBeforePlBind) |
| { |
| if (pipelines[a].isDynamicColorWriteEnable()) |
| m_vkd.cmdSetColorWriteEnableEXT(*cmdBuff, static_cast<deUint32>(writeEnables.size()), writeEnables.data()); |
| m_vkd.cmdBindPipeline(*cmdBuff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[a].getPipeline()); |
| } |
| else |
| { |
| m_vkd.cmdBindPipeline(*cmdBuff, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[a].getPipeline()); |
| if (pipelines[a].isDynamicColorWriteEnable()) |
| m_vkd.cmdSetColorWriteEnableEXT(*cmdBuff, static_cast<deUint32>(writeEnables.size()), writeEnables.data()); |
| } |
| |
| beginRenderPass(m_vkd, *cmdBuff, *renderPasses[a], *framebuffers[a].framebuffer, renderArea, attachmentCount, clearValues.data()); |
| m_vkd.cmdDraw(*cmdBuff, 6u, 1u, 0u, (a + 1)); |
| endRenderPass(m_vkd, *cmdBuff); |
| |
| for (Attachment& attachment : framebuffers[a].attachments) |
| { |
| attachmentReady.image = **attachment.image; |
| m_vkd.cmdPipelineBarrier(*cmdBuff, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, |
| VK_DEPENDENCY_VIEW_LOCAL_BIT, 0, nullptr, 0, nullptr, 1u, &attachmentReady); |
| } |
| } |
| |
| endCommandBuffer(m_vkd, *cmdBuff); |
| submitCommandsAndWait(m_vkd, m_device, queue, *cmdBuff); |
| |
| deUint32 failureCount = 0; |
| for (deUint32 i = 0; i < attachmentCount; ++i) |
| for (deUint32 a = 0; a < (i+1); ++a) |
| { |
| const auto colorBuffer = readColorAttachment(m_vkd, m_device, queue, queueIndex, m_allocator, |
| **framebuffers.at(i).attachments.at(a).image, m_params.format, |
| UVec2(m_params.width, m_params.height)); |
| failureCount += verifyAttachment(a, (i+1), colorBuffer->getAccess(), writeEnables, background, blendComp) ? 0u : 1u; |
| } |
| |
| return (0u == failureCount) ? TestStatus::pass("") : TestStatus::fail(""); |
| } |
| |
| } // unnamed namespace |
| |
| tcu::TestCaseGroup* createColorWriteEnableTests (tcu::TestContext& testCtx, vk::PipelineConstructionType pct) |
| { |
| de::MovePtr<tcu::TestCaseGroup> colorWriteEnableGroup(new tcu::TestCaseGroup(testCtx, "color_write_enable", "Tests for VK_EXT_color_write_enable")); |
| |
| DE_ASSERT(kNumColorAttachments >= 2); |
| |
| std::vector<bool> mask_all (kNumColorAttachments, true); |
| std::vector<bool> mask_first (kNumColorAttachments, false); mask_first[0] = true; |
| std::vector<bool> mask_second (kNumColorAttachments, false); mask_second[1] = true; |
| std::vector<bool> mask_last (kNumColorAttachments, false); mask_last.back() = true; |
| std::vector<bool> mask_first_and_second (kNumColorAttachments, false); mask_first_and_second[0] = mask_first_and_second[1] = true; |
| std::vector<bool> mask_second_and_last (kNumColorAttachments, false); mask_second_and_last[1] = mask_second_and_last.back() = true; |
| |
| // Test cases for channel enables |
| static const struct |
| { |
| tcu::BVec4 enabledChannels; |
| std::string name; |
| std::string desc; |
| } kChannelCases[] = |
| { |
| { tcu::BVec4(true, true, true, true), "all_channels", "Enable all channels in colorWriteMask"}, |
| { tcu::BVec4(true, false, false, false), "red_channel", "Red channel enabled in colorWriteMask"}, |
| { tcu::BVec4(false, true, false, false), "green_channel", "Green channel enabled in colorWriteMask"}, |
| { tcu::BVec4(false, false, true, false), "blue_channel", "Blue channel enabled in colorWriteMask"}, |
| { tcu::BVec4(false, false, false, true), "alpha_channel", "Alpha channel enabled in colorWriteMask"}, |
| { tcu::BVec4(false, false, false, false), "no_channels", "Disable all channels in colorWriteMask"}, |
| }; |
| |
| // Test cases for the dynamic state |
| static const struct |
| { |
| SequenceOrdering ordering; |
| std::string name; |
| std::string desc; |
| } kOrderingCases[] = |
| { |
| { SequenceOrdering::CMD_BUFFER_START, "cmd_buffer_start", "Dynamic state set after command buffer start" }, |
| { SequenceOrdering::BEFORE_DRAW, "before_draw", "Dynamic state set just before drawing" }, |
| { SequenceOrdering::BETWEEN_PIPELINES, "between_pipelines", "Dynamic after a pipeline with static states has been bound and before a pipeline with dynamic states has been bound" }, |
| { SequenceOrdering::AFTER_PIPELINES, "after_pipelines", "Dynamic state set after both a static-state pipeline and a second dynamic-state pipeline have been bound" }, |
| { SequenceOrdering::BEFORE_GOOD_STATIC, "before_good_static", "Dynamic state set after a dynamic pipeline has been bound and before a second static-state pipeline with the right values has been bound" }, |
| { SequenceOrdering::TWO_DRAWS_DYNAMIC, "two_draws_dynamic", "Bind bad static pipeline and draw, followed by binding correct dynamic pipeline and drawing again" }, |
| { SequenceOrdering::TWO_DRAWS_STATIC, "two_draws_static", "Bind bad dynamic pipeline and draw, followed by binding correct static pipeline and drawing again" }, |
| }; |
| |
| for (int channelCaseIdx = 0; channelCaseIdx < DE_LENGTH_OF_ARRAY(kChannelCases); ++channelCaseIdx) |
| { |
| const auto& kChannelCase = kChannelCases[channelCaseIdx]; |
| de::MovePtr<tcu::TestCaseGroup> channelGroup(new tcu::TestCaseGroup(testCtx, kChannelCase.name.c_str(), kChannelCase.desc.c_str())); |
| |
| for (int orderingIdx = 0; orderingIdx < DE_LENGTH_OF_ARRAY(kOrderingCases); ++orderingIdx) |
| { |
| const auto& kOrderingCase = kOrderingCases[orderingIdx]; |
| const auto& kOrdering = kOrderingCase.ordering; |
| |
| de::MovePtr<tcu::TestCaseGroup> orderingGroup(new tcu::TestCaseGroup(testCtx, kOrderingCase.name.c_str(), kOrderingCase.desc.c_str())); |
| |
| AddSingleTestCaseDynamic("enable_all", "Dynamically enable writes to all color attachments", pct, mask_all, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("enable_first", "Dynamically enable writes to the first color attachment", pct, mask_first, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("enable_second", "Dynamically enable writes to the second color attachment", pct, mask_second, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("enable_last", "Dynamically enable writes to the last color attachment", pct, mask_last, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("enable_first_and_second", "Dynamically enable writes to the first two color attachments", pct, mask_first_and_second, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("enable_second_and_last", "Dynamically enable writes to the second and last color attachments", pct, mask_second_and_last, kChannelCase.enabledChannels, false, orderingGroup.get(), testCtx, kOrdering); |
| |
| AddSingleTestCaseDynamic("disable_all", "Dynamically disable writes to all color attachments", pct, mask_all, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("disable_first", "Dynamically disable writes to the first color attachment", pct, mask_first, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("disable_second", "Dynamically disable writes to the second color attachment", pct, mask_second, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("disable_last", "Dynamically disable writes to the last color attachment", pct, mask_last, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("disable_first_and_second", "Dynamically disable writes to the first two color attachments", pct, mask_first_and_second, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| AddSingleTestCaseDynamic("disable_second_and_last", "Dynamically disable writes to the second and last color attachments", pct, mask_second_and_last, kChannelCase.enabledChannels, true, orderingGroup.get(), testCtx, kOrdering); |
| |
| channelGroup->addChild(orderingGroup.release()); |
| } |
| |
| // Test cases for the static state |
| // Note that the dynamic state test cases above also test pipelines with static state (when ordering is BEFORE_GOOD_STATIC and TWO_DRAWS_STATIC). |
| // However they all bind a pipeline with the static state AFTER binding a pipeline with the dynamic state. |
| // The only case missing, then, is static state alone without any dynamic pipelines in the same render pass or command buffer. |
| de::MovePtr<tcu::TestCaseGroup> staticOrderingGroup(new tcu::TestCaseGroup(testCtx, "static", "Static state set")); |
| |
| AddSingleTestCaseStatic("enable_all", "Statically enable writes to all color attachments", pct, mask_all, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("enable_first", "Statically enable writes to the first color attachment", pct, mask_first, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("enable_second", "Statically enable writes to the second color attachment", pct, mask_second, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("enable_last", "Statically enable writes to the last color attachment", pct, mask_last, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("enable_first_and_second", "Statically enable writes to the first two color attachments", pct, mask_first_and_second, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("enable_second_and_last", "Statically enable writes to the second and last color attachments", pct, mask_second_and_last, kChannelCase.enabledChannels, false, staticOrderingGroup.get(), testCtx); |
| |
| AddSingleTestCaseStatic("disable_all", "Statically disable writes to all color attachments", pct, mask_all, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("disable_first", "Statically disable writes to the first color attachment", pct, mask_first, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("disable_second", "Statically disable writes to the second color attachment", pct, mask_second, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("disable_last", "Statically disable writes to the last color attachment", pct, mask_last, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("disable_first_and_second", "Statically disable writes to the first two color attachments", pct, mask_first_and_second, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| AddSingleTestCaseStatic("disable_second_and_last", "Statically disable writes to the second and last color attachments", pct, mask_second_and_last, kChannelCase.enabledChannels, true, staticOrderingGroup.get(), testCtx); |
| |
| channelGroup->addChild(staticOrderingGroup.release()); |
| |
| colorWriteEnableGroup->addChild(channelGroup.release()); |
| } |
| |
| return colorWriteEnableGroup.release(); |
| } |
| |
| tcu::TestCaseGroup* createColorWriteEnable2Tests (tcu::TestContext& testCtx, vk::PipelineConstructionType pct) |
| { |
| const deUint32 attachmentCounts[] { 3,4,5 }; |
| const deUint32 attachentMores[] { 0,1,2,3 }; |
| |
| std::pair<bool, const char*> |
| static const setCweMoments[] |
| { |
| { true, "cwe_before_bind" }, |
| { false, "cwe_after_bind" } |
| }; |
| |
| |
| tcu::TestCaseGroup* rootGroup = new tcu::TestCaseGroup(testCtx, "color_write_enable_maxa", "Tests for VK_EXT_color_write_enable"); |
| |
| for (const auto& setCweMoment : setCweMoments) |
| { |
| tcu::TestCaseGroup* setCweGroup = new tcu::TestCaseGroup(testCtx, setCweMoment.second, "A moment when cmdSetColorWriteEnableEXT() is called"); |
| |
| for (auto attachmentCount : attachmentCounts) |
| { |
| for (auto attachentMore : attachentMores) |
| { |
| const std::string title = "attachments" + std::to_string(attachmentCount) + "_more" + std::to_string(attachentMore); |
| |
| TestParams p; |
| p.format = VK_FORMAT_UNDEFINED; |
| p.width = 32; |
| p.height = 32; |
| p.setCweBeforePlBind = setCweMoment.first; |
| p.colorWriteEnables = true; |
| p.attachmentCount = attachmentCount; |
| p.attachmentMore = attachentMore; |
| p.pct = pct; |
| setCweGroup->addChild(new ColorWriteEnable2Test(testCtx, title, "", p)); |
| } |
| } |
| rootGroup->addChild(setCweGroup); |
| } |
| return rootGroup; |
| } |
| |
| } // pipeline |
| } // vkt |