#include "deSharedPtr.hpp"
#include "tcuTestLog.hpp"
#include "tcuVector.hpp"
+#include "tcuTestLog.hpp"
#include "vkMemUtil.hpp"
#include "vktSpvAsmUtils.hpp"
{
BUFFERTYPE_INPUT = 0,
BUFFERTYPE_EXPECTED,
+ BUFFERTYPE_ATOMIC_RET,
BUFFERTYPE_LAST
};
}
}
}
+ 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* const>(&bytes.front());
+ for (size_t ndx = 0; ndx < m_numInputElements; ndx++)
+ bytesAsInt[ndx] = inputInts[ndx] % 2;
+ }
+ }
else
DE_FATAL("Unknown buffer type");
}
{
switch (m_type)
{
+ case BUFFERTYPE_ATOMIC_RET:
case BUFFERTYPE_INPUT:
return m_numInputElements * sizeof(deInt32);
case BUFFERTYPE_EXPECTED:
}
}
+ template <int OpAtomic>
+ static bool compareWithRetvals (const std::vector<BufferSp>& inputs, const std::vector<AllocationSp>& outputAllocs, const std::vector<BufferSp>& 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];
+ 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 char* name;
const char* assembly;
+ const char* retValAssembly;
OpAtomicType opAtomic;
deInt32 numOutputElements;
- OpAtomicCase (const char* _name, const char* _assembly, OpAtomicType _opAtomic, deInt32 _numOutputElements)
+ OpAtomicCase(const char* _name, const char* _assembly, const char* _retValAssembly, OpAtomicType _opAtomic, deInt32 _numOutputElements)
: name (_name)
, assembly (_assembly)
+ , retValAssembly (_retValAssembly)
, opAtomic (_opAtomic)
, numOutputElements (_numOutputElements) {}
};
-tcu::TestCaseGroup* createOpAtomicGroup (tcu::TestContext& testCtx, bool useStorageBuffer)
+tcu::TestCaseGroup* createOpAtomicGroup (tcu::TestContext& testCtx, bool useStorageBuffer, int numElements = 65535, bool verifyReturnValues = false)
{
- de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx,
- useStorageBuffer ? "opatomic_storage_buffer" : "opatomic",
- "Test the OpAtomic* opcodes"));
- const int numElements = 65535;
+ std::string groupName ("opatomic");
+ if (useStorageBuffer)
+ groupName += "_storage_buffer";
+ if (verifyReturnValues)
+ groupName += "_return_values";
+ de::MovePtr<tcu::TestCaseGroup> group (new tcu::TestCaseGroup(testCtx, groupName.c_str(), "Test the OpAtomic* opcodes"));
vector<OpAtomicCase> cases;
const StringTemplate shaderTemplate (
"OpMemberDecorate %sumbuf 0 Coherent\n"
"OpMemberDecorate %sumbuf 0 Offset 0\n"
+ "${RETVAL_BUF_DECORATE}"
+
+ getComputeAsmCommonTypes("${BLOCK_POINTER_TYPE}") +
"%buf = OpTypeStruct %i32arr\n"
"%sumbufptr = OpTypePointer ${BLOCK_POINTER_TYPE} %sumbuf\n"
"%sum = OpVariable %sumbufptr ${BLOCK_POINTER_TYPE}\n"
+ "${RETVAL_BUF_DECL}"
+
"%id = OpVariable %uvec3ptr Input\n"
"%minusone = OpConstant %i32 -1\n"
"%zero = OpConstant %i32 0\n"
"%outloc = OpAccessChain %i32ptr %sum %zero ${INDEX}\n"
"${INSTRUCTION}"
+ "${RETVAL_ASSEMBLY}"
" OpReturn\n"
" OpFunctionEnd\n");
- #define ADD_OPATOMIC_CASE(NAME, ASSEMBLY, OPATOMIC, NUM_OUTPUT_ELEMENTS) \
+ #define ADD_OPATOMIC_CASE(NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC, NUM_OUTPUT_ELEMENTS) \
do { \
- DE_STATIC_ASSERT((NUM_OUTPUT_ELEMENTS) == 1 || (NUM_OUTPUT_ELEMENTS) == numElements); \
- cases.push_back(OpAtomicCase(#NAME, ASSEMBLY, OPATOMIC, NUM_OUTPUT_ELEMENTS)); \
+ DE_ASSERT((NUM_OUTPUT_ELEMENTS) == 1 || (NUM_OUTPUT_ELEMENTS) == numElements); \
+ cases.push_back(OpAtomicCase(#NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC, NUM_OUTPUT_ELEMENTS)); \
} while (deGetFalse())
- #define ADD_OPATOMIC_CASE_1(NAME, ASSEMBLY, OPATOMIC) ADD_OPATOMIC_CASE(NAME, ASSEMBLY, OPATOMIC, 1)
- #define ADD_OPATOMIC_CASE_N(NAME, ASSEMBLY, OPATOMIC) ADD_OPATOMIC_CASE(NAME, ASSEMBLY, OPATOMIC, numElements)
-
- ADD_OPATOMIC_CASE_1(iadd, "%unused = OpAtomicIAdd %i32 %outloc %one %zero %inval\n", OPATOMIC_IADD );
- ADD_OPATOMIC_CASE_1(isub, "%unused = OpAtomicISub %i32 %outloc %one %zero %inval\n", OPATOMIC_ISUB );
- ADD_OPATOMIC_CASE_1(iinc, "%unused = OpAtomicIIncrement %i32 %outloc %one %zero\n", OPATOMIC_IINC );
- ADD_OPATOMIC_CASE_1(idec, "%unused = OpAtomicIDecrement %i32 %outloc %one %zero\n", OPATOMIC_IDEC );
- ADD_OPATOMIC_CASE_N(load, "%inval2 = OpAtomicLoad %i32 %inloc %one %zero\n"
- " OpStore %outloc %inval2\n", OPATOMIC_LOAD );
- ADD_OPATOMIC_CASE_N(store, " OpAtomicStore %outloc %one %zero %inval\n", OPATOMIC_STORE );
+ #define ADD_OPATOMIC_CASE_1(NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC) ADD_OPATOMIC_CASE(NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC, 1)
+ #define ADD_OPATOMIC_CASE_N(NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC) ADD_OPATOMIC_CASE(NAME, ASSEMBLY, RETVAL_ASSEMBLY, OPATOMIC, numElements)
+
+ ADD_OPATOMIC_CASE_1(iadd, "%retv = OpAtomicIAdd %i32 %outloc %one %zero %inval\n",
+ " OpStore %retloc %retv\n", OPATOMIC_IADD );
+ ADD_OPATOMIC_CASE_1(isub, "%retv = OpAtomicISub %i32 %outloc %one %zero %inval\n",
+ " OpStore %retloc %retv\n", OPATOMIC_ISUB );
+ ADD_OPATOMIC_CASE_1(iinc, "%retv = OpAtomicIIncrement %i32 %outloc %one %zero\n",
+ " OpStore %retloc %retv\n", OPATOMIC_IINC );
+ ADD_OPATOMIC_CASE_1(idec, "%retv = OpAtomicIDecrement %i32 %outloc %one %zero\n",
+ " OpStore %retloc %retv\n", OPATOMIC_IDEC );
+ if (!verifyReturnValues)
+ {
+ ADD_OPATOMIC_CASE_N(load, "%inval2 = OpAtomicLoad %i32 %inloc %one %zero\n"
+ " OpStore %outloc %inval2\n", "", OPATOMIC_LOAD );
+ ADD_OPATOMIC_CASE_N(store, " OpAtomicStore %outloc %one %zero %inval\n", "", OPATOMIC_STORE );
+ }
+
ADD_OPATOMIC_CASE_N(compex, "%even = OpSMod %i32 %inval %two\n"
" OpStore %outloc %even\n"
- "%unused = OpAtomicCompareExchange %i32 %outloc %one %zero %zero %minusone %zero\n", OPATOMIC_COMPEX );
+ "%retv = OpAtomicCompareExchange %i32 %outloc %one %zero %zero %minusone %zero\n",
+ " OpStore %retloc %retv\n", OPATOMIC_COMPEX );
+
#undef ADD_OPATOMIC_CASE
#undef ADD_OPATOMIC_CASE_1
specializations["INSTRUCTION"] = cases[caseNdx].assembly;
specializations["BLOCK_DECORATION"] = useStorageBuffer ? "Block" : "BufferBlock";
specializations["BLOCK_POINTER_TYPE"] = useStorageBuffer ? "StorageBuffer" : "Uniform";
+
+ if (verifyReturnValues)
+ {
+ const StringTemplate blockDecoration (
+ "\n"
+ "OpDecorate %retbuf ${BLOCK_DECORATION}\n"
+ "OpDecorate %ret DescriptorSet 0\n"
+ "OpDecorate %ret Binding 2\n"
+ "OpMemberDecorate %retbuf 0 Offset 0\n\n");
+
+ const StringTemplate blockDeclaration (
+ "\n"
+ "%retbuf = OpTypeStruct %i32arr\n"
+ "%retbufptr = OpTypePointer ${BLOCK_POINTER_TYPE} %retbuf\n"
+ "%ret = OpVariable %retbufptr ${BLOCK_POINTER_TYPE}\n\n");
+
+ specializations["RETVAL_ASSEMBLY"] =
+ "%retloc = OpAccessChain %i32ptr %ret %zero %x\n"
+ + std::string(cases[caseNdx].retValAssembly);
+
+ specializations["RETVAL_BUF_DECORATE"] = blockDecoration.specialize(specializations);
+ specializations["RETVAL_BUF_DECL"] = blockDeclaration.specialize(specializations);
+ }
+ else
+ {
+ specializations["RETVAL_ASSEMBLY"] = "";
+ specializations["RETVAL_BUF_DECORATE"] = "";
+ specializations["RETVAL_BUF_DECL"] = "";
+ }
+
spec.assembly = shaderTemplate.specialize(specializations);
if (useStorageBuffer)
spec.inputs.push_back(BufferSp(new OpAtomicBuffer(numElements, cases[caseNdx].numOutputElements, cases[caseNdx].opAtomic, BUFFERTYPE_INPUT)));
spec.outputs.push_back(BufferSp(new OpAtomicBuffer(numElements, cases[caseNdx].numOutputElements, cases[caseNdx].opAtomic, BUFFERTYPE_EXPECTED)));
+ if (verifyReturnValues)
+ spec.outputs.push_back(BufferSp(new OpAtomicBuffer(numElements, cases[caseNdx].numOutputElements, cases[caseNdx].opAtomic, BUFFERTYPE_ATOMIC_RET)));
spec.numWorkGroups = IVec3(numElements, 1, 1);
+
+ if (verifyReturnValues)
+ {
+ switch (cases[caseNdx].opAtomic)
+ {
+ case OPATOMIC_IADD:
+ spec.verifyIO = OpAtomicBuffer::compareWithRetvals<OPATOMIC_IADD>;
+ break;
+ case OPATOMIC_ISUB:
+ spec.verifyIO = OpAtomicBuffer::compareWithRetvals<OPATOMIC_ISUB>;
+ break;
+ case OPATOMIC_IINC:
+ spec.verifyIO = OpAtomicBuffer::compareWithRetvals<OPATOMIC_IINC>;
+ break;
+ case OPATOMIC_IDEC:
+ spec.verifyIO = OpAtomicBuffer::compareWithRetvals<OPATOMIC_IDEC>;
+ break;
+ case OPATOMIC_COMPEX:
+ spec.verifyIO = OpAtomicBuffer::compareWithRetvals<OPATOMIC_COMPEX>;
+ break;
+ default:
+ DE_FATAL("Unsupported OpAtomic type for return value verification");
+ }
+ }
group->addChild(new SpvAsmComputeShaderCase(testCtx, cases[caseNdx].name, cases[caseNdx].name, spec));
}
computeTests->addChild(createOpNopGroup(testCtx));
computeTests->addChild(createOpFUnordGroup(testCtx));
computeTests->addChild(createOpAtomicGroup(testCtx, false));
- computeTests->addChild(createOpAtomicGroup(testCtx, true)); // Using new StorageBuffer decoration
+ computeTests->addChild(createOpAtomicGroup(testCtx, true)); // Using new StorageBuffer decoration
+ computeTests->addChild(createOpAtomicGroup(testCtx, false, 1024, true)); // Return value validation
computeTests->addChild(createOpLineGroup(testCtx));
computeTests->addChild(createOpModuleProcessedGroup(testCtx));
computeTests->addChild(createOpNoLineGroup(testCtx));