/*-------------------------------------------------------------------------
* drawElements Quality Program OpenGL ES 3.1 Module
* -------------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* 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 Shader atomic operation tests.
*//*--------------------------------------------------------------------*/
#include "es31fShaderAtomicOpTests.hpp"
#include "gluShaderProgram.hpp"
#include "gluShaderUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluObjectWrapper.hpp"
#include "gluProgramInterfaceQuery.hpp"
#include "tcuVector.hpp"
#include "tcuTestLog.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuFormatUtil.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include <algorithm>
#include <set>
namespace deqp
{
namespace gles31
{
namespace Functional
{
using std::string;
using std::vector;
using tcu::TestLog;
using tcu::UVec3;
using std::set;
using namespace glu;
template<typename T, int Size>
static inline T product (const tcu::Vector<T, Size>& v)
{
T res = v[0];
for (int ndx = 1; ndx < Size; ndx++)
res *= v[ndx];
return res;
}
class ShaderAtomicOpCase : public TestCase
{
public:
ShaderAtomicOpCase (Context& context, const char* name, const char* funcName, AtomicOperandType operandType, DataType type, Precision precision, const UVec3& workGroupSize);
~ShaderAtomicOpCase (void);
void init (void);
void deinit (void);
IterateResult iterate (void);
protected:
virtual void getInputs (int numValues, int stride, void* inputs) const = 0;
virtual bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const = 0;
const string m_funcName;
const AtomicOperandType m_operandType;
const DataType m_type;
const Precision m_precision;
const UVec3 m_workGroupSize;
const UVec3 m_numWorkGroups;
deUint32 m_initialValue;
private:
ShaderAtomicOpCase (const ShaderAtomicOpCase& other);
ShaderAtomicOpCase& operator= (const ShaderAtomicOpCase& other);
ShaderProgram* m_program;
};
ShaderAtomicOpCase::ShaderAtomicOpCase (Context& context, const char* name, const char* funcName, AtomicOperandType operandType, DataType type, Precision precision, const UVec3& workGroupSize)
: TestCase (context, name, funcName)
, m_funcName (funcName)
, m_operandType (operandType)
, m_type (type)
, m_precision (precision)
, m_workGroupSize (workGroupSize)
, m_numWorkGroups (4,4,4)
, m_initialValue (0)
, m_program (DE_NULL)
{
}
ShaderAtomicOpCase::~ShaderAtomicOpCase (void)
{
ShaderAtomicOpCase::deinit();
}
void ShaderAtomicOpCase::init (void)
{
const bool isSSBO = m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE;
const char* precName = getPrecisionName(m_precision);
const char* typeName = getDataTypeName(m_type);
const DataType outType = isSSBO ? m_type : glu::TYPE_UINT;
const char* outTypeName = getDataTypeName(outType);
const deUint32 numValues = product(m_workGroupSize)*product(m_numWorkGroups);
std::ostringstream src;
src << "#version 310 es\n"
<< "layout(local_size_x = " << m_workGroupSize.x()
<< ", local_size_y = " << m_workGroupSize.y()
<< ", local_size_z = " << m_workGroupSize.z() << ") in;\n"
<< "layout(binding = 0) buffer InOut\n"
<< "{\n"
<< " " << precName << " " << typeName << " inputValues[" << numValues << "];\n"
<< " " << precName << " " << outTypeName << " outputValues[" << numValues << "];\n"
<< " " << (isSSBO ? "coherent " : "") << precName << " " << outTypeName << " groupValues[" << product(m_numWorkGroups) << "];\n"
<< "} sb_inout;\n";
if (!isSSBO)
src << "shared " << precName << " " << typeName << " s_var;\n";
src << "\n"
<< "void main (void)\n"
<< "{\n"
<< " uint localSize = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
<< " uint globalNdx = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
<< " uint globalOffs = localSize*globalNdx;\n"
<< " uint offset = globalOffs + gl_LocalInvocationIndex;\n"
<< "\n";
if (isSSBO)
{
DE_ASSERT(outType == m_type);
src << " sb_inout.outputValues[offset] = " << m_funcName << "(sb_inout.groupValues[globalNdx], sb_inout.inputValues[offset]);\n";
}
else
{
const string castBeg = outType != m_type ? (string(outTypeName) + "(") : string("");
const char* const castEnd = outType != m_type ? ")" : "";
src << " if (gl_LocalInvocationIndex == 0u)\n"
<< " s_var = " << typeName << "(" << tcu::toHex(m_initialValue) << "u);\n"
<< " barrier();\n"
<< " " << precName << " " << typeName << " res = " << m_funcName << "(s_var, sb_inout.inputValues[offset]);\n"
<< " sb_inout.outputValues[offset] = " << castBeg << "res" << castEnd << ";\n"
<< " barrier();\n"
<< " if (gl_LocalInvocationIndex == 0u)\n"
<< " sb_inout.groupValues[globalNdx] = " << castBeg << "s_var" << castEnd << ";\n";
}
src << "}\n";
DE_ASSERT(!m_program);
m_program = new ShaderProgram(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
m_testCtx.getLog() << *m_program;
if (!m_program->isOk())
{
delete m_program;
m_program = DE_NULL;
throw tcu::TestError("Compile failed");
}
}
void ShaderAtomicOpCase::deinit (void)
{
delete m_program;
m_program = DE_NULL;
}
ShaderAtomicOpCase::IterateResult ShaderAtomicOpCase::iterate (void)
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
const deUint32 program = m_program->getProgram();
const Buffer inoutBuffer (m_context.getRenderContext());
const deUint32 blockNdx = gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "InOut");
const InterfaceBlockInfo blockInfo = getProgramInterfaceBlockInfo(gl, program, GL_SHADER_STORAGE_BLOCK, blockNdx);
const deUint32 inVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.inputValues[0]");
const InterfaceVariableInfo inVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, inVarNdx);
const deUint32 outVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.outputValues[0]");
const InterfaceVariableInfo outVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outVarNdx);
const deUint32 groupVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.groupValues[0]");
const InterfaceVariableInfo groupVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, groupVarNdx);
const deUint32 numValues = product(m_workGroupSize)*product(m_numWorkGroups);
TCU_CHECK(inVarInfo.arraySize == numValues &&
outVarInfo.arraySize == numValues &&
groupVarInfo.arraySize == product(m_numWorkGroups));
gl.useProgram(program);
// Setup buffer.
{
vector<deUint8> bufData(blockInfo.dataSize);
std::fill(bufData.begin(), bufData.end(), 0);
getInputs((int)numValues, (int)inVarInfo.arrayStride, &bufData[0] + inVarInfo.offset);
if (m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE)
{
for (deUint32 valNdx = 0; valNdx < product(m_numWorkGroups); valNdx++)
*(deUint32*)(&bufData[0] + groupVarInfo.offset + groupVarInfo.arrayStride*valNdx) = m_initialValue;
}
gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inoutBuffer);
gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, &bufData[0], GL_STATIC_READ);
gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *inoutBuffer);
GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
}
gl.dispatchCompute(m_numWorkGroups.x(), m_numWorkGroups.y(), m_numWorkGroups.z());
// Read back and compare
{
const void* resPtr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, blockInfo.dataSize, GL_MAP_READ_BIT);
bool isOk = true;
GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
TCU_CHECK(resPtr);
isOk = verify((int)numValues,
(int)inVarInfo.arrayStride, (const deUint8*)resPtr + inVarInfo.offset,
(int)outVarInfo.arrayStride, (const deUint8*)resPtr + outVarInfo.offset,
(int)groupVarInfo.arrayStride, (const deUint8*)resPtr + groupVarInfo.offset);
gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
isOk ? "Pass" : "Comparison failed");
}
return STOP;
}
class ShaderAtomicAddCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicAddCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicAdd", operandType, type, precision, UVec3(3,2,1))
{
m_initialValue = 1;
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const int maxVal = m_precision == PRECISION_LOWP ? 2 : 32;
const int minVal = 1;
// \todo [2013-09-04 pyry] Negative values!
for (int valNdx = 0; valNdx < numValues; valNdx++)
*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const int groupOutput = *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
set<int> outValues;
bool maxFound = false;
int valueSum = (int)m_initialValue;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const int inputValue = *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
valueSum += inputValue;
}
if (groupOutput != valueSum)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected sum " << valueSum << ", got " << groupOutput << TestLog::EndMessage;
return false;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const int inputValue = *(const int*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
const int outputValue = *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if (!de::inRange(outputValue, (int)m_initialValue, valueSum-inputValue))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": expected value in range [" << m_initialValue << ", " << (valueSum-inputValue)
<< "], got " << outputValue
<< TestLog::EndMessage;
return false;
}
if (outValues.find(outputValue) != outValues.end())
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found duplicate value " << outputValue
<< TestLog::EndMessage;
return false;
}
outValues.insert(outputValue);
if (outputValue == valueSum-inputValue)
maxFound = true;
}
if (!maxFound)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: could not find maximum expected value from group " << groupNdx << TestLog::EndMessage;
return false;
}
if (outValues.find((int)m_initialValue) == outValues.end())
{
m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
return false;
}
}
return true;
}
};
static int getPrecisionNumIntegerBits (glu::Precision precision)
{
switch (precision)
{
case glu::PRECISION_HIGHP: return 32;
case glu::PRECISION_MEDIUMP: return 16;
case glu::PRECISION_LOWP: return 9;
default:
DE_ASSERT(false);
return 0;
}
}
static deUint32 getPrecisionMask (int numPreciseBits)
{
// \note: bit shift with larger or equal than var length is undefined, use 64 bit ints
return (deUint32)((((deUint64)1u) << numPreciseBits) - 1) ;
}
static bool intEqualsAfterUintCast (deInt32 value, deUint32 casted, glu::Precision precision)
{
// Bit format of 'casted' = [ uint -> highp uint promotion bits (0) ] [ sign extend bits (s) ] [ value bits ]
// |--min len---|
// |---------------signed length---------|
// |-------------------------------- highp uint length ----------------------------|
const deUint32 reference = (deUint32)value;
const int signBitOn = value < 0;
const int numPreciseBits = getPrecisionNumIntegerBits(precision);
const deUint32 preciseMask = getPrecisionMask(numPreciseBits);
// Lowest N bits must match, N = minimum precision
if ((reference & preciseMask) != (casted & preciseMask))
return false;
// Other lowest bits must match the sign and the remaining (topmost) if any must be 0
for (int signedIntegerLength = numPreciseBits; signedIntegerLength <= 32; ++signedIntegerLength)
{
const deUint32 signBits = (signBitOn) ? (getPrecisionMask(signedIntegerLength)) : (0u);
if ((signBits & ~preciseMask) == (casted & ~preciseMask))
return true;
}
return false;
}
static bool containsAfterUintCast (const std::set<deInt32>& haystack, deUint32 needle, glu::Precision precision)
{
for (std::set<deInt32>::const_iterator it = haystack.begin(); it != haystack.end(); ++it)
if (intEqualsAfterUintCast(*it, needle, precision))
return true;
return false;
}
static bool containsAfterUintCast (const std::set<deUint32>& haystack, deInt32 needle, glu::Precision precision)
{
for (std::set<deUint32>::const_iterator it = haystack.begin(); it != haystack.end(); ++it)
if (intEqualsAfterUintCast(needle, *it, precision))
return true;
return false;
}
class ShaderAtomicMinCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicMinCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicMin", operandType, type, precision, UVec3(3,2,1))
{
m_initialValue = m_precision == PRECISION_LOWP ? 100 : 1000;
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const bool isSigned = m_type == TYPE_INT;
const int maxVal = m_precision == PRECISION_LOWP ? 100 : 1000;
const int minVal = isSigned ? -maxVal : 0;
for (int valNdx = 0; valNdx < numValues; valNdx++)
*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
bool anyError = false;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupOutput = *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
set<deInt32> inValues;
set<deUint32> outValues;
int minValue = (int)m_initialValue;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deInt32 inputValue = *(const deInt32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
inValues.insert(inputValue);
minValue = de::min(inputValue, minValue);
}
if (!intEqualsAfterUintCast(minValue, groupOutput, m_precision))
{
m_testCtx.getLog()
<< TestLog::Message
<< "ERROR: at group " << groupNdx
<< ": expected minimum " << minValue << " (" << tcu::Format::Hex<8>((deUint32)minValue) << ")"
<< ", got " << groupOutput << " (" << tcu::Format::Hex<8>(groupOutput) << ")"
<< TestLog::EndMessage;
anyError = true;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if (!containsAfterUintCast(inValues, outputValue, m_precision) &&
!intEqualsAfterUintCast((deInt32)m_initialValue, outputValue, m_precision))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << outputValue
<< " (" << tcu::Format::Hex<8>(outputValue) << ")"
<< TestLog::EndMessage;
anyError = true;
}
outValues.insert(outputValue);
}
if (!containsAfterUintCast(outValues, (int)m_initialValue, m_precision))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
anyError = true;
}
}
return !anyError;
}
};
class ShaderAtomicMaxCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicMaxCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicMax", operandType, type, precision, UVec3(3,2,1))
{
const bool isSigned = m_type == TYPE_INT;
m_initialValue = isSigned ? (m_precision == PRECISION_LOWP ? -100 : -1000) : 0;
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const bool isSigned = m_type == TYPE_INT;
const int maxVal = m_precision == PRECISION_LOWP ? 100 : 1000;
const int minVal = isSigned ? -maxVal : 0;
for (int valNdx = 0; valNdx < numValues; valNdx++)
*(int*)((deUint8*)inputs + stride*valNdx) = rnd.getInt(minVal, maxVal);
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
bool anyError = false;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupOutput = *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
set<int> inValues;
set<deUint32> outValues;
int maxValue = (int)m_initialValue;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deInt32 inputValue = *(const deInt32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
inValues.insert(inputValue);
maxValue = de::max(maxValue, inputValue);
}
if (!intEqualsAfterUintCast(maxValue, groupOutput, m_precision))
{
m_testCtx.getLog()
<< TestLog::Message
<< "ERROR: at group " << groupNdx
<< ": expected maximum " << maxValue << " (" << tcu::Format::Hex<8>((deUint32)maxValue) << ")"
<< ", got " << groupOutput << " (" << tcu::Format::Hex<8>(groupOutput) << ")"
<< TestLog::EndMessage;
anyError = true;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if (!containsAfterUintCast(inValues, outputValue, m_precision) &&
!intEqualsAfterUintCast((deInt32)m_initialValue, outputValue, m_precision))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << outputValue
<< " (" << tcu::Format::Hex<8>(outputValue) << ")"
<< TestLog::EndMessage;
anyError = true;
}
outValues.insert(outputValue);
}
if (!containsAfterUintCast(outValues, (int)m_initialValue, m_precision))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: could not find initial value from group " << groupNdx << TestLog::EndMessage;
anyError = true;
}
}
return !anyError;
}
};
class ShaderAtomicAndCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicAndCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicAnd", operandType, type, precision, UVec3(3,2,1))
{
const int numBits = m_precision == PRECISION_HIGHP ? 32 :
m_precision == PRECISION_MEDIUMP ? 16 : 8;
const deUint32 valueMask = numBits == 32 ? ~0u : (1u<<numBits)-1u;
m_initialValue = ~((1u<<(numBits-1u)) | 1u) & valueMask; // All bits except lowest and highest set.
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
const int numBits = m_precision == PRECISION_HIGHP ? 32 :
m_precision == PRECISION_MEDIUMP ? 16 : 8;
const deUint32 valueMask = numBits == 32 ? ~0u : (1u<<numBits)-1u;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupMask = 1<<rnd.getInt(0, numBits-2); // One bit is always set.
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = (rnd.getUint32() & valueMask) | groupMask;
}
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupOutput = *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
deUint32 expectedValue = m_initialValue;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
expectedValue &= inputValue;
}
if (expectedValue != groupOutput)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expectedValue) << ", got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
return false;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if ((outputValue & ~m_initialValue) != 0)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << tcu::toHex(outputValue)
<< TestLog::EndMessage;
return false;
}
}
}
return true;
}
};
class ShaderAtomicOrCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicOrCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicOr", operandType, type, precision, UVec3(3,2,1))
{
m_initialValue = 1u; // Lowest bit set.
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
const int numBits = m_precision == PRECISION_HIGHP ? 32 :
m_precision == PRECISION_MEDIUMP ? 16 : 8;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = 1u<<rnd.getInt(0, numBits-1);
}
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupOutput = *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
deUint32 expectedValue = m_initialValue;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
expectedValue |= inputValue;
}
if (expectedValue != groupOutput)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expectedValue) << ", got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
return false;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if ((outputValue & m_initialValue) == 0)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << tcu::toHex(outputValue)
<< TestLog::EndMessage;
return false;
}
}
}
return true;
}
};
class ShaderAtomicXorCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicXorCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicXor", operandType, type, precision, UVec3(3,2,1))
{
m_initialValue = 0;
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
de::Random rnd (deStringHash(getName()));
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
// First uses random bit-pattern.
*(deUint32*)((deUint8*)inputs + stride*(groupOffset)) = rnd.getUint32();
// Rest have either all or no bits set.
for (int localNdx = 1; localNdx < workGroupSize; localNdx++)
*(deUint32*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = rnd.getBool() ? ~0u : 0u;
}
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
const int numBits = m_precision == PRECISION_HIGHP ? 32 :
m_precision == PRECISION_MEDIUMP ? 16 : 8;
const deUint32 compareMask = numBits == 32 ? ~0u : (1u<<numBits)-1u;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const deUint32 groupOutput = *(const deUint32*)((const deUint8*)groupOutputs + groupNdx*groupStride);
const deUint32 randomValue = *(const int*)((const deUint8*)inputs + inputStride*groupOffset);
const deUint32 expected0 = randomValue ^ 0u;
const deUint32 expected1 = randomValue ^ ~0u;
int numXorZeros = (m_initialValue == 0) ? 1 : 0;
for (int localNdx = 1; localNdx < workGroupSize; localNdx++)
{
const deUint32 inputValue = *(const deUint32*)((const deUint8*)inputs + inputStride*(groupOffset+localNdx));
if (inputValue == 0)
numXorZeros += 1;
}
const deUint32 expected = (numXorZeros%2 == 0) ? expected0 : expected1;
if ((groupOutput & compareMask) != (expected & compareMask))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected " << tcu::toHex(expected0)
<< " or " << tcu::toHex(expected1) << " (compare mask " << tcu::toHex(compareMask)
<< "), got " << tcu::toHex(groupOutput) << TestLog::EndMessage;
return false;
}
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const deUint32 outputValue = *(const deUint32*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if ((outputValue&compareMask) != 0 &&
(outputValue&compareMask) != compareMask &&
(outputValue&compareMask) != (expected0&compareMask) &&
(outputValue&compareMask) != (expected1&compareMask))
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << tcu::toHex(outputValue)
<< TestLog::EndMessage;
return false;
}
}
}
return true;
}
};
class ShaderAtomicExchangeCase : public ShaderAtomicOpCase
{
public:
ShaderAtomicExchangeCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: ShaderAtomicOpCase(context, name, "atomicExchange", operandType, type, precision, UVec3(3,2,1))
{
m_initialValue = 0;
}
protected:
void getInputs (int numValues, int stride, void* inputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
*(int*)((deUint8*)inputs + stride*(groupOffset+localNdx)) = localNdx+1;
}
}
bool verify (int numValues, int inputStride, const void* inputs, int outputStride, const void* outputs, int groupStride, const void* groupOutputs) const
{
const int workGroupSize = (int)product(m_workGroupSize);
const int numWorkGroups = numValues/workGroupSize;
DE_UNREF(inputStride && inputs);
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const int groupOutput = *(const int*)((const deUint8*)groupOutputs + groupNdx*groupStride);
set<int> usedValues;
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const int outputValue = *(const int*)((const deUint8*)outputs + outputStride*(groupOffset+localNdx));
if (!de::inRange(outputValue, 0, workGroupSize) || usedValues.find(outputValue) != usedValues.end())
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": found unexpected value " << outputValue
<< TestLog::EndMessage;
return false;
}
usedValues.insert(outputValue);
}
if (!de::inRange(groupOutput, 0, workGroupSize) || usedValues.find(groupOutput) != usedValues.end())
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": unexpected final value" << groupOutput << TestLog::EndMessage;
return false;
}
}
return true;
}
};
class ShaderAtomicCompSwapCase : public TestCase
{
public:
ShaderAtomicCompSwapCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision);
~ShaderAtomicCompSwapCase (void);
void init (void);
void deinit (void);
IterateResult iterate (void);
protected:
private:
ShaderAtomicCompSwapCase (const ShaderAtomicCompSwapCase& other);
ShaderAtomicCompSwapCase& operator= (const ShaderAtomicCompSwapCase& other);
const AtomicOperandType m_operandType;
const DataType m_type;
const Precision m_precision;
const UVec3 m_workGroupSize;
const UVec3 m_numWorkGroups;
ShaderProgram* m_program;
};
ShaderAtomicCompSwapCase::ShaderAtomicCompSwapCase (Context& context, const char* name, AtomicOperandType operandType, DataType type, Precision precision)
: TestCase (context, name, "atomicCompSwap() Test")
, m_operandType (operandType)
, m_type (type)
, m_precision (precision)
, m_workGroupSize (3,2,1)
, m_numWorkGroups (4,4,4)
, m_program (DE_NULL)
{
}
ShaderAtomicCompSwapCase::~ShaderAtomicCompSwapCase (void)
{
ShaderAtomicCompSwapCase::deinit();
}
void ShaderAtomicCompSwapCase::init (void)
{
const bool isSSBO = m_operandType == ATOMIC_OPERAND_BUFFER_VARIABLE;
const char* precName = getPrecisionName(m_precision);
const char* typeName = getDataTypeName(m_type);
const deUint32 numValues = product(m_workGroupSize)*product(m_numWorkGroups);
std::ostringstream src;
src << "#version 310 es\n"
<< "layout(local_size_x = " << m_workGroupSize.x()
<< ", local_size_y = " << m_workGroupSize.y()
<< ", local_size_z = " << m_workGroupSize.z() << ") in;\n"
<< "layout(binding = 0) buffer InOut\n"
<< "{\n"
<< " " << precName << " " << typeName << " compareValues[" << numValues << "];\n"
<< " " << precName << " " << typeName << " exchangeValues[" << numValues << "];\n"
<< " " << precName << " " << typeName << " outputValues[" << numValues << "];\n"
<< " " << (isSSBO ? "coherent " : "") << precName << " " << typeName << " groupValues[" << product(m_numWorkGroups) << "];\n"
<< "} sb_inout;\n";
if (!isSSBO)
src << "shared " << precName << " " << typeName << " s_var;\n";
src << "\n"
<< "void main (void)\n"
<< "{\n"
<< " uint localSize = gl_WorkGroupSize.x*gl_WorkGroupSize.y*gl_WorkGroupSize.z;\n"
<< " uint globalNdx = gl_NumWorkGroups.x*gl_NumWorkGroups.y*gl_WorkGroupID.z + gl_NumWorkGroups.x*gl_WorkGroupID.y + gl_WorkGroupID.x;\n"
<< " uint globalOffs = localSize*globalNdx;\n"
<< " uint offset = globalOffs + gl_LocalInvocationIndex;\n"
<< "\n";
if (!isSSBO)
{
src << " if (gl_LocalInvocationIndex == 0u)\n"
<< " s_var = " << typeName << "(" << 0 << ");\n"
<< "\n";
}
src << " " << precName << " " << typeName << " compare = sb_inout.compareValues[offset];\n"
<< " " << precName << " " << typeName << " exchange = sb_inout.exchangeValues[offset];\n"
<< " " << precName << " " << typeName << " result;\n"
<< " bool swapDone = false;\n"
<< "\n"
<< " for (uint ndx = 0u; ndx < localSize; ndx++)\n"
<< " {\n"
<< " barrier();\n"
<< " if (!swapDone)\n"
<< " {\n"
<< " result = atomicCompSwap(" << (isSSBO ? "sb_inout.groupValues[globalNdx]" : "s_var") << ", compare, exchange);\n"
<< " if (result == compare)\n"
<< " swapDone = true;\n"
<< " }\n"
<< " }\n"
<< "\n"
<< " sb_inout.outputValues[offset] = result;\n";
if (!isSSBO)
{
src << " barrier();\n"
<< " if (gl_LocalInvocationIndex == 0u)\n"
<< " sb_inout.groupValues[globalNdx] = s_var;\n";
}
src << "}\n";
DE_ASSERT(!m_program);
m_program = new ShaderProgram(m_context.getRenderContext(), ProgramSources() << ComputeSource(src.str()));
m_testCtx.getLog() << *m_program;
if (!m_program->isOk())
{
delete m_program;
m_program = DE_NULL;
throw tcu::TestError("Compile failed");
}
}
void ShaderAtomicCompSwapCase::deinit (void)
{
delete m_program;
m_program = DE_NULL;
}
ShaderAtomicOpCase::IterateResult ShaderAtomicCompSwapCase::iterate (void)
{
const glw::Functions& gl = m_context.getRenderContext().getFunctions();
const deUint32 program = m_program->getProgram();
const Buffer inoutBuffer (m_context.getRenderContext());
const deUint32 blockNdx = gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "InOut");
const InterfaceBlockInfo blockInfo = getProgramInterfaceBlockInfo(gl, program, GL_SHADER_STORAGE_BLOCK, blockNdx);
const deUint32 cmpVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.compareValues[0]");
const InterfaceVariableInfo cmpVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, cmpVarNdx);
const deUint32 exhVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.exchangeValues[0]");
const InterfaceVariableInfo exhVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, exhVarNdx);
const deUint32 outVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.outputValues[0]");
const InterfaceVariableInfo outVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, outVarNdx);
const deUint32 groupVarNdx = gl.getProgramResourceIndex(program, GL_BUFFER_VARIABLE, "InOut.groupValues[0]");
const InterfaceVariableInfo groupVarInfo = getProgramInterfaceVariableInfo(gl, program, GL_BUFFER_VARIABLE, groupVarNdx);
const deUint32 numValues = product(m_workGroupSize)*product(m_numWorkGroups);
TCU_CHECK(cmpVarInfo.arraySize == numValues &&
exhVarInfo.arraySize == numValues &&
outVarInfo.arraySize == numValues &&
groupVarInfo.arraySize == product(m_numWorkGroups));
gl.useProgram(program);
// \todo [2013-09-05 pyry] Use randomized input values!
// Setup buffer.
{
const deUint32 workGroupSize = product(m_workGroupSize);
vector<deUint8> bufData (blockInfo.dataSize);
std::fill(bufData.begin(), bufData.end(), 0);
for (deUint32 ndx = 0; ndx < numValues; ndx++)
*(deUint32*)(&bufData[0] + cmpVarInfo.offset + cmpVarInfo.arrayStride*ndx) = ndx%workGroupSize;
for (deUint32 ndx = 0; ndx < numValues; ndx++)
*(deUint32*)(&bufData[0] + exhVarInfo.offset + exhVarInfo.arrayStride*ndx) = (ndx%workGroupSize)+1;
gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, *inoutBuffer);
gl.bufferData(GL_SHADER_STORAGE_BUFFER, blockInfo.dataSize, &bufData[0], GL_STATIC_READ);
gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *inoutBuffer);
GLU_EXPECT_NO_ERROR(gl.getError(), "Output buffer setup failed");
}
gl.dispatchCompute(m_numWorkGroups.x(), m_numWorkGroups.y(), m_numWorkGroups.z());
// Read back and compare
{
const void* resPtr = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, blockInfo.dataSize, GL_MAP_READ_BIT);
const int numWorkGroups = (int)product(m_numWorkGroups);
const int workGroupSize = (int)product(m_workGroupSize);
bool isOk = true;
GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()");
TCU_CHECK(resPtr);
for (int groupNdx = 0; groupNdx < numWorkGroups; groupNdx++)
{
const int groupOffset = groupNdx*workGroupSize;
const int groupOutput = *(const int*)((const deUint8*)resPtr + groupVarInfo.offset + groupNdx*groupVarInfo.arrayStride);
for (int localNdx = 0; localNdx < workGroupSize; localNdx++)
{
const int refValue = localNdx;
const int outputValue = *(const int*)((const deUint8*)resPtr + outVarInfo.offset + outVarInfo.arrayStride*(groupOffset+localNdx));
if (outputValue != refValue)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ", invocation " << localNdx
<< ": expected " << refValue << ", got " << outputValue
<< TestLog::EndMessage;
isOk = false;
break;
}
}
if (groupOutput != workGroupSize)
{
m_testCtx.getLog() << TestLog::Message << "ERROR: at group " << groupNdx << ": expected" << workGroupSize << ", got " << groupOutput << TestLog::EndMessage;
isOk = false;
break;
}
}
gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER);
GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer()");
m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
isOk ? "Pass" : "Comparison failed");
}
return STOP;
}
ShaderAtomicOpTests::ShaderAtomicOpTests (Context& context, const char* name, AtomicOperandType operandType)
: TestCaseGroup (context, name, "Atomic Operation Tests")
, m_operandType (operandType)
{
}
ShaderAtomicOpTests::~ShaderAtomicOpTests (void)
{
}
template<typename T>
static tcu::TestCaseGroup* createAtomicOpGroup (Context& context, AtomicOperandType operandType, const char* groupName)
{
tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(context.getTestContext(), groupName, (string("Atomic ") + groupName).c_str());
try
{
for (int precNdx = 0; precNdx < PRECISION_LAST; precNdx++)
{
for (int typeNdx = 0; typeNdx < 2; typeNdx++)
{
const Precision precision = Precision(precNdx);
const DataType type = typeNdx > 0 ? TYPE_INT : TYPE_UINT;
const string caseName = string(getPrecisionName(precision)) + "_" + getDataTypeName(type);
group->addChild(new T(context, caseName.c_str(), operandType, type, precision));
}
}
return group;
}
catch (...)
{
delete group;
throw;
}
}
void ShaderAtomicOpTests::init (void)
{
addChild(createAtomicOpGroup<ShaderAtomicAddCase> (m_context, m_operandType, "add"));
addChild(createAtomicOpGroup<ShaderAtomicMinCase> (m_context, m_operandType, "min"));
addChild(createAtomicOpGroup<ShaderAtomicMaxCase> (m_context, m_operandType, "max"));
addChild(createAtomicOpGroup<ShaderAtomicAndCase> (m_context, m_operandType, "and"));
addChild(createAtomicOpGroup<ShaderAtomicOrCase> (m_context, m_operandType, "or"));
addChild(createAtomicOpGroup<ShaderAtomicXorCase> (m_context, m_operandType, "xor"));
addChild(createAtomicOpGroup<ShaderAtomicExchangeCase> (m_context, m_operandType, "exchange"));
addChild(createAtomicOpGroup<ShaderAtomicCompSwapCase> (m_context, m_operandType, "compswap"));
}
} // Functional
} // gles31
} // deqp