blob: 08017ab8231c5609abed0e822e1ae0c16cc16152 [file] [log] [blame]
#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 <string>
#include <vector>
#include <map>
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<deUint8>& bytes) const
{
std::vector<deInt32> 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<deInt32*>(&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<deInt32*>(&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<deUint8>& 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 <int OpAtomic>
static bool compareWithRetvals (const std::vector<Resource>& inputs, const std::vector<AllocationSp>& outputAllocs, const std::vector<Resource>& 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<deUint32*>(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<deUint8> inputBytes;
inputs[0].getBytes(inputBytes);
const deUint32* inputValues = reinterpret_cast<deUint32*>(&inputBytes.front());
const size_t inputValuesCount = inputBytes.size() / sizeof(deUint32);
// result of all atomic operations
const deUint32 resultValue = *reinterpret_cast<deUint32*>(outputAllocs[0]->getHostPtr());
if (!compareRetVals<OpAtomic>(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<deUint8> 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 <int OpAtomic>
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<bool> 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<typename E>
class Buffer : public BufferInterface
{
public:
Buffer (const std::vector<E>& elements, deUint32 padding = 0 /* in bytes */)
: m_elements(elements)
, m_padding(padding)
{}
void getBytes (std::vector<deUint8>& 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<deUint8>& 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<E> m_elements;
deUint32 m_padding;
};
DE_STATIC_ASSERT(sizeof(tcu::Vec4) == 4 * sizeof(float));
typedef Buffer<float> Float32Buffer;
typedef Buffer<deFloat16> Float16Buffer;
typedef Buffer<double> Float64Buffer;
typedef Buffer<deInt64> Int64Buffer;
typedef Buffer<deInt32> Int32Buffer;
typedef Buffer<deInt16> Int16Buffer;
typedef Buffer<deInt8> Int8Buffer;
typedef Buffer<deUint8> Uint8Buffer;
typedef Buffer<deUint16> Uint16Buffer;
typedef Buffer<deUint32> Uint32Buffer;
typedef Buffer<deUint64> Uint64Buffer;
typedef Buffer<tcu::Vec4> 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<Resource> inputs;
std::vector<Resource> outputs;
tcu::IVec3 numWorkGroups;
SpecConstants specConstants;
BufferSp pushConstants;
std::vector<std::string> 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<Resource>&,
const std::vector<AllocationSp>& outputAllocs,
const std::vector<Resource>& expectedOutputs,
tcu::TestLog& log);
// Creates vertex-shader assembly by specializing a boilerplate StringTemplate
std::string makeComputeShaderAssembly(const std::map<std::string, std::string>& fragments);
} // SpirVAssembly
} // vkt
#endif // _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP