#ifndef _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP #define _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP /*------------------------------------------------------------------------- * Vulkan Conformance Tests * ------------------------ * * Copyright (c) 2015 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 Compute Shader Based Test Case Utility Structs/Functions *//*--------------------------------------------------------------------*/ #include "deDefs.h" #include "deFloat16.h" #include "deRandom.hpp" #include "tcuTestLog.hpp" #include "tcuVector.hpp" #include "tcuTestLog.hpp" #include "vkMemUtil.hpp" #include "vktSpvAsmUtils.hpp" #include #include #include using namespace vk; namespace vkt { namespace SpirVAssembly { enum OpAtomicType { OPATOMIC_IADD = 0, OPATOMIC_ISUB, OPATOMIC_IINC, OPATOMIC_IDEC, OPATOMIC_LOAD, OPATOMIC_STORE, OPATOMIC_COMPEX, OPATOMIC_LAST }; enum BufferType { BUFFERTYPE_INPUT = 0, BUFFERTYPE_EXPECTED, BUFFERTYPE_ATOMIC_RET, BUFFERTYPE_LAST }; static void fillRandomScalars (de::Random& rnd, deInt32 minValue, deInt32 maxValue, deInt32* dst, deInt32 numValues) { for (int i = 0; i < numValues; i++) dst[i] = rnd.getInt(minValue, maxValue); } /*--------------------------------------------------------------------*//*! * \brief Concrete class for an input/output storage buffer object used for OpAtomic tests *//*--------------------------------------------------------------------*/ class OpAtomicBuffer : public BufferInterface { public: OpAtomicBuffer (const deUint32 numInputElements, const deUint32 numOuptutElements, const OpAtomicType opAtomic, const BufferType type) : m_numInputElements (numInputElements) , m_numOutputElements (numOuptutElements) , m_opAtomic (opAtomic) , m_type (type) {} void getBytes (std::vector& bytes) const { std::vector inputInts (m_numInputElements, 0); de::Random rnd (m_opAtomic); fillRandomScalars(rnd, 1, 100, &inputInts.front(), m_numInputElements); // Return input values as is if (m_type == BUFFERTYPE_INPUT) { size_t inputSize = m_numInputElements * sizeof(deInt32); bytes.resize(inputSize); deMemcpy(&bytes.front(), &inputInts.front(), inputSize); } // Calculate expected output values else if (m_type == BUFFERTYPE_EXPECTED) { size_t outputSize = m_numOutputElements * sizeof(deInt32); bytes.resize(outputSize, 0xffu); for (size_t ndx = 0; ndx < m_numInputElements; ndx++) { deInt32* const bytesAsInt = reinterpret_cast(&bytes.front()); switch (m_opAtomic) { case OPATOMIC_IADD: bytesAsInt[0] += inputInts[ndx]; break; case OPATOMIC_ISUB: bytesAsInt[0] -= inputInts[ndx]; break; case OPATOMIC_IINC: bytesAsInt[0]++; break; case OPATOMIC_IDEC: bytesAsInt[0]--; break; case OPATOMIC_LOAD: bytesAsInt[ndx] = inputInts[ndx]; break; case OPATOMIC_STORE: bytesAsInt[ndx] = inputInts[ndx]; break; case OPATOMIC_COMPEX: bytesAsInt[ndx] = (inputInts[ndx] % 2) == 0 ? -1 : 1; break; default: DE_FATAL("Unknown OpAtomic type"); } } } else if (m_type == BUFFERTYPE_ATOMIC_RET) { bytes.resize(m_numInputElements * sizeof(deInt32), 0xff); if (m_opAtomic == OPATOMIC_COMPEX) { deInt32* const bytesAsInt = reinterpret_cast(&bytes.front()); for (size_t ndx = 0; ndx < m_numInputElements; ndx++) bytesAsInt[ndx] = inputInts[ndx] % 2; } } else DE_FATAL("Unknown buffer type"); } void getPackedBytes (std::vector& bytes) const { return getBytes(bytes); } size_t getByteSize (void) const { switch (m_type) { case BUFFERTYPE_ATOMIC_RET: case BUFFERTYPE_INPUT: return m_numInputElements * sizeof(deInt32); case BUFFERTYPE_EXPECTED: return m_numOutputElements * sizeof(deInt32); default: DE_FATAL("Unknown buffer type"); return 0; } } template static bool compareWithRetvals (const std::vector& inputs, const std::vector& outputAllocs, const std::vector& expectedOutputs, tcu::TestLog& log) { if (outputAllocs.size() != 2 || inputs.size() != 1) DE_FATAL("Wrong number of buffers to compare"); for (size_t i = 0; i < outputAllocs.size(); ++i) { const deUint32* values = reinterpret_cast(outputAllocs[i]->getHostPtr()); if (i == 1 && OpAtomic != OPATOMIC_COMPEX) { // BUFFERTYPE_ATOMIC_RET for arithmetic operations must be verified manually by matching return values to inputs std::vector inputBytes; inputs[0].getBytes(inputBytes); const deUint32* inputValues = reinterpret_cast(&inputBytes.front()); const size_t inputValuesCount = inputBytes.size() / sizeof(deUint32); // result of all atomic operations const deUint32 resultValue = *reinterpret_cast(outputAllocs[0]->getHostPtr()); if (!compareRetVals(inputValues, inputValuesCount, resultValue, values)) { log << tcu::TestLog::Message << "Wrong contents of buffer with return values after atomic operation." << tcu::TestLog::EndMessage; return false; } } else { const BufferSp& expectedOutput = expectedOutputs[i].getBuffer(); std::vector expectedBytes; expectedOutput->getBytes(expectedBytes); if (deMemCmp(&expectedBytes.front(), values, expectedBytes.size())) { log << tcu::TestLog::Message << "Wrong contents of buffer after atomic operation" << tcu::TestLog::EndMessage; return false; } } } return true; } template static bool compareRetVals (const deUint32* inputValues, const size_t inputValuesCount, const deUint32 resultValue, const deUint32* returnValues) { // as the order of execution is undefined, validation of return values for atomic operations is tricky: // each inputValue stands for one atomic operation. Iterate through all of // done operations in time, each time finding one matching current result and un-doing it. std::vector operationsUndone (inputValuesCount, false); deUint32 currentResult = resultValue; for (size_t operationUndone = 0; operationUndone < inputValuesCount; ++operationUndone) { // find which of operations was done at this moment size_t ndx; for (ndx = 0; ndx < inputValuesCount; ++ndx) { if (operationsUndone[ndx]) continue; deUint32 previousResult = currentResult; switch (OpAtomic) { // operations are undone here, so the actual opeation is reversed case OPATOMIC_IADD: previousResult -= inputValues[ndx]; break; case OPATOMIC_ISUB: previousResult += inputValues[ndx]; break; case OPATOMIC_IINC: previousResult--; break; case OPATOMIC_IDEC: previousResult++; break; default: DE_FATAL("Unsupported OpAtomic type for return value compare"); } if (previousResult == returnValues[ndx]) { // found matching operation currentResult = returnValues[ndx]; operationsUndone[ndx] = true; break; } } if (ndx == inputValuesCount) { // no operation matches the current result value return false; } } return true; } private: const deUint32 m_numInputElements; const deUint32 m_numOutputElements; const OpAtomicType m_opAtomic; const BufferType m_type; }; /*--------------------------------------------------------------------*//*! * \brief Concrete class for an input/output storage buffer object *//*--------------------------------------------------------------------*/ template class Buffer : public BufferInterface { public: Buffer (const std::vector& elements, deUint32 padding = 0 /* in bytes */) : m_elements(elements) , m_padding(padding) {} void getBytes (std::vector& bytes) const { const size_t count = m_elements.size(); const size_t perSegmentSize = sizeof(E) + m_padding; const size_t size = count * perSegmentSize; bytes.resize(size); if (m_padding == 0) { deMemcpy(&bytes.front(), &m_elements.front(), size); } else { deMemset(&bytes.front(), 0xff, size); for (deUint32 elementIdx = 0; elementIdx < count; ++elementIdx) deMemcpy(&bytes[elementIdx * perSegmentSize], &m_elements[elementIdx], sizeof(E)); } } void getPackedBytes (std::vector& bytes) const { const size_t size = m_elements.size() * sizeof(E); bytes.resize(size); deMemcpy(&bytes.front(), &m_elements.front(), size); } size_t getByteSize (void) const { return m_elements.size() * (sizeof(E) + m_padding); } private: std::vector m_elements; deUint32 m_padding; }; DE_STATIC_ASSERT(sizeof(tcu::Vec4) == 4 * sizeof(float)); typedef Buffer Float32Buffer; typedef Buffer Float16Buffer; typedef Buffer Float64Buffer; typedef Buffer Int64Buffer; typedef Buffer Int32Buffer; typedef Buffer Int16Buffer; typedef Buffer Int8Buffer; typedef Buffer Uint8Buffer; typedef Buffer Uint16Buffer; typedef Buffer Uint32Buffer; typedef Buffer Uint64Buffer; typedef Buffer Vec4Buffer; typedef bool (*ComputeVerifyBinaryFunc) (const ProgramBinary& binary); /*--------------------------------------------------------------------*//*! * \brief Specification for a compute shader. * * This struct bundles SPIR-V assembly code, input and expected output * together. *//*--------------------------------------------------------------------*/ struct ComputeShaderSpec { std::string assembly; std::string entryPoint; std::vector inputs; std::vector outputs; tcu::IVec3 numWorkGroups; SpecConstants specConstants; BufferSp pushConstants; std::vector extensions; VulkanFeatures requestedVulkanFeatures; qpTestResult failResult; std::string failMessage; // 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. Exact meaning of failure can be customized with failResult. VerifyIOFunc verifyIO; ComputeVerifyBinaryFunc verifyBinary; SpirvVersion spirvVersion; bool coherentMemory; bool usesPhysStorageBuffer; ComputeShaderSpec (void) : entryPoint ("main") , pushConstants (DE_NULL) , requestedVulkanFeatures () , failResult (QP_TEST_RESULT_FAIL) , failMessage ("Output doesn't match with expected") , verifyIO (DE_NULL) , verifyBinary (DE_NULL) , spirvVersion (SPIRV_VERSION_1_0) , coherentMemory (false) , usesPhysStorageBuffer (false) {} }; /*--------------------------------------------------------------------*//*! * \brief Helper functions for SPIR-V assembly shared by various tests *//*--------------------------------------------------------------------*/ std::string getComputeAsmShaderPreamble (const std::string& capabilities = "", const std::string& extensions = "", const std::string& exeModes = "", const std::string& extraEntryPoints = "", const std::string& extraEntryPointsArguments = ""); const char* getComputeAsmShaderPreambleWithoutLocalSize (void); std::string getComputeAsmCommonTypes (std::string blockStorageClass = "Uniform"); const char* getComputeAsmCommonInt64Types (void); /*--------------------------------------------------------------------*//*! * Declares two uniform variables (indata, outdata) of type * "struct { float[] }". Depends on type "f32arr" (for "float[]"). *//*--------------------------------------------------------------------*/ std::string getComputeAsmInputOutputBuffer (std::string blockStorageClass = "Uniform"); /*--------------------------------------------------------------------*//*! * Declares buffer type and layout for uniform variables indata and * outdata. Both of them are SSBO bounded to descriptor set 0. * indata is at binding point 0, while outdata is at 1. *//*--------------------------------------------------------------------*/ std::string getComputeAsmInputOutputBufferTraits (std::string blockStorageClass = "BufferBlock"); bool verifyOutput (const std::vector&, const std::vector& outputAllocs, const std::vector& expectedOutputs, tcu::TestLog& log); // Creates vertex-shader assembly by specializing a boilerplate StringTemplate std::string makeComputeShaderAssembly(const std::map& fragments); } // SpirVAssembly } // vkt #endif // _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP