#ifndef _VKTSPVASMGRAPHICSSHADERTESTUTIL_HPP #define _VKTSPVASMGRAPHICSSHADERTESTUTIL_HPP /*------------------------------------------------------------------------- * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2017 Google 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 Graphics pipeline and helper functions for SPIR-V assembly tests *//*--------------------------------------------------------------------*/ #include "tcuCommandLine.hpp" #include "tcuRGBA.hpp" #include "vkPrograms.hpp" #include "vktSpvAsmComputeShaderTestUtil.hpp" #include "vktSpvAsmUtils.hpp" #include "vktTestCaseUtil.hpp" #include "deRandom.hpp" #include "deSharedPtr.hpp" #include #include #include #include namespace vkt { namespace SpirVAssembly { typedef vk::Unique BufferHandleUp; typedef vk::Unique ImageHandleUp; typedef vk::Unique ImageViewHandleUp; typedef vk::Unique SamplerHandleUp; typedef de::SharedPtr BufferHandleSp; typedef de::SharedPtr ImageHandleSp; typedef de::SharedPtr ImageViewHandleSp; typedef de::SharedPtr SamplerHandleSp; typedef vk::Unique ModuleHandleUp; typedef de::SharedPtr ModuleHandleSp; typedef std::pair EntryToStage; typedef std::map > ModuleMap; typedef std::map StageToSpecConstantMap; enum NumberType { NUMBERTYPE_INT32, NUMBERTYPE_UINT32, NUMBERTYPE_FLOAT32, NUMBERTYPE_END32, // Marks the end of 32-bit scalar types NUMBERTYPE_INT16, NUMBERTYPE_UINT16, NUMBERTYPE_FLOAT16, NUMBERTYPE_END16, // Marks the end of 16-bit scalar types NUMBERTYPE_FLOAT64, }; typedef enum RoundingModeFlags_e { ROUNDINGMODE_RTE = 0x1, // Round to nearest even ROUNDINGMODE_RTZ = 0x2, // Round to zero } RoundingModeFlags; typedef bool (*GraphicsVerifyBinaryFunc) (const ProgramBinary& binary); // Resources used by graphics-pipeline-based tests. struct GraphicsResources { // Resources used as inputs. std::vector inputs; // Input resource format if used VkFormat inputFormat; // Resources used as outputs. The data supplied will be used as // the expected outputs for the corresponding bindings by default. // If other behaviors are needed, please provide a custom verifyIO. std::vector outputs; // If null, a default verification will be performed by comparing the // memory pointed to by outputAllocations and the contents of // expectedOutputs. Otherwise the function pointed to by verifyIO will // be called. If true is returned, then the test case is assumed to // have passed, if false is returned, then the test case is assumed // to have failed. VerifyIOFunc verifyIO; GraphicsVerifyBinaryFunc verifyBinary; SpirvVersion spirvVersion; GraphicsResources() : inputFormat (VK_FORMAT_R32G32B32A32_SFLOAT) , verifyIO (DE_NULL) , verifyBinary (DE_NULL) , spirvVersion (SPIRV_VERSION_1_0) {} }; // Interface data type. struct IFDataType { IFDataType (deUint32 numE, NumberType elementT) : numElements (numE) , elementType (elementT) { DE_ASSERT(numE > 0 && numE < 5); DE_ASSERT(elementT != NUMBERTYPE_END32 && elementT != NUMBERTYPE_END16); } IFDataType (const IFDataType& that) : numElements (that.numElements) , elementType (that.elementType) {} deUint32 getElementNumBytes (void) const; deUint32 getNumBytes (void) const { return numElements * getElementNumBytes(); } vk::VkFormat getVkFormat (void) const; tcu::TextureFormat getTextureFormat (void) const; std::string str (void) const; bool elementIs32bit (void) const { return elementType < NUMBERTYPE_END32; } bool elementIs64bit (void) const { return elementType > NUMBERTYPE_END16; } bool isVector (void) const { return numElements > 1; } deUint32 numElements; NumberType elementType; }; typedef std::pair Interface; // Interface variables used by graphics-pipeline-based tests. class GraphicsInterfaces { public: GraphicsInterfaces () : rndMode (static_cast(0)) {} GraphicsInterfaces (const GraphicsInterfaces& that) : inputs (that.inputs) , outputs (that.outputs) , rndMode (that.rndMode) {} void setInputOutput (const Interface& input, const Interface& output) { inputs.clear(); outputs.clear(); inputs.push_back(input); outputs.push_back(output); } const IFDataType& getInputType (void) const { DE_ASSERT(inputs.size() == 1); return inputs.front().first; } const IFDataType& getOutputType (void) const { DE_ASSERT(outputs.size() == 1); return outputs.front().first; } const BufferSp& getInputBuffer (void) const { DE_ASSERT(inputs.size() == 1); return inputs.front().second; } const BufferSp& getOutputBuffer (void) const { DE_ASSERT(outputs.size() == 1); return outputs.front().second; } bool empty (void) const { return inputs.size() == 0; } void setRoundingMode (RoundingModeFlags flag) { rndMode = flag; } RoundingModeFlags getRoundingMode (void) const { return rndMode; } private: // vector acts as a null-able Interface here. Canonically we should use // std::unique_ptr, but sadly we cannot leverage C++11 in dEQP. dEQP has its own // de::UniquePtr, but still cumbersome to use in InstanceContext and do copies // at various places. // Public methods should make sure that there are less than two elements in both // members and both members have the same number of elements. std::vector inputs; std::vector outputs; RoundingModeFlags rndMode; }; struct PushConstants { public: PushConstants (void) {} PushConstants (const PushConstants& that) : pcs (that.pcs) {} void setPushConstant (const BufferSp& pc) { pcs.clear(); pcs.push_back(pc); } bool empty (void) const { return pcs.empty(); } const BufferSp& getBuffer(void) const { DE_ASSERT(pcs.size() == 1); return pcs[0]; } private: // Right now we only support one field in the push constant block. std::vector pcs; }; // Returns the corresponding buffer usage flag bit for the given descriptor type. VkBufferUsageFlagBits getMatchingBufferUsageFlagBit (VkDescriptorType dType); // Context for a specific test instantiation. For example, an instantiation // may test colors yellow/magenta/cyan/mauve in a tesselation shader // with an entry point named 'main_to_the_main' struct InstanceContext { // Map of modules to what entry_points we care to use from those modules. ModuleMap moduleMap; tcu::RGBA inputColors[4]; tcu::RGBA outputColors[4]; // Concrete SPIR-V code to test via boilerplate specialization. std::map testCodeFragments; StageToSpecConstantMap specConstants; bool hasTessellation; vk::VkShaderStageFlagBits requiredStages; std::vector requiredDeviceExtensions; VulkanFeatures requestedFeatures; PushConstants pushConstants; // Specifies the (one or more) stages that use a customized shader code. VkShaderStageFlags customizedStages; // Possible resources used by the graphics pipeline. // If it is not empty, a single descriptor set (number 0) will be allocated // to point to all resources specified. Binding numbers are allocated in // accord with the resources' order in the vector; outputs are allocated // after inputs. GraphicsResources resources; // Possible interface variables use by the graphics pipeline. // If it is not empty, input/output variables will be set up for shader stages // in the test. Both the input and output variable will take location #2 in the // pipeline for all stages, except that the output variable in the fragment // stage will take location #1. GraphicsInterfaces interfaces; qpTestResult failResult; std::string failMessageTemplate; //!< ${reason} in the template will be replaced with a detailed failure message bool renderFullSquare; // Forces to render whole render area, though with background color bool splitRenderArea; // Split render into multiple submissions. InstanceContext (const tcu::RGBA (&inputs)[4], const tcu::RGBA (&outputs)[4], const std::map& testCodeFragments_, const StageToSpecConstantMap& specConstants_, const PushConstants& pushConsants_, const GraphicsResources& resources_, const GraphicsInterfaces& interfaces_, const std::vector& extensions_, VulkanFeatures vulkanFeatures_, VkShaderStageFlags customizedStages_); InstanceContext (const InstanceContext& other); std::string getSpecializedFailMessage (const std::string& failureReason); }; enum ShaderTask { SHADER_TASK_NONE = 0, SHADER_TASK_NORMAL, SHADER_TASK_UNUSED_VAR, SHADER_TASK_UNUSED_FUNC, SHADER_TASK_LAST }; enum ShaderTaskIndex { SHADER_TASK_INDEX_VERTEX = 0, SHADER_TASK_INDEX_GEOMETRY = 1, SHADER_TASK_INDEX_TESS_CONTROL = 2, SHADER_TASK_INDEX_TESS_EVAL = 3, SHADER_TASK_INDEX_FRAGMENT = 4, SHADER_TASK_INDEX_LAST = 5 }; typedef ShaderTask ShaderTaskArray[SHADER_TASK_INDEX_LAST]; struct UnusedVariableContext { InstanceContext instanceContext; ShaderTaskArray shaderTasks; VariableLocation variableLocation; UnusedVariableContext(const InstanceContext& ctx, const ShaderTaskArray& tasks, const VariableLocation& location) : instanceContext(ctx), shaderTasks(), variableLocation(location) { deMemcpy(shaderTasks, tasks, sizeof(tasks)); } }; // A description of a shader to be used for a single stage of the graphics pipeline. struct ShaderElement { // The module that contains this shader entrypoint. std::string moduleName; // The name of the entrypoint. std::string entryName; // Which shader stage this entry point represents. vk::VkShaderStageFlagBits stage; ShaderElement (const std::string& moduleName_, const std::string& entryPoint_, vk::VkShaderStageFlagBits shaderStage_); }; template const std::string numberToString (T number) { std::stringstream ss; ss << number; return ss.str(); } void getDefaultColors (tcu::RGBA (&colors)[4]); void getHalfColorsFullAlpha (tcu::RGBA (&colors)[4]); void getInvertedDefaultColors (tcu::RGBA (&colors)[4]); // Creates fragments that specialize into a simple pass-through shader (of any kind). std::map passthruFragments (void); // Creates a combined shader module based on VkShaderStageFlagBits defined in InstanceContext. void createCombinedModule (vk::SourceCollections& dst, InstanceContext ctx); // Creates shaders with unused variables based on the UnusedVariableContext. void createUnusedVariableModules (vk::SourceCollections& dst, UnusedVariableContext ctx); // This has two shaders of each stage. The first // is a passthrough, the second inverts the color. void createMultipleEntries (vk::SourceCollections& dst, InstanceContext); // Turns a vector of ShaderElements into an instance-context by setting up the mapping of modules // to their contained shaders and stages. The inputs and expected outputs are given by inputColors // and outputColors InstanceContext createInstanceContext (const std::vector& elements, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const StageToSpecConstantMap& specConstants, const PushConstants& pushConstants, const GraphicsResources& resources, const GraphicsInterfaces& interfaces, const std::vector& extensions, VulkanFeatures vulkanFeatures, VkShaderStageFlags customizedStages, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()); // Same as above but using a statically sized array. template inline InstanceContext createInstanceContext (const ShaderElement (&elements)[N], const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const StageToSpecConstantMap& specConstants, const PushConstants& pushConstants, const GraphicsResources& resources, const GraphicsInterfaces& interfaces, const std::vector& extensions, VulkanFeatures vulkanFeatures, VkShaderStageFlags customizedStages, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()) { std::vector elementsVector(elements, elements + N); return createInstanceContext(elementsVector, inputColors, outputColors, testCodeFragments, specConstants, pushConstants, resources, interfaces, extensions, vulkanFeatures, customizedStages, failResult, failMessageTemplate); } // The same as createInstanceContext above, without extensions, spec constants, and resources. InstanceContext createInstanceContext (const std::vector& elements, tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments); // Same as above, but using a statically sized array. template inline InstanceContext createInstanceContext (const ShaderElement (&elements)[N], tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments) { std::vector elementsVector(elements, elements + N); return createInstanceContext(elementsVector, inputColors, outputColors, testCodeFragments); } // The same as createInstanceContext above, but with default colors. InstanceContext createInstanceContext (const std::vector& elements, const std::map& testCodeFragments); // Same as above, but using a statically sized array. template inline InstanceContext createInstanceContext (const ShaderElement (&elements)[N], const std::map& testCodeFragments) { std::vector elementsVector(elements, elements + N); return createInstanceContext(elementsVector, testCodeFragments); } // Create an unused variable context for the given combination. UnusedVariableContext createUnusedVariableContext(const ShaderTaskArray& shaderTasks, const VariableLocation& location); void addShaderCodeCustomVertex (vk::SourceCollections& dst, InstanceContext& context, const SpirVAsmBuildOptions* spirVAsmBuildOptions); void addShaderCodeCustomTessControl (vk::SourceCollections& dst, InstanceContext& context, const SpirVAsmBuildOptions* spirVAsmBuildOptions); void addShaderCodeCustomTessEval (vk::SourceCollections& dst, InstanceContext& context, const SpirVAsmBuildOptions* spirVAsmBuildOptions); void addShaderCodeCustomGeometry (vk::SourceCollections& dst, InstanceContext& context, const SpirVAsmBuildOptions* spirVAsmBuildOptions); void addShaderCodeCustomFragment (vk::SourceCollections& dst, InstanceContext& context, const SpirVAsmBuildOptions* spirVAsmBuildOptions); void createTestForStage (vk::VkShaderStageFlagBits stage, const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const SpecConstants& specConstants, const PushConstants& pushConstants, const GraphicsResources& resources, const GraphicsInterfaces& interfaces, const std::vector& extensions, VulkanFeatures vulkanFeatures, tcu::TestCaseGroup* tests, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string(), const bool renderFullSquare = false, const bool splitRenderArea = false); void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const SpecConstants& specConstants, const PushConstants& pushConstants, const GraphicsResources& resources, const GraphicsInterfaces& interfaces, const std::vector& extensions, VulkanFeatures vulkanFeatures, tcu::TestCaseGroup* tests, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string(), const bool splitRenderArea = false); inline void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, tcu::TestCaseGroup* tests, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()) { SpecConstants noSpecConstants; PushConstants noPushConstants; GraphicsResources noResources; GraphicsInterfaces noInterfaces; std::vector noExtensions; createTestsForAllStages( name, inputColors, outputColors, testCodeFragments, noSpecConstants, noPushConstants, noResources, noInterfaces, noExtensions, VulkanFeatures(), tests, failResult, failMessageTemplate); } inline void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const SpecConstants& specConstants, tcu::TestCaseGroup* tests, const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()) { PushConstants noPushConstants; GraphicsResources noResources; GraphicsInterfaces noInterfaces; std::vector noExtensions; createTestsForAllStages( name, inputColors, outputColors, testCodeFragments, specConstants, noPushConstants, noResources, noInterfaces, noExtensions, VulkanFeatures(), tests, failResult, failMessageTemplate); } inline void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const GraphicsResources& resources, const std::vector& extensions, tcu::TestCaseGroup* tests, VulkanFeatures vulkanFeatures = VulkanFeatures(), const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string(), const bool splitRenderArea = false) { SpecConstants noSpecConstants; PushConstants noPushConstants; GraphicsInterfaces noInterfaces; createTestsForAllStages( name, inputColors, outputColors, testCodeFragments, noSpecConstants, noPushConstants, resources, noInterfaces, extensions, vulkanFeatures, tests, failResult, failMessageTemplate, splitRenderArea ); } inline void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const GraphicsInterfaces interfaces, const std::vector& extensions, tcu::TestCaseGroup* tests, VulkanFeatures vulkanFeatures = VulkanFeatures(), const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()) { GraphicsResources noResources; SpecConstants noSpecConstants; PushConstants noPushConstants; createTestsForAllStages( name, inputColors, outputColors, testCodeFragments, noSpecConstants, noPushConstants, noResources, interfaces, extensions, vulkanFeatures, tests, failResult, failMessageTemplate); } inline void createTestsForAllStages (const std::string& name, const tcu::RGBA (&inputColors)[4], const tcu::RGBA (&outputColors)[4], const std::map& testCodeFragments, const PushConstants& pushConstants, const GraphicsResources& resources, const std::vector& extensions, tcu::TestCaseGroup* tests, VulkanFeatures vulkanFeatures = VulkanFeatures(), const qpTestResult failResult = QP_TEST_RESULT_FAIL, const std::string& failMessageTemplate = std::string()) { SpecConstants noSpecConstants; GraphicsInterfaces noInterfaces; createTestsForAllStages( name, inputColors, outputColors, testCodeFragments, noSpecConstants, pushConstants, resources, noInterfaces, extensions, vulkanFeatures, tests, failResult, failMessageTemplate); } // Sets up and runs a Vulkan pipeline, then spot-checks the resulting image. // Feeds the pipeline a set of colored triangles, which then must occur in the // rendered image. The surface is cleared before executing the pipeline, so // whatever the shaders draw can be directly spot-checked. tcu::TestStatus runAndVerifyDefaultPipeline (Context& context, InstanceContext instance); // Use the instance context in the UnusedVariableContext to run the function above. tcu::TestStatus runAndVerifyUnusedVariablePipeline (Context &context, UnusedVariableContext unusedVariableContext); // Adds a new test to group using custom fragments for the tessellation-control // stage and passthrough fragments for all other stages. Uses default colors // for input and expected output. void addTessCtrlTest (tcu::TestCaseGroup* group, const char* name, const std::map& fragments); // Given the original 32-bit float value, computes the corresponding 16-bit // float value under the given rounding mode flags and compares with the // returned 16-bit float value. Returns true if they are considered as equal. // // The following equivalence criteria are respected: // * Positive and negative zeros are considered equivalent. // * Denormalized floats are allowed to be flushed to zeros, including // * Inputted 32bit denormalized float // * Generated 16bit denormalized float // * Different bit patterns of NaNs are allowed. // * For the rest, require exactly the same bit pattern. bool compare16BitFloat (float original, deUint16 returned, RoundingModeFlags flags, tcu::TestLog& log); // Given the original 16-bit float value, computes the corresponding 32-bit // float value and compares with the returned 32-bit float value. // Returns true if they are considered as equal. // // The following equivalence criteria are respected: // * Positive and negative zeros are considered equivalent. // * Denormalized floats are allowed to be flushed to zeros, including // * Inputted 16bit denormalized float // * Generated 32bit denormalized float // * Different bit patterns of NaNs are allowed. // * For the rest, require exactly the same bit pattern. bool compare16BitFloat (deUint16 returned, float original, tcu::TestLog& log); bool compare16BitFloat (deFloat16 original, deFloat16 returned, std::string& error); // Given the original 64-bit float value, computes the corresponding 16-bit // float value under the given rounding mode flags and compares with the // returned 16-bit float value. Returns true if they are considered as equal. // // The following equivalence criteria are respected: // * Positive and negative zeros are considered equivalent. // * Denormalized floats are allowed to be flushed to zeros, including // * Inputted 64bit denormalized float // * Generated 16bit denormalized float // * Different bit patterns of NaNs are allowed. // * For the rest, require exactly the same bit pattern. bool compare16BitFloat64 (double original, deUint16 returned, RoundingModeFlags flags, tcu::TestLog& log); // Compare the returned 32-bit float against its expected value. // // The following equivalence criteria are respected: // * Denormalized floats are allowed to be flushed to zeros, including // * The expected value itself is a denormalized float // * The expected value is a denormalized float if converted to 16bit // * Different bit patterns of NaNs/Infs are allowed. // * For the rest, use C++ float equivalence check. bool compare32BitFloat (float expected, float returned, tcu::TestLog& log); // Compare the returned 64-bit float against its expected value. // // The following equivalence criteria are respected: // * Denormalized floats are allowed to be flushed to zeros, including // * The expected value itself is a denormalized float // * The expected value is a denormalized float if converted to 16bit // * Different bit patterns of NaNs/Infs are allowed. // * For the rest, use C++ float equivalence check. bool compare64BitFloat (double expected, double returned, tcu::TestLog& log); } // SpirVAssembly } // vkt #endif // _VKTSPVASMGRAPHICSSHADERTESTUTIL_HPP