/*------------------------------------------------------------------------- * 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 SSBO layout case. *//*--------------------------------------------------------------------*/ #include "es31fSSBOLayoutCase.hpp" #include "gluRenderContext.hpp" #include "gluShaderProgram.hpp" #include "gluPixelTransfer.hpp" #include "gluContextInfo.hpp" #include "gluRenderContext.hpp" #include "gluProgramInterfaceQuery.hpp" #include "gluObjectWrapper.hpp" #include "gluVarTypeUtil.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "tcuTestLog.hpp" #include "tcuSurface.hpp" #include "tcuRenderTarget.hpp" #include "deInt32.h" #include "deRandom.hpp" #include "deMath.h" #include "deMemory.h" #include "deString.h" #include "deStringUtil.hpp" #include #include using std::map; using std::string; using std::vector; using tcu::TestLog; namespace deqp { namespace gles31 { using glu::StructMember; using glu::StructType; using glu::VarType; namespace bb { struct LayoutFlagsFmt { uint32_t flags; LayoutFlagsFmt(uint32_t flags_) : flags(flags_) { } }; std::ostream &operator<<(std::ostream &str, const LayoutFlagsFmt &fmt) { static const struct { uint32_t bit; const char *token; } bitDesc[] = {{LAYOUT_SHARED, "shared"}, {LAYOUT_PACKED, "packed"}, {LAYOUT_STD140, "std140"}, {LAYOUT_STD430, "std430"}, {LAYOUT_ROW_MAJOR, "row_major"}, {LAYOUT_COLUMN_MAJOR, "column_major"}}; uint32_t remBits = fmt.flags; for (int descNdx = 0; descNdx < DE_LENGTH_OF_ARRAY(bitDesc); descNdx++) { if (remBits & bitDesc[descNdx].bit) { if (remBits != fmt.flags) str << ", "; str << bitDesc[descNdx].token; remBits &= ~bitDesc[descNdx].bit; } } DE_ASSERT(remBits == 0); return str; } // BufferVar implementation. BufferVar::BufferVar(const char *name, const VarType &type, uint32_t flags) : m_name(name), m_type(type), m_flags(flags) { } // BufferBlock implementation. BufferBlock::BufferBlock(const char *blockName) : m_blockName(blockName), m_arraySize(-1), m_flags(0) { setArraySize(0); } void BufferBlock::setArraySize(int arraySize) { DE_ASSERT(arraySize >= 0); m_lastUnsizedArraySizes.resize(arraySize == 0 ? 1 : arraySize, 0); m_arraySize = arraySize; } struct BlockLayoutEntry { BlockLayoutEntry(void) : size(0) { } std::string name; int size; std::vector activeVarIndices; }; std::ostream &operator<<(std::ostream &stream, const BlockLayoutEntry &entry) { stream << entry.name << " { name = " << entry.name << ", size = " << entry.size << ", activeVarIndices = ["; for (vector::const_iterator i = entry.activeVarIndices.begin(); i != entry.activeVarIndices.end(); i++) { if (i != entry.activeVarIndices.begin()) stream << ", "; stream << *i; } stream << "] }"; return stream; } struct BufferVarLayoutEntry { BufferVarLayoutEntry(void) : type(glu::TYPE_LAST) , blockNdx(-1) , offset(-1) , arraySize(-1) , arrayStride(-1) , matrixStride(-1) , topLevelArraySize(-1) , topLevelArrayStride(-1) , isRowMajor(false) { } std::string name; glu::DataType type; int blockNdx; int offset; int arraySize; int arrayStride; int matrixStride; int topLevelArraySize; int topLevelArrayStride; bool isRowMajor; }; static bool isUnsizedArray(const BufferVarLayoutEntry &entry) { DE_ASSERT(entry.arraySize != 0 || entry.topLevelArraySize != 0); return entry.arraySize == 0 || entry.topLevelArraySize == 0; } std::ostream &operator<<(std::ostream &stream, const BufferVarLayoutEntry &entry) { stream << entry.name << " { type = " << glu::getDataTypeName(entry.type) << ", blockNdx = " << entry.blockNdx << ", offset = " << entry.offset << ", arraySize = " << entry.arraySize << ", arrayStride = " << entry.arrayStride << ", matrixStride = " << entry.matrixStride << ", topLevelArraySize = " << entry.topLevelArraySize << ", topLevelArrayStride = " << entry.topLevelArrayStride << ", isRowMajor = " << (entry.isRowMajor ? "true" : "false") << " }"; return stream; } class BufferLayout { public: std::vector blocks; std::vector bufferVars; int getVariableIndex(const string &name) const; int getBlockIndex(const string &name) const; }; // \todo [2012-01-24 pyry] Speed up lookups using hash. int BufferLayout::getVariableIndex(const string &name) const { for (int ndx = 0; ndx < (int)bufferVars.size(); ndx++) { if (bufferVars[ndx].name == name) return ndx; } return -1; } int BufferLayout::getBlockIndex(const string &name) const { for (int ndx = 0; ndx < (int)blocks.size(); ndx++) { if (blocks[ndx].name == name) return ndx; } return -1; } // ShaderInterface implementation. ShaderInterface::ShaderInterface(void) { } ShaderInterface::~ShaderInterface(void) { for (std::vector::iterator i = m_structs.begin(); i != m_structs.end(); i++) delete *i; for (std::vector::iterator i = m_bufferBlocks.begin(); i != m_bufferBlocks.end(); i++) delete *i; } StructType &ShaderInterface::allocStruct(const char *name) { m_structs.reserve(m_structs.size() + 1); m_structs.push_back(new StructType(name)); return *m_structs.back(); } struct StructNameEquals { std::string name; StructNameEquals(const char *name_) : name(name_) { } bool operator()(const StructType *type) const { return type->getTypeName() && name == type->getTypeName(); } }; const StructType *ShaderInterface::findStruct(const char *name) const { std::vector::const_iterator pos = std::find_if(m_structs.begin(), m_structs.end(), StructNameEquals(name)); return pos != m_structs.end() ? *pos : DE_NULL; } void ShaderInterface::getNamedStructs(std::vector &structs) const { for (std::vector::const_iterator i = m_structs.begin(); i != m_structs.end(); i++) { if ((*i)->getTypeName() != DE_NULL) structs.push_back(*i); } } BufferBlock &ShaderInterface::allocBlock(const char *name) { m_bufferBlocks.reserve(m_bufferBlocks.size() + 1); m_bufferBlocks.push_back(new BufferBlock(name)); return *m_bufferBlocks.back(); } // BlockDataPtr struct BlockDataPtr { void *ptr; int size; //!< Redundant, for debugging purposes. int lastUnsizedArraySize; BlockDataPtr(void *ptr_, int size_, int lastUnsizedArraySize_) : ptr(ptr_) , size(size_) , lastUnsizedArraySize(lastUnsizedArraySize_) { } BlockDataPtr(void) : ptr(DE_NULL), size(0), lastUnsizedArraySize(0) { } }; namespace // Utilities { int findBlockIndex(const BufferLayout &layout, const string &name) { for (int ndx = 0; ndx < (int)layout.blocks.size(); ndx++) { if (layout.blocks[ndx].name == name) return ndx; } return -1; } // Layout computation. int getDataTypeByteSize(glu::DataType type) { return glu::getDataTypeScalarSize(type) * (int)sizeof(uint32_t); } int getDataTypeByteAlignment(glu::DataType type) { switch (type) { case glu::TYPE_FLOAT: case glu::TYPE_INT: case glu::TYPE_UINT: case glu::TYPE_BOOL: return 1 * (int)sizeof(uint32_t); case glu::TYPE_FLOAT_VEC2: case glu::TYPE_INT_VEC2: case glu::TYPE_UINT_VEC2: case glu::TYPE_BOOL_VEC2: return 2 * (int)sizeof(uint32_t); case glu::TYPE_FLOAT_VEC3: case glu::TYPE_INT_VEC3: case glu::TYPE_UINT_VEC3: case glu::TYPE_BOOL_VEC3: // Fall-through to vec4 case glu::TYPE_FLOAT_VEC4: case glu::TYPE_INT_VEC4: case glu::TYPE_UINT_VEC4: case glu::TYPE_BOOL_VEC4: return 4 * (int)sizeof(uint32_t); default: DE_ASSERT(false); return 0; } } int computeStd140BaseAlignment(const VarType &type, uint32_t layoutFlags) { const int vec4Alignment = (int)sizeof(uint32_t) * 4; if (type.isBasicType()) { glu::DataType basicType = type.getBasicType(); if (glu::isDataTypeMatrix(basicType)) { const bool isRowMajor = !!(layoutFlags & LAYOUT_ROW_MAJOR); const int vecSize = isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType) : glu::getDataTypeMatrixNumRows(basicType); const int vecAlign = deAlign32(getDataTypeByteAlignment(glu::getDataTypeFloatVec(vecSize)), vec4Alignment); return vecAlign; } else return getDataTypeByteAlignment(basicType); } else if (type.isArrayType()) { int elemAlignment = computeStd140BaseAlignment(type.getElementType(), layoutFlags); // Round up to alignment of vec4 return deAlign32(elemAlignment, vec4Alignment); } else { DE_ASSERT(type.isStructType()); int maxBaseAlignment = 0; for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++) maxBaseAlignment = de::max(maxBaseAlignment, computeStd140BaseAlignment(memberIter->getType(), layoutFlags)); return deAlign32(maxBaseAlignment, vec4Alignment); } } int computeStd430BaseAlignment(const VarType &type, uint32_t layoutFlags) { // Otherwise identical to std140 except that alignment of structures and arrays // are not rounded up to alignment of vec4. if (type.isBasicType()) { glu::DataType basicType = type.getBasicType(); if (glu::isDataTypeMatrix(basicType)) { const bool isRowMajor = !!(layoutFlags & LAYOUT_ROW_MAJOR); const int vecSize = isRowMajor ? glu::getDataTypeMatrixNumColumns(basicType) : glu::getDataTypeMatrixNumRows(basicType); const int vecAlign = getDataTypeByteAlignment(glu::getDataTypeFloatVec(vecSize)); return vecAlign; } else return getDataTypeByteAlignment(basicType); } else if (type.isArrayType()) { return computeStd430BaseAlignment(type.getElementType(), layoutFlags); } else { DE_ASSERT(type.isStructType()); int maxBaseAlignment = 0; for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++) maxBaseAlignment = de::max(maxBaseAlignment, computeStd430BaseAlignment(memberIter->getType(), layoutFlags)); return maxBaseAlignment; } } inline uint32_t mergeLayoutFlags(uint32_t prevFlags, uint32_t newFlags) { const uint32_t packingMask = LAYOUT_PACKED | LAYOUT_SHARED | LAYOUT_STD140 | LAYOUT_STD430; const uint32_t matrixMask = LAYOUT_ROW_MAJOR | LAYOUT_COLUMN_MAJOR; uint32_t mergedFlags = 0; mergedFlags |= ((newFlags & packingMask) ? newFlags : prevFlags) & packingMask; mergedFlags |= ((newFlags & matrixMask) ? newFlags : prevFlags) & matrixMask; return mergedFlags; } //! Appends all child elements to layout, returns value that should be appended to offset. int computeReferenceLayout(BufferLayout &layout, int curBlockNdx, int baseOffset, const std::string &curPrefix, const VarType &type, uint32_t layoutFlags) { // Reference layout uses std430 rules by default. std140 rules are // choosen only for blocks that have std140 layout. const bool isStd140 = (layoutFlags & LAYOUT_STD140) != 0; const int baseAlignment = isStd140 ? computeStd140BaseAlignment(type, layoutFlags) : computeStd430BaseAlignment(type, layoutFlags); int curOffset = deAlign32(baseOffset, baseAlignment); const int topLevelArraySize = 1; // Default values const int topLevelArrayStride = 0; if (type.isBasicType()) { const glu::DataType basicType = type.getBasicType(); BufferVarLayoutEntry entry; entry.name = curPrefix; entry.type = basicType; entry.arraySize = 1; entry.arrayStride = 0; entry.matrixStride = 0; entry.topLevelArraySize = topLevelArraySize; entry.topLevelArrayStride = topLevelArrayStride; entry.blockNdx = curBlockNdx; if (glu::isDataTypeMatrix(basicType)) { // Array of vectors as specified in rules 5 & 7. const bool isRowMajor = !!(layoutFlags & LAYOUT_ROW_MAJOR); const int numVecs = isRowMajor ? glu::getDataTypeMatrixNumRows(basicType) : glu::getDataTypeMatrixNumColumns(basicType); entry.offset = curOffset; entry.matrixStride = baseAlignment; entry.isRowMajor = isRowMajor; curOffset += numVecs * baseAlignment; } else { // Scalar or vector. entry.offset = curOffset; curOffset += getDataTypeByteSize(basicType); } layout.bufferVars.push_back(entry); } else if (type.isArrayType()) { const VarType &elemType = type.getElementType(); if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType())) { // Array of scalars or vectors. const glu::DataType elemBasicType = elemType.getBasicType(); const int stride = baseAlignment; BufferVarLayoutEntry entry; entry.name = curPrefix + "[0]"; // Array variables are always postfixed with [0] entry.type = elemBasicType; entry.blockNdx = curBlockNdx; entry.offset = curOffset; entry.arraySize = type.getArraySize(); entry.arrayStride = stride; entry.matrixStride = 0; entry.topLevelArraySize = topLevelArraySize; entry.topLevelArrayStride = topLevelArrayStride; curOffset += stride * type.getArraySize(); layout.bufferVars.push_back(entry); } else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType())) { // Array of matrices. const glu::DataType elemBasicType = elemType.getBasicType(); const bool isRowMajor = !!(layoutFlags & LAYOUT_ROW_MAJOR); const int numVecs = isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType) : glu::getDataTypeMatrixNumColumns(elemBasicType); const int vecStride = baseAlignment; BufferVarLayoutEntry entry; entry.name = curPrefix + "[0]"; // Array variables are always postfixed with [0] entry.type = elemBasicType; entry.blockNdx = curBlockNdx; entry.offset = curOffset; entry.arraySize = type.getArraySize(); entry.arrayStride = vecStride * numVecs; entry.matrixStride = vecStride; entry.isRowMajor = isRowMajor; entry.topLevelArraySize = topLevelArraySize; entry.topLevelArrayStride = topLevelArrayStride; curOffset += numVecs * vecStride * type.getArraySize(); layout.bufferVars.push_back(entry); } else { DE_ASSERT(elemType.isStructType() || elemType.isArrayType()); for (int elemNdx = 0; elemNdx < type.getArraySize(); elemNdx++) curOffset += computeReferenceLayout(layout, curBlockNdx, curOffset, curPrefix + "[" + de::toString(elemNdx) + "]", type.getElementType(), layoutFlags); } } else { DE_ASSERT(type.isStructType()); for (StructType::ConstIterator memberIter = type.getStructPtr()->begin(); memberIter != type.getStructPtr()->end(); memberIter++) curOffset += computeReferenceLayout(layout, curBlockNdx, curOffset, curPrefix + "." + memberIter->getName(), memberIter->getType(), layoutFlags); curOffset = deAlign32(curOffset, baseAlignment); } return curOffset - baseOffset; } //! Appends all child elements to layout, returns offset increment. int computeReferenceLayout(BufferLayout &layout, int curBlockNdx, const std::string &blockPrefix, int baseOffset, const BufferVar &bufVar, uint32_t blockLayoutFlags) { const VarType &varType = bufVar.getType(); const uint32_t combinedFlags = mergeLayoutFlags(blockLayoutFlags, bufVar.getFlags()); if (varType.isArrayType()) { // Top-level arrays need special care. const int topLevelArraySize = varType.getArraySize() == VarType::UNSIZED_ARRAY ? 0 : varType.getArraySize(); const string prefix = blockPrefix + bufVar.getName() + "[0]"; const bool isStd140 = (blockLayoutFlags & LAYOUT_STD140) != 0; const int vec4Align = (int)sizeof(uint32_t) * 4; const int baseAlignment = isStd140 ? computeStd140BaseAlignment(varType, combinedFlags) : computeStd430BaseAlignment(varType, combinedFlags); int curOffset = deAlign32(baseOffset, baseAlignment); const VarType &elemType = varType.getElementType(); if (elemType.isBasicType() && !glu::isDataTypeMatrix(elemType.getBasicType())) { // Array of scalars or vectors. const glu::DataType elemBasicType = elemType.getBasicType(); const int elemBaseAlign = getDataTypeByteAlignment(elemBasicType); const int stride = isStd140 ? deAlign32(elemBaseAlign, vec4Align) : elemBaseAlign; BufferVarLayoutEntry entry; entry.name = prefix; entry.topLevelArraySize = 1; entry.topLevelArrayStride = 0; entry.type = elemBasicType; entry.blockNdx = curBlockNdx; entry.offset = curOffset; entry.arraySize = topLevelArraySize; entry.arrayStride = stride; entry.matrixStride = 0; layout.bufferVars.push_back(entry); curOffset += stride * topLevelArraySize; } else if (elemType.isBasicType() && glu::isDataTypeMatrix(elemType.getBasicType())) { // Array of matrices. const glu::DataType elemBasicType = elemType.getBasicType(); const bool isRowMajor = !!(combinedFlags & LAYOUT_ROW_MAJOR); const int vecSize = isRowMajor ? glu::getDataTypeMatrixNumColumns(elemBasicType) : glu::getDataTypeMatrixNumRows(elemBasicType); const int numVecs = isRowMajor ? glu::getDataTypeMatrixNumRows(elemBasicType) : glu::getDataTypeMatrixNumColumns(elemBasicType); const glu::DataType vecType = glu::getDataTypeFloatVec(vecSize); const int vecBaseAlign = getDataTypeByteAlignment(vecType); const int stride = isStd140 ? deAlign32(vecBaseAlign, vec4Align) : vecBaseAlign; BufferVarLayoutEntry entry; entry.name = prefix; entry.topLevelArraySize = 1; entry.topLevelArrayStride = 0; entry.type = elemBasicType; entry.blockNdx = curBlockNdx; entry.offset = curOffset; entry.arraySize = topLevelArraySize; entry.arrayStride = stride * numVecs; entry.matrixStride = stride; entry.isRowMajor = isRowMajor; layout.bufferVars.push_back(entry); curOffset += stride * numVecs * topLevelArraySize; } else { DE_ASSERT(elemType.isStructType() || elemType.isArrayType()); // Struct base alignment is not added multiple times as curOffset supplied to computeReferenceLayout // was already aligned correctly. Thus computeReferenceLayout should not add any extra padding // before struct. Padding after struct will be added as it should. // // Stride could be computed prior to creating child elements, but it would essentially require running // the layout computation twice. Instead we fix stride to child elements afterwards. const int firstChildNdx = (int)layout.bufferVars.size(); const int stride = computeReferenceLayout(layout, curBlockNdx, curOffset, prefix, varType.getElementType(), combinedFlags); for (int childNdx = firstChildNdx; childNdx < (int)layout.bufferVars.size(); childNdx++) { layout.bufferVars[childNdx].topLevelArraySize = topLevelArraySize; layout.bufferVars[childNdx].topLevelArrayStride = stride; } curOffset += stride * topLevelArraySize; } return curOffset - baseOffset; } else return computeReferenceLayout(layout, curBlockNdx, baseOffset, blockPrefix + bufVar.getName(), varType, combinedFlags); } void computeReferenceLayout(BufferLayout &layout, const ShaderInterface &interface) { int numBlocks = interface.getNumBlocks(); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BufferBlock &block = interface.getBlock(blockNdx); bool hasInstanceName = block.getInstanceName() != DE_NULL; std::string blockPrefix = hasInstanceName ? (std::string(block.getBlockName()) + ".") : std::string(""); int curOffset = 0; int activeBlockNdx = (int)layout.blocks.size(); int firstVarNdx = (int)layout.bufferVars.size(); for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++) { const BufferVar &bufVar = *varIter; curOffset += computeReferenceLayout(layout, activeBlockNdx, blockPrefix, curOffset, bufVar, block.getFlags()); } int varIndicesEnd = (int)layout.bufferVars.size(); int blockSize = curOffset; int numInstances = block.isArray() ? block.getArraySize() : 1; // Create block layout entries for each instance. for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { // Allocate entry for instance. layout.blocks.push_back(BlockLayoutEntry()); BlockLayoutEntry &blockEntry = layout.blocks.back(); blockEntry.name = block.getBlockName(); blockEntry.size = blockSize; // Compute active variable set for block. for (int varNdx = firstVarNdx; varNdx < varIndicesEnd; varNdx++) blockEntry.activeVarIndices.push_back(varNdx); if (block.isArray()) blockEntry.name += "[" + de::toString(instanceNdx) + "]"; } } } // Value generator. void generateValue(const BufferVarLayoutEntry &entry, int unsizedArraySize, void *basePtr, de::Random &rnd) { const glu::DataType scalarType = glu::getDataTypeScalarType(entry.type); const int scalarSize = glu::getDataTypeScalarSize(entry.type); const int arraySize = entry.arraySize == 0 ? unsizedArraySize : entry.arraySize; const int arrayStride = entry.arrayStride; const int topLevelSize = entry.topLevelArraySize == 0 ? unsizedArraySize : entry.topLevelArraySize; const int topLevelStride = entry.topLevelArrayStride; const bool isMatrix = glu::isDataTypeMatrix(entry.type); const int numVecs = isMatrix ? (entry.isRowMajor ? glu::getDataTypeMatrixNumRows(entry.type) : glu::getDataTypeMatrixNumColumns(entry.type)) : 1; const int vecSize = scalarSize / numVecs; const int compSize = sizeof(uint32_t); DE_ASSERT(scalarSize % numVecs == 0); DE_ASSERT(topLevelSize >= 0); DE_ASSERT(arraySize >= 0); for (int topElemNdx = 0; topElemNdx < topLevelSize; topElemNdx++) { uint8_t *const topElemPtr = (uint8_t *)basePtr + entry.offset + topElemNdx * topLevelStride; for (int elemNdx = 0; elemNdx < arraySize; elemNdx++) { uint8_t *const elemPtr = topElemPtr + elemNdx * arrayStride; for (int vecNdx = 0; vecNdx < numVecs; vecNdx++) { uint8_t *const vecPtr = elemPtr + (isMatrix ? vecNdx * entry.matrixStride : 0); for (int compNdx = 0; compNdx < vecSize; compNdx++) { uint8_t *const compPtr = vecPtr + compSize * compNdx; switch (scalarType) { case glu::TYPE_FLOAT: *((float *)compPtr) = (float)rnd.getInt(-9, 9); break; case glu::TYPE_INT: *((int *)compPtr) = rnd.getInt(-9, 9); break; case glu::TYPE_UINT: *((uint32_t *)compPtr) = (uint32_t)rnd.getInt(0, 9); break; // \note Random bit pattern is used for true values. Spec states that all non-zero values are // interpreted as true but some implementations fail this. case glu::TYPE_BOOL: *((uint32_t *)compPtr) = rnd.getBool() ? rnd.getUint32() | 1u : 0u; break; default: DE_ASSERT(false); } } } } } } void generateValues(const BufferLayout &layout, const vector &blockPointers, uint32_t seed) { de::Random rnd(seed); const int numBlocks = (int)layout.blocks.size(); DE_ASSERT(numBlocks == (int)blockPointers.size()); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BlockLayoutEntry &blockLayout = layout.blocks[blockNdx]; const BlockDataPtr &blockPtr = blockPointers[blockNdx]; const int numEntries = (int)layout.blocks[blockNdx].activeVarIndices.size(); for (int entryNdx = 0; entryNdx < numEntries; entryNdx++) { const int varNdx = blockLayout.activeVarIndices[entryNdx]; const BufferVarLayoutEntry &varEntry = layout.bufferVars[varNdx]; generateValue(varEntry, blockPtr.lastUnsizedArraySize, blockPtr.ptr, rnd); } } } // Shader generator. const char *getCompareFuncForType(glu::DataType type) { switch (type) { case glu::TYPE_FLOAT: return "bool compare_float (highp float a, highp float b) { return abs(a - b) < 0.05; }\n"; case glu::TYPE_FLOAT_VEC2: return "bool compare_vec2 (highp vec2 a, highp vec2 b) { return compare_float(a.x, " "b.x)&&compare_float(a.y, b.y); }\n"; case glu::TYPE_FLOAT_VEC3: return "bool compare_vec3 (highp vec3 a, highp vec3 b) { return compare_float(a.x, " "b.x)&&compare_float(a.y, b.y)&&compare_float(a.z, b.z); }\n"; case glu::TYPE_FLOAT_VEC4: return "bool compare_vec4 (highp vec4 a, highp vec4 b) { return compare_float(a.x, " "b.x)&&compare_float(a.y, b.y)&&compare_float(a.z, b.z)&&compare_float(a.w, b.w); }\n"; case glu::TYPE_FLOAT_MAT2: return "bool compare_mat2 (highp mat2 a, highp mat2 b) { return compare_vec2(a[0], " "b[0])&&compare_vec2(a[1], b[1]); }\n"; case glu::TYPE_FLOAT_MAT2X3: return "bool compare_mat2x3 (highp mat2x3 a, highp mat2x3 b){ return compare_vec3(a[0], " "b[0])&&compare_vec3(a[1], b[1]); }\n"; case glu::TYPE_FLOAT_MAT2X4: return "bool compare_mat2x4 (highp mat2x4 a, highp mat2x4 b){ return compare_vec4(a[0], " "b[0])&&compare_vec4(a[1], b[1]); }\n"; case glu::TYPE_FLOAT_MAT3X2: return "bool compare_mat3x2 (highp mat3x2 a, highp mat3x2 b){ return compare_vec2(a[0], " "b[0])&&compare_vec2(a[1], b[1])&&compare_vec2(a[2], b[2]); }\n"; case glu::TYPE_FLOAT_MAT3: return "bool compare_mat3 (highp mat3 a, highp mat3 b) { return compare_vec3(a[0], " "b[0])&&compare_vec3(a[1], b[1])&&compare_vec3(a[2], b[2]); }\n"; case glu::TYPE_FLOAT_MAT3X4: return "bool compare_mat3x4 (highp mat3x4 a, highp mat3x4 b){ return compare_vec4(a[0], " "b[0])&&compare_vec4(a[1], b[1])&&compare_vec4(a[2], b[2]); }\n"; case glu::TYPE_FLOAT_MAT4X2: return "bool compare_mat4x2 (highp mat4x2 a, highp mat4x2 b){ return compare_vec2(a[0], " "b[0])&&compare_vec2(a[1], b[1])&&compare_vec2(a[2], b[2])&&compare_vec2(a[3], b[3]); }\n"; case glu::TYPE_FLOAT_MAT4X3: return "bool compare_mat4x3 (highp mat4x3 a, highp mat4x3 b){ return compare_vec3(a[0], " "b[0])&&compare_vec3(a[1], b[1])&&compare_vec3(a[2], b[2])&&compare_vec3(a[3], b[3]); }\n"; case glu::TYPE_FLOAT_MAT4: return "bool compare_mat4 (highp mat4 a, highp mat4 b) { return compare_vec4(a[0], " "b[0])&&compare_vec4(a[1], b[1])&&compare_vec4(a[2], b[2])&&compare_vec4(a[3], b[3]); }\n"; case glu::TYPE_INT: return "bool compare_int (highp int a, highp int b) { return a == b; }\n"; case glu::TYPE_INT_VEC2: return "bool compare_ivec2 (highp ivec2 a, highp ivec2 b) { return a == b; }\n"; case glu::TYPE_INT_VEC3: return "bool compare_ivec3 (highp ivec3 a, highp ivec3 b) { return a == b; }\n"; case glu::TYPE_INT_VEC4: return "bool compare_ivec4 (highp ivec4 a, highp ivec4 b) { return a == b; }\n"; case glu::TYPE_UINT: return "bool compare_uint (highp uint a, highp uint b) { return a == b; }\n"; case glu::TYPE_UINT_VEC2: return "bool compare_uvec2 (highp uvec2 a, highp uvec2 b) { return a == b; }\n"; case glu::TYPE_UINT_VEC3: return "bool compare_uvec3 (highp uvec3 a, highp uvec3 b) { return a == b; }\n"; case glu::TYPE_UINT_VEC4: return "bool compare_uvec4 (highp uvec4 a, highp uvec4 b) { return a == b; }\n"; case glu::TYPE_BOOL: return "bool compare_bool (bool a, bool b) { return a == b; }\n"; case glu::TYPE_BOOL_VEC2: return "bool compare_bvec2 (bvec2 a, bvec2 b) { return a == b; }\n"; case glu::TYPE_BOOL_VEC3: return "bool compare_bvec3 (bvec3 a, bvec3 b) { return a == b; }\n"; case glu::TYPE_BOOL_VEC4: return "bool compare_bvec4 (bvec4 a, bvec4 b) { return a == b; }\n"; default: DE_ASSERT(false); return DE_NULL; } } void getCompareDependencies(std::set &compareFuncs, glu::DataType basicType) { switch (basicType) { case glu::TYPE_FLOAT_VEC2: case glu::TYPE_FLOAT_VEC3: case glu::TYPE_FLOAT_VEC4: compareFuncs.insert(glu::TYPE_FLOAT); compareFuncs.insert(basicType); break; case glu::TYPE_FLOAT_MAT2: case glu::TYPE_FLOAT_MAT2X3: case glu::TYPE_FLOAT_MAT2X4: case glu::TYPE_FLOAT_MAT3X2: case glu::TYPE_FLOAT_MAT3: case glu::TYPE_FLOAT_MAT3X4: case glu::TYPE_FLOAT_MAT4X2: case glu::TYPE_FLOAT_MAT4X3: case glu::TYPE_FLOAT_MAT4: compareFuncs.insert(glu::TYPE_FLOAT); compareFuncs.insert(glu::getDataTypeFloatVec(glu::getDataTypeMatrixNumRows(basicType))); compareFuncs.insert(basicType); break; default: compareFuncs.insert(basicType); break; } } void collectUniqueBasicTypes(std::set &basicTypes, const VarType &type) { if (type.isStructType()) { for (StructType::ConstIterator iter = type.getStructPtr()->begin(); iter != type.getStructPtr()->end(); ++iter) collectUniqueBasicTypes(basicTypes, iter->getType()); } else if (type.isArrayType()) collectUniqueBasicTypes(basicTypes, type.getElementType()); else { DE_ASSERT(type.isBasicType()); basicTypes.insert(type.getBasicType()); } } void collectUniqueBasicTypes(std::set &basicTypes, const BufferBlock &bufferBlock) { for (BufferBlock::const_iterator iter = bufferBlock.begin(); iter != bufferBlock.end(); ++iter) collectUniqueBasicTypes(basicTypes, iter->getType()); } void collectUniqueBasicTypes(std::set &basicTypes, const ShaderInterface &interface) { for (int ndx = 0; ndx < interface.getNumBlocks(); ++ndx) collectUniqueBasicTypes(basicTypes, interface.getBlock(ndx)); } void generateCompareFuncs(std::ostream &str, const ShaderInterface &interface) { std::set types; std::set compareFuncs; // Collect unique basic types collectUniqueBasicTypes(types, interface); // Set of compare functions required for (std::set::const_iterator iter = types.begin(); iter != types.end(); ++iter) { getCompareDependencies(compareFuncs, *iter); } for (int type = 0; type < glu::TYPE_LAST; ++type) { if (compareFuncs.find(glu::DataType(type)) != compareFuncs.end()) str << getCompareFuncForType(glu::DataType(type)); } } struct Indent { int level; Indent(int level_) : level(level_) { } }; std::ostream &operator<<(std::ostream &str, const Indent &indent) { for (int i = 0; i < indent.level; i++) str << "\t"; return str; } void generateDeclaration(std::ostream &src, const BufferVar &bufferVar, int indentLevel) { // \todo [pyry] Qualifiers if ((bufferVar.getFlags() & LAYOUT_MASK) != 0) src << "layout(" << LayoutFlagsFmt(bufferVar.getFlags() & LAYOUT_MASK) << ") "; src << glu::declare(bufferVar.getType(), bufferVar.getName(), indentLevel); } void generateDeclaration(std::ostream &src, const BufferBlock &block, int bindingPoint) { src << "layout("; if ((block.getFlags() & LAYOUT_MASK) != 0) src << LayoutFlagsFmt(block.getFlags() & LAYOUT_MASK) << ", "; src << "binding = " << bindingPoint; src << ") "; src << "buffer " << block.getBlockName(); src << "\n{\n"; for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++) { src << Indent(1); generateDeclaration(src, *varIter, 1 /* indent level */); src << ";\n"; } src << "}"; if (block.getInstanceName() != DE_NULL) { src << " " << block.getInstanceName(); if (block.isArray()) src << "[" << block.getArraySize() << "]"; } else DE_ASSERT(!block.isArray()); src << ";\n"; } void generateImmMatrixSrc(std::ostream &src, glu::DataType basicType, int matrixStride, bool isRowMajor, const void *valuePtr) { DE_ASSERT(glu::isDataTypeMatrix(basicType)); const int compSize = sizeof(uint32_t); const int numRows = glu::getDataTypeMatrixNumRows(basicType); const int numCols = glu::getDataTypeMatrixNumColumns(basicType); src << glu::getDataTypeName(basicType) << "("; // Constructed in column-wise order. for (int colNdx = 0; colNdx < numCols; colNdx++) { for (int rowNdx = 0; rowNdx < numRows; rowNdx++) { const uint8_t *compPtr = (const uint8_t *)valuePtr + (isRowMajor ? rowNdx * matrixStride + colNdx * compSize : colNdx * matrixStride + rowNdx * compSize); if (colNdx > 0 || rowNdx > 0) src << ", "; src << de::floatToString(*((const float *)compPtr), 1); } } src << ")"; } void generateImmScalarVectorSrc(std::ostream &src, glu::DataType basicType, const void *valuePtr) { DE_ASSERT(glu::isDataTypeFloatOrVec(basicType) || glu::isDataTypeIntOrIVec(basicType) || glu::isDataTypeUintOrUVec(basicType) || glu::isDataTypeBoolOrBVec(basicType)); const glu::DataType scalarType = glu::getDataTypeScalarType(basicType); const int scalarSize = glu::getDataTypeScalarSize(basicType); const int compSize = sizeof(uint32_t); if (scalarSize > 1) src << glu::getDataTypeName(basicType) << "("; for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++) { const uint8_t *compPtr = (const uint8_t *)valuePtr + scalarNdx * compSize; if (scalarNdx > 0) src << ", "; switch (scalarType) { case glu::TYPE_FLOAT: src << de::floatToString(*((const float *)compPtr), 1); break; case glu::TYPE_INT: src << *((const int *)compPtr); break; case glu::TYPE_UINT: src << *((const uint32_t *)compPtr) << "u"; break; case glu::TYPE_BOOL: src << (*((const uint32_t *)compPtr) != 0u ? "true" : "false"); break; default: DE_ASSERT(false); } } if (scalarSize > 1) src << ")"; } string getAPIName(const BufferBlock &block, const BufferVar &var, const glu::TypeComponentVector &accessPath) { std::ostringstream name; if (block.getInstanceName()) name << block.getBlockName() << "."; name << var.getName(); for (glu::TypeComponentVector::const_iterator pathComp = accessPath.begin(); pathComp != accessPath.end(); pathComp++) { if (pathComp->type == glu::VarTypeComponent::STRUCT_MEMBER) { const VarType curType = glu::getVarType(var.getType(), accessPath.begin(), pathComp); const StructType *structPtr = curType.getStructPtr(); name << "." << structPtr->getMember(pathComp->index).getName(); } else if (pathComp->type == glu::VarTypeComponent::ARRAY_ELEMENT) { if (pathComp == accessPath.begin() || (pathComp + 1) == accessPath.end()) name << "[0]"; // Top- / bottom-level array else name << "[" << pathComp->index << "]"; } else DE_ASSERT(false); } return name.str(); } string getShaderName(const BufferBlock &block, int instanceNdx, const BufferVar &var, const glu::TypeComponentVector &accessPath) { std::ostringstream name; if (block.getInstanceName()) { name << block.getInstanceName(); if (block.isArray()) name << "[" << instanceNdx << "]"; name << "."; } else DE_ASSERT(instanceNdx == 0); name << var.getName(); for (glu::TypeComponentVector::const_iterator pathComp = accessPath.begin(); pathComp != accessPath.end(); pathComp++) { if (pathComp->type == glu::VarTypeComponent::STRUCT_MEMBER) { const VarType curType = glu::getVarType(var.getType(), accessPath.begin(), pathComp); const StructType *structPtr = curType.getStructPtr(); name << "." << structPtr->getMember(pathComp->index).getName(); } else if (pathComp->type == glu::VarTypeComponent::ARRAY_ELEMENT) name << "[" << pathComp->index << "]"; else DE_ASSERT(false); } return name.str(); } int computeOffset(const BufferVarLayoutEntry &varLayout, const glu::TypeComponentVector &accessPath) { const int topLevelNdx = (accessPath.size() > 1 && accessPath.front().type == glu::VarTypeComponent::ARRAY_ELEMENT) ? accessPath.front().index : 0; const int bottomLevelNdx = (!accessPath.empty() && accessPath.back().type == glu::VarTypeComponent::ARRAY_ELEMENT) ? accessPath.back().index : 0; return varLayout.offset + varLayout.topLevelArrayStride * topLevelNdx + varLayout.arrayStride * bottomLevelNdx; } void generateCompareSrc(std::ostream &src, const char *resultVar, const BufferLayout &bufferLayout, const BufferBlock &block, int instanceNdx, const BlockDataPtr &blockPtr, const BufferVar &bufVar, const glu::SubTypeAccess &accessPath) { const VarType curType = accessPath.getType(); if (curType.isArrayType()) { const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize(); for (int elemNdx = 0; elemNdx < arraySize; elemNdx++) generateCompareSrc(src, resultVar, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.element(elemNdx)); } else if (curType.isStructType()) { const int numMembers = curType.getStructPtr()->getNumMembers(); for (int memberNdx = 0; memberNdx < numMembers; memberNdx++) generateCompareSrc(src, resultVar, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.member(memberNdx)); } else { DE_ASSERT(curType.isBasicType()); const string apiName = getAPIName(block, bufVar, accessPath.getPath()); const int varNdx = bufferLayout.getVariableIndex(apiName); DE_ASSERT(varNdx >= 0); { const BufferVarLayoutEntry &varLayout = bufferLayout.bufferVars[varNdx]; const string shaderName = getShaderName(block, instanceNdx, bufVar, accessPath.getPath()); const glu::DataType basicType = curType.getBasicType(); const bool isMatrix = glu::isDataTypeMatrix(basicType); const char *typeName = glu::getDataTypeName(basicType); const void *valuePtr = (const uint8_t *)blockPtr.ptr + computeOffset(varLayout, accessPath.getPath()); src << "\t" << resultVar << " = " << resultVar << " && compare_" << typeName << "(" << shaderName << ", "; if (isMatrix) generateImmMatrixSrc(src, basicType, varLayout.matrixStride, varLayout.isRowMajor, valuePtr); else generateImmScalarVectorSrc(src, basicType, valuePtr); src << ");\n"; } } } void generateCompareSrc(std::ostream &src, const char *resultVar, const ShaderInterface &interface, const BufferLayout &layout, const vector &blockPointers) { for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++) { const BufferBlock &block = interface.getBlock(declNdx); const bool isArray = block.isArray(); const int numInstances = isArray ? block.getArraySize() : 1; DE_ASSERT(!isArray || block.getInstanceName()); for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { const string instanceName = block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string("")); const int blockNdx = layout.getBlockIndex(instanceName); const BlockDataPtr &blockPtr = blockPointers[blockNdx]; for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++) { const BufferVar &bufVar = *varIter; if ((bufVar.getFlags() & ACCESS_READ) == 0) continue; // Don't read from that variable. generateCompareSrc(src, resultVar, layout, block, instanceNdx, blockPtr, bufVar, glu::SubTypeAccess(bufVar.getType())); } } } } // \todo [2013-10-14 pyry] Almost identical to generateCompareSrc - unify? void generateWriteSrc(std::ostream &src, const BufferLayout &bufferLayout, const BufferBlock &block, int instanceNdx, const BlockDataPtr &blockPtr, const BufferVar &bufVar, const glu::SubTypeAccess &accessPath) { const VarType curType = accessPath.getType(); if (curType.isArrayType()) { const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize(); for (int elemNdx = 0; elemNdx < arraySize; elemNdx++) generateWriteSrc(src, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.element(elemNdx)); } else if (curType.isStructType()) { const int numMembers = curType.getStructPtr()->getNumMembers(); for (int memberNdx = 0; memberNdx < numMembers; memberNdx++) generateWriteSrc(src, bufferLayout, block, instanceNdx, blockPtr, bufVar, accessPath.member(memberNdx)); } else { DE_ASSERT(curType.isBasicType()); const string apiName = getAPIName(block, bufVar, accessPath.getPath()); const int varNdx = bufferLayout.getVariableIndex(apiName); DE_ASSERT(varNdx >= 0); { const BufferVarLayoutEntry &varLayout = bufferLayout.bufferVars[varNdx]; const string shaderName = getShaderName(block, instanceNdx, bufVar, accessPath.getPath()); const glu::DataType basicType = curType.getBasicType(); const bool isMatrix = glu::isDataTypeMatrix(basicType); const void *valuePtr = (const uint8_t *)blockPtr.ptr + computeOffset(varLayout, accessPath.getPath()); src << "\t" << shaderName << " = "; if (isMatrix) generateImmMatrixSrc(src, basicType, varLayout.matrixStride, varLayout.isRowMajor, valuePtr); else generateImmScalarVectorSrc(src, basicType, valuePtr); src << ";\n"; } } } void generateWriteSrc(std::ostream &src, const ShaderInterface &interface, const BufferLayout &layout, const vector &blockPointers) { for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++) { const BufferBlock &block = interface.getBlock(declNdx); const bool isArray = block.isArray(); const int numInstances = isArray ? block.getArraySize() : 1; DE_ASSERT(!isArray || block.getInstanceName()); for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { const string instanceName = block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string("")); const int blockNdx = layout.getBlockIndex(instanceName); const BlockDataPtr &blockPtr = blockPointers[blockNdx]; for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++) { const BufferVar &bufVar = *varIter; if ((bufVar.getFlags() & ACCESS_WRITE) == 0) continue; // Don't write to that variable. generateWriteSrc(src, layout, block, instanceNdx, blockPtr, bufVar, glu::SubTypeAccess(bufVar.getType())); } } } } string generateComputeShader(const glw::Functions &gl, glu::GLSLVersion glslVersion, const ShaderInterface &interface, const BufferLayout &layout, const vector &comparePtrs, const vector &writePtrs) { std::ostringstream src; glw::GLint maxShaderStorageBufferBindings; glw::GLint maxComputeShaderStorageBlocks; DE_ASSERT(glslVersion == glu::GLSL_VERSION_310_ES || glslVersion == glu::GLSL_VERSION_430); gl.getIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings); gl.getIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &maxComputeShaderStorageBlocks); src << glu::getGLSLVersionDeclaration(glslVersion) << "\n"; src << "layout(local_size_x = 1) in;\n"; src << "\n"; std::vector namedStructs; interface.getNamedStructs(namedStructs); for (std::vector::const_iterator structIter = namedStructs.begin(); structIter != namedStructs.end(); structIter++) src << glu::declare(*structIter) << ";\n"; { int bindingPoint = 0; for (int blockNdx = 0; blockNdx < interface.getNumBlocks(); blockNdx++) { const BufferBlock &block = interface.getBlock(blockNdx); generateDeclaration(src, block, bindingPoint); bindingPoint += block.isArray() ? block.getArraySize() : 1; } if (bindingPoint > maxShaderStorageBufferBindings) { throw tcu::NotSupportedError("Test requires support for more SSBO bindings than implementation exposes"); } if (bindingPoint > maxComputeShaderStorageBlocks) { throw tcu::NotSupportedError( "Test requires support for more compute shader storage blocks than implementation exposes"); } } // Atomic counter for counting passed invocations. src << "\nlayout(binding = 0) uniform atomic_uint ac_numPassed;\n"; // Comparison utilities. src << "\n"; generateCompareFuncs(src, interface); src << "\n" "void main (void)\n" "{\n" " bool allOk = true;\n"; // Value compare. generateCompareSrc(src, "allOk", interface, layout, comparePtrs); src << " if (allOk)\n" << " atomicCounterIncrement(ac_numPassed);\n" << "\n"; // Value write. generateWriteSrc(src, interface, layout, writePtrs); src << "}\n"; return src.str(); } void getGLBufferLayout(const glw::Functions &gl, BufferLayout &layout, uint32_t program) { int numActiveBufferVars = 0; int numActiveBlocks = 0; gl.getProgramInterfaceiv(program, GL_BUFFER_VARIABLE, GL_ACTIVE_RESOURCES, &numActiveBufferVars); gl.getProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numActiveBlocks); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get number of buffer variables and buffer blocks"); // Block entries. layout.blocks.resize(numActiveBlocks); for (int blockNdx = 0; blockNdx < numActiveBlocks; blockNdx++) { BlockLayoutEntry &entry = layout.blocks[blockNdx]; const uint32_t queryParams[] = {GL_BUFFER_DATA_SIZE, GL_NUM_ACTIVE_VARIABLES, GL_NAME_LENGTH}; int returnValues[] = {0, 0, 0}; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(queryParams) == DE_LENGTH_OF_ARRAY(returnValues)); { int returnLength = 0; gl.getProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, (uint32_t)blockNdx, DE_LENGTH_OF_ARRAY(queryParams), &queryParams[0], DE_LENGTH_OF_ARRAY(returnValues), &returnLength, &returnValues[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK) failed"); if (returnLength != DE_LENGTH_OF_ARRAY(returnValues)) throw tcu::TestError("glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK) returned wrong number of values"); } entry.size = returnValues[0]; // Query active variables if (returnValues[1] > 0) { const int numBlockVars = returnValues[1]; const uint32_t queryArg = GL_ACTIVE_VARIABLES; int retLength = 0; entry.activeVarIndices.resize(numBlockVars); gl.getProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, (uint32_t)blockNdx, 1, &queryArg, numBlockVars, &retLength, &entry.activeVarIndices[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_VARIABLES) failed"); if (retLength != numBlockVars) throw tcu::TestError("glGetProgramResourceiv(GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_VARIABLES) returned " "wrong number of values"); } // Query name if (returnValues[2] > 0) { const int nameLen = returnValues[2]; int retLen = 0; vector name(nameLen); gl.getProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, (uint32_t)blockNdx, (glw::GLsizei)name.size(), &retLen, &name[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) failed"); if (retLen + 1 != nameLen) throw tcu::TestError("glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) returned invalid name. Number " "of characters written is inconsistent with NAME_LENGTH property."); if (name[nameLen - 1] != 0) throw tcu::TestError("glGetProgramResourceName(GL_SHADER_STORAGE_BLOCK) returned invalid name. " "Expected null terminator at index " + de::toString(nameLen - 1)); entry.name = &name[0]; } else throw tcu::TestError("glGetProgramResourceiv() returned invalid GL_NAME_LENGTH"); } layout.bufferVars.resize(numActiveBufferVars); for (int bufVarNdx = 0; bufVarNdx < numActiveBufferVars; bufVarNdx++) { BufferVarLayoutEntry &entry = layout.bufferVars[bufVarNdx]; const uint32_t queryParams[] = { GL_BLOCK_INDEX, // 0 GL_TYPE, // 1 GL_OFFSET, // 2 GL_ARRAY_SIZE, // 3 GL_ARRAY_STRIDE, // 4 GL_MATRIX_STRIDE, // 5 GL_TOP_LEVEL_ARRAY_SIZE, // 6 GL_TOP_LEVEL_ARRAY_STRIDE, // 7 GL_IS_ROW_MAJOR, // 8 GL_NAME_LENGTH // 9 }; int returnValues[DE_LENGTH_OF_ARRAY(queryParams)]; DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(queryParams) == DE_LENGTH_OF_ARRAY(returnValues)); { int returnLength = 0; gl.getProgramResourceiv(program, GL_BUFFER_VARIABLE, (uint32_t)bufVarNdx, DE_LENGTH_OF_ARRAY(queryParams), &queryParams[0], DE_LENGTH_OF_ARRAY(returnValues), &returnLength, &returnValues[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceiv(GL_BUFFER_VARIABLE) failed"); if (returnLength != DE_LENGTH_OF_ARRAY(returnValues)) throw tcu::TestError("glGetProgramResourceiv(GL_BUFFER_VARIABLE) returned wrong number of values"); } // Map values entry.blockNdx = returnValues[0]; entry.type = glu::getDataTypeFromGLType(returnValues[1]); entry.offset = returnValues[2]; entry.arraySize = returnValues[3]; entry.arrayStride = returnValues[4]; entry.matrixStride = returnValues[5]; entry.topLevelArraySize = returnValues[6]; entry.topLevelArrayStride = returnValues[7]; entry.isRowMajor = returnValues[8] != 0; // Query name DE_ASSERT(queryParams[9] == GL_NAME_LENGTH); if (returnValues[9] > 0) { const int nameLen = returnValues[9]; int retLen = 0; vector name(nameLen); gl.getProgramResourceName(program, GL_BUFFER_VARIABLE, (uint32_t)bufVarNdx, (glw::GLsizei)name.size(), &retLen, &name[0]); GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramResourceName(GL_BUFFER_VARIABLE) failed"); if (retLen + 1 != nameLen) throw tcu::TestError("glGetProgramResourceName(GL_BUFFER_VARIABLE) returned invalid name. Number of " "characters written is inconsistent with NAME_LENGTH property."); if (name[nameLen - 1] != 0) throw tcu::TestError("glGetProgramResourceName(GL_BUFFER_VARIABLE) returned invalid name. Expected " "null terminator at index " + de::toString(nameLen - 1)); entry.name = &name[0]; } else throw tcu::TestError("glGetProgramResourceiv() returned invalid GL_NAME_LENGTH"); } } void copyBufferVarData(const BufferVarLayoutEntry &dstEntry, const BlockDataPtr &dstBlockPtr, const BufferVarLayoutEntry &srcEntry, const BlockDataPtr &srcBlockPtr) { DE_ASSERT(dstEntry.arraySize <= srcEntry.arraySize); DE_ASSERT(dstEntry.topLevelArraySize <= srcEntry.topLevelArraySize); DE_ASSERT(dstBlockPtr.lastUnsizedArraySize <= srcBlockPtr.lastUnsizedArraySize); DE_ASSERT(dstEntry.type == srcEntry.type); uint8_t *const dstBasePtr = (uint8_t *)dstBlockPtr.ptr + dstEntry.offset; const uint8_t *const srcBasePtr = (const uint8_t *)srcBlockPtr.ptr + srcEntry.offset; const int scalarSize = glu::getDataTypeScalarSize(dstEntry.type); const bool isMatrix = glu::isDataTypeMatrix(dstEntry.type); const int compSize = sizeof(uint32_t); const int dstArraySize = dstEntry.arraySize == 0 ? dstBlockPtr.lastUnsizedArraySize : dstEntry.arraySize; const int dstArrayStride = dstEntry.arrayStride; const int dstTopLevelSize = dstEntry.topLevelArraySize == 0 ? dstBlockPtr.lastUnsizedArraySize : dstEntry.topLevelArraySize; const int dstTopLevelStride = dstEntry.topLevelArrayStride; const int srcArraySize = srcEntry.arraySize == 0 ? srcBlockPtr.lastUnsizedArraySize : srcEntry.arraySize; const int srcArrayStride = srcEntry.arrayStride; const int srcTopLevelSize = srcEntry.topLevelArraySize == 0 ? srcBlockPtr.lastUnsizedArraySize : srcEntry.topLevelArraySize; const int srcTopLevelStride = srcEntry.topLevelArrayStride; DE_ASSERT(dstArraySize <= srcArraySize && dstTopLevelSize <= srcTopLevelSize); DE_UNREF(srcArraySize && srcTopLevelSize); for (int topElemNdx = 0; topElemNdx < dstTopLevelSize; topElemNdx++) { uint8_t *const dstTopPtr = dstBasePtr + topElemNdx * dstTopLevelStride; const uint8_t *const srcTopPtr = srcBasePtr + topElemNdx * srcTopLevelStride; for (int elementNdx = 0; elementNdx < dstArraySize; elementNdx++) { uint8_t *const dstElemPtr = dstTopPtr + elementNdx * dstArrayStride; const uint8_t *const srcElemPtr = srcTopPtr + elementNdx * srcArrayStride; if (isMatrix) { const int numRows = glu::getDataTypeMatrixNumRows(dstEntry.type); const int numCols = glu::getDataTypeMatrixNumColumns(dstEntry.type); for (int colNdx = 0; colNdx < numCols; colNdx++) { for (int rowNdx = 0; rowNdx < numRows; rowNdx++) { uint8_t *dstCompPtr = dstElemPtr + (dstEntry.isRowMajor ? rowNdx * dstEntry.matrixStride + colNdx * compSize : colNdx * dstEntry.matrixStride + rowNdx * compSize); const uint8_t *srcCompPtr = srcElemPtr + (srcEntry.isRowMajor ? rowNdx * srcEntry.matrixStride + colNdx * compSize : colNdx * srcEntry.matrixStride + rowNdx * compSize); DE_ASSERT((intptr_t)(srcCompPtr + compSize) - (intptr_t)srcBlockPtr.ptr <= (intptr_t)srcBlockPtr.size); DE_ASSERT((intptr_t)(dstCompPtr + compSize) - (intptr_t)dstBlockPtr.ptr <= (intptr_t)dstBlockPtr.size); deMemcpy(dstCompPtr, srcCompPtr, compSize); } } } else { DE_ASSERT((intptr_t)(srcElemPtr + scalarSize * compSize) - (intptr_t)srcBlockPtr.ptr <= (intptr_t)srcBlockPtr.size); DE_ASSERT((intptr_t)(dstElemPtr + scalarSize * compSize) - (intptr_t)dstBlockPtr.ptr <= (intptr_t)dstBlockPtr.size); deMemcpy(dstElemPtr, srcElemPtr, scalarSize * compSize); } } } } void copyData(const BufferLayout &dstLayout, const vector &dstBlockPointers, const BufferLayout &srcLayout, const vector &srcBlockPointers) { // \note Src layout is used as reference in case of activeVarIndices happens to be incorrect in dstLayout blocks. int numBlocks = (int)srcLayout.blocks.size(); for (int srcBlockNdx = 0; srcBlockNdx < numBlocks; srcBlockNdx++) { const BlockLayoutEntry &srcBlock = srcLayout.blocks[srcBlockNdx]; const BlockDataPtr &srcBlockPtr = srcBlockPointers[srcBlockNdx]; int dstBlockNdx = dstLayout.getBlockIndex(srcBlock.name.c_str()); if (dstBlockNdx >= 0) { DE_ASSERT(de::inBounds(dstBlockNdx, 0, (int)dstBlockPointers.size())); const BlockDataPtr &dstBlockPtr = dstBlockPointers[dstBlockNdx]; for (vector::const_iterator srcVarNdxIter = srcBlock.activeVarIndices.begin(); srcVarNdxIter != srcBlock.activeVarIndices.end(); srcVarNdxIter++) { const BufferVarLayoutEntry &srcEntry = srcLayout.bufferVars[*srcVarNdxIter]; int dstVarNdx = dstLayout.getVariableIndex(srcEntry.name.c_str()); if (dstVarNdx >= 0) copyBufferVarData(dstLayout.bufferVars[dstVarNdx], dstBlockPtr, srcEntry, srcBlockPtr); } } } } void copyNonWrittenData(const BufferLayout &layout, const BufferBlock &block, int instanceNdx, const BlockDataPtr &srcBlockPtr, const BlockDataPtr &dstBlockPtr, const BufferVar &bufVar, const glu::SubTypeAccess &accessPath) { const VarType curType = accessPath.getType(); if (curType.isArrayType()) { const int arraySize = curType.getArraySize() == VarType::UNSIZED_ARRAY ? block.getLastUnsizedArraySize(instanceNdx) : curType.getArraySize(); for (int elemNdx = 0; elemNdx < arraySize; elemNdx++) copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, accessPath.element(elemNdx)); } else if (curType.isStructType()) { const int numMembers = curType.getStructPtr()->getNumMembers(); for (int memberNdx = 0; memberNdx < numMembers; memberNdx++) copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, accessPath.member(memberNdx)); } else { DE_ASSERT(curType.isBasicType()); const string apiName = getAPIName(block, bufVar, accessPath.getPath()); const int varNdx = layout.getVariableIndex(apiName); DE_ASSERT(varNdx >= 0); { const BufferVarLayoutEntry &varLayout = layout.bufferVars[varNdx]; copyBufferVarData(varLayout, dstBlockPtr, varLayout, srcBlockPtr); } } } void copyNonWrittenData(const ShaderInterface &interface, const BufferLayout &layout, const vector &srcPtrs, const vector &dstPtrs) { for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++) { const BufferBlock &block = interface.getBlock(declNdx); const bool isArray = block.isArray(); const int numInstances = isArray ? block.getArraySize() : 1; DE_ASSERT(!isArray || block.getInstanceName()); for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { const string instanceName = block.getBlockName() + (isArray ? "[" + de::toString(instanceNdx) + "]" : string("")); const int blockNdx = layout.getBlockIndex(instanceName); const BlockDataPtr &srcBlockPtr = srcPtrs[blockNdx]; const BlockDataPtr &dstBlockPtr = dstPtrs[blockNdx]; for (BufferBlock::const_iterator varIter = block.begin(); varIter != block.end(); varIter++) { const BufferVar &bufVar = *varIter; if (bufVar.getFlags() & ACCESS_WRITE) continue; copyNonWrittenData(layout, block, instanceNdx, srcBlockPtr, dstBlockPtr, bufVar, glu::SubTypeAccess(bufVar.getType())); } } } } bool compareComponents(glu::DataType scalarType, const void *ref, const void *res, int numComps) { if (scalarType == glu::TYPE_FLOAT) { const float threshold = 0.05f; // Same as used in shaders - should be fine for values being used. for (int ndx = 0; ndx < numComps; ndx++) { const float refVal = *((const float *)ref + ndx); const float resVal = *((const float *)res + ndx); if (!(deFloatAbs(resVal - refVal) <= threshold)) return false; } } else if (scalarType == glu::TYPE_BOOL) { for (int ndx = 0; ndx < numComps; ndx++) { const uint32_t refVal = *((const uint32_t *)ref + ndx); const uint32_t resVal = *((const uint32_t *)res + ndx); if ((refVal != 0) != (resVal != 0)) return false; } } else { DE_ASSERT(scalarType == glu::TYPE_INT || scalarType == glu::TYPE_UINT); for (int ndx = 0; ndx < numComps; ndx++) { const uint32_t refVal = *((const uint32_t *)ref + ndx); const uint32_t resVal = *((const uint32_t *)res + ndx); if (refVal != resVal) return false; } } return true; } bool compareBufferVarData(tcu::TestLog &log, const BufferVarLayoutEntry &refEntry, const BlockDataPtr &refBlockPtr, const BufferVarLayoutEntry &resEntry, const BlockDataPtr &resBlockPtr) { DE_ASSERT(resEntry.arraySize <= refEntry.arraySize); DE_ASSERT(resEntry.topLevelArraySize <= refEntry.topLevelArraySize); DE_ASSERT(resBlockPtr.lastUnsizedArraySize <= refBlockPtr.lastUnsizedArraySize); DE_ASSERT(resEntry.type == refEntry.type); uint8_t *const resBasePtr = (uint8_t *)resBlockPtr.ptr + resEntry.offset; const uint8_t *const refBasePtr = (const uint8_t *)refBlockPtr.ptr + refEntry.offset; const glu::DataType scalarType = glu::getDataTypeScalarType(refEntry.type); const int scalarSize = glu::getDataTypeScalarSize(resEntry.type); const bool isMatrix = glu::isDataTypeMatrix(resEntry.type); const int compSize = sizeof(uint32_t); const int maxPrints = 3; int numFailed = 0; const int resArraySize = resEntry.arraySize == 0 ? resBlockPtr.lastUnsizedArraySize : resEntry.arraySize; const int resArrayStride = resEntry.arrayStride; const int resTopLevelSize = resEntry.topLevelArraySize == 0 ? resBlockPtr.lastUnsizedArraySize : resEntry.topLevelArraySize; const int resTopLevelStride = resEntry.topLevelArrayStride; const int refArraySize = refEntry.arraySize == 0 ? refBlockPtr.lastUnsizedArraySize : refEntry.arraySize; const int refArrayStride = refEntry.arrayStride; const int refTopLevelSize = refEntry.topLevelArraySize == 0 ? refBlockPtr.lastUnsizedArraySize : refEntry.topLevelArraySize; const int refTopLevelStride = refEntry.topLevelArrayStride; DE_ASSERT(resArraySize <= refArraySize && resTopLevelSize <= refTopLevelSize); DE_UNREF(refArraySize && refTopLevelSize); for (int topElemNdx = 0; topElemNdx < resTopLevelSize; topElemNdx++) { uint8_t *const resTopPtr = resBasePtr + topElemNdx * resTopLevelStride; const uint8_t *const refTopPtr = refBasePtr + topElemNdx * refTopLevelStride; for (int elementNdx = 0; elementNdx < resArraySize; elementNdx++) { uint8_t *const resElemPtr = resTopPtr + elementNdx * resArrayStride; const uint8_t *const refElemPtr = refTopPtr + elementNdx * refArrayStride; if (isMatrix) { const int numRows = glu::getDataTypeMatrixNumRows(resEntry.type); const int numCols = glu::getDataTypeMatrixNumColumns(resEntry.type); bool isOk = true; for (int colNdx = 0; colNdx < numCols; colNdx++) { for (int rowNdx = 0; rowNdx < numRows; rowNdx++) { uint8_t *resCompPtr = resElemPtr + (resEntry.isRowMajor ? rowNdx * resEntry.matrixStride + colNdx * compSize : colNdx * resEntry.matrixStride + rowNdx * compSize); const uint8_t *refCompPtr = refElemPtr + (refEntry.isRowMajor ? rowNdx * refEntry.matrixStride + colNdx * compSize : colNdx * refEntry.matrixStride + rowNdx * compSize); DE_ASSERT((intptr_t)(refCompPtr + compSize) - (intptr_t)refBlockPtr.ptr <= (intptr_t)refBlockPtr.size); DE_ASSERT((intptr_t)(resCompPtr + compSize) - (intptr_t)resBlockPtr.ptr <= (intptr_t)resBlockPtr.size); isOk = isOk && compareComponents(scalarType, resCompPtr, refCompPtr, 1); } } if (!isOk) { numFailed += 1; if (numFailed < maxPrints) { std::ostringstream expected, got; generateImmMatrixSrc(expected, refEntry.type, refEntry.matrixStride, refEntry.isRowMajor, refElemPtr); generateImmMatrixSrc(got, resEntry.type, resEntry.matrixStride, resEntry.isRowMajor, resElemPtr); log << TestLog::Message << "ERROR: mismatch in " << refEntry.name << ", top-level ndx " << topElemNdx << ", bottom-level ndx " << elementNdx << ":\n" << " expected " << expected.str() << "\n" << " got " << got.str() << TestLog::EndMessage; } } } else { DE_ASSERT((intptr_t)(refElemPtr + scalarSize * compSize) - (intptr_t)refBlockPtr.ptr <= (intptr_t)refBlockPtr.size); DE_ASSERT((intptr_t)(resElemPtr + scalarSize * compSize) - (intptr_t)resBlockPtr.ptr <= (intptr_t)resBlockPtr.size); const bool isOk = compareComponents(scalarType, resElemPtr, refElemPtr, scalarSize); if (!isOk) { numFailed += 1; if (numFailed < maxPrints) { std::ostringstream expected, got; generateImmScalarVectorSrc(expected, refEntry.type, refElemPtr); generateImmScalarVectorSrc(got, resEntry.type, resElemPtr); log << TestLog::Message << "ERROR: mismatch in " << refEntry.name << ", top-level ndx " << topElemNdx << ", bottom-level ndx " << elementNdx << ":\n" << " expected " << expected.str() << "\n" << " got " << got.str() << TestLog::EndMessage; } } } } } if (numFailed >= maxPrints) log << TestLog::Message << "... (" << numFailed << " failures for " << refEntry.name << " in total)" << TestLog::EndMessage; return numFailed == 0; } bool compareData(tcu::TestLog &log, const BufferLayout &refLayout, const vector &refBlockPointers, const BufferLayout &resLayout, const vector &resBlockPointers) { const int numBlocks = (int)refLayout.blocks.size(); bool allOk = true; for (int refBlockNdx = 0; refBlockNdx < numBlocks; refBlockNdx++) { const BlockLayoutEntry &refBlock = refLayout.blocks[refBlockNdx]; const BlockDataPtr &refBlockPtr = refBlockPointers[refBlockNdx]; int resBlockNdx = resLayout.getBlockIndex(refBlock.name.c_str()); if (resBlockNdx >= 0) { DE_ASSERT(de::inBounds(resBlockNdx, 0, (int)resBlockPointers.size())); const BlockDataPtr &resBlockPtr = resBlockPointers[resBlockNdx]; for (vector::const_iterator refVarNdxIter = refBlock.activeVarIndices.begin(); refVarNdxIter != refBlock.activeVarIndices.end(); refVarNdxIter++) { const BufferVarLayoutEntry &refEntry = refLayout.bufferVars[*refVarNdxIter]; int resVarNdx = resLayout.getVariableIndex(refEntry.name.c_str()); if (resVarNdx >= 0) { const BufferVarLayoutEntry &resEntry = resLayout.bufferVars[resVarNdx]; allOk = compareBufferVarData(log, refEntry, refBlockPtr, resEntry, resBlockPtr) && allOk; } } } } return allOk; } string getBlockAPIName(const BufferBlock &block, int instanceNdx) { DE_ASSERT(block.isArray() || instanceNdx == 0); return block.getBlockName() + (block.isArray() ? ("[" + de::toString(instanceNdx) + "]") : string()); } // \note Some implementations don't report block members in the order they are declared. // For checking whether size has to be adjusted by some top-level array actual size, // we only need to know a) whether there is a unsized top-level array, and b) // what is stride of that array. static bool hasUnsizedArray(const BufferLayout &layout, const BlockLayoutEntry &entry) { for (vector::const_iterator varNdx = entry.activeVarIndices.begin(); varNdx != entry.activeVarIndices.end(); ++varNdx) { if (isUnsizedArray(layout.bufferVars[*varNdx])) return true; } return false; } static int getUnsizedArrayStride(const BufferLayout &layout, const BlockLayoutEntry &entry) { for (vector::const_iterator varNdx = entry.activeVarIndices.begin(); varNdx != entry.activeVarIndices.end(); ++varNdx) { const BufferVarLayoutEntry &varEntry = layout.bufferVars[*varNdx]; if (varEntry.arraySize == 0) return varEntry.arrayStride; else if (varEntry.topLevelArraySize == 0) return varEntry.topLevelArrayStride; } return 0; } vector computeBufferSizes(const ShaderInterface &interface, const BufferLayout &layout) { vector sizes(layout.blocks.size()); for (int declNdx = 0; declNdx < interface.getNumBlocks(); declNdx++) { const BufferBlock &block = interface.getBlock(declNdx); const bool isArray = block.isArray(); const int numInstances = isArray ? block.getArraySize() : 1; for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { const string apiName = getBlockAPIName(block, instanceNdx); const int blockNdx = layout.getBlockIndex(apiName); if (blockNdx >= 0) { const BlockLayoutEntry &blockLayout = layout.blocks[blockNdx]; const int baseSize = blockLayout.size; const bool isLastUnsized = hasUnsizedArray(layout, blockLayout); const int lastArraySize = isLastUnsized ? block.getLastUnsizedArraySize(instanceNdx) : 0; const int stride = isLastUnsized ? getUnsizedArrayStride(layout, blockLayout) : 0; sizes[blockNdx] = baseSize + lastArraySize * stride; } } } return sizes; } BlockDataPtr getBlockDataPtr(const BufferLayout &layout, const BlockLayoutEntry &blockLayout, void *ptr, int bufferSize) { const bool isLastUnsized = hasUnsizedArray(layout, blockLayout); const int baseSize = blockLayout.size; if (isLastUnsized) { const int lastArrayStride = getUnsizedArrayStride(layout, blockLayout); const int lastArraySize = (bufferSize - baseSize) / (lastArrayStride ? lastArrayStride : 1); DE_ASSERT(baseSize + lastArraySize * lastArrayStride == bufferSize); return BlockDataPtr(ptr, bufferSize, lastArraySize); } else return BlockDataPtr(ptr, bufferSize, 0); } struct RefDataStorage { vector data; vector pointers; }; struct Buffer { uint32_t buffer; int size; Buffer(uint32_t buffer_, int size_) : buffer(buffer_), size(size_) { } Buffer(void) : buffer(0), size(0) { } }; struct BlockLocation { int index; int offset; int size; BlockLocation(int index_, int offset_, int size_) : index(index_), offset(offset_), size(size_) { } BlockLocation(void) : index(0), offset(0), size(0) { } }; void initRefDataStorage(const ShaderInterface &interface, const BufferLayout &layout, RefDataStorage &storage) { DE_ASSERT(storage.data.empty() && storage.pointers.empty()); const vector bufferSizes = computeBufferSizes(interface, layout); int totalSize = 0; for (vector::const_iterator sizeIter = bufferSizes.begin(); sizeIter != bufferSizes.end(); ++sizeIter) totalSize += *sizeIter; storage.data.resize(totalSize); // Pointers for each block. { uint8_t *basePtr = storage.data.empty() ? DE_NULL : &storage.data[0]; int curOffset = 0; DE_ASSERT(bufferSizes.size() == layout.blocks.size()); DE_ASSERT(totalSize == 0 || basePtr); storage.pointers.resize(layout.blocks.size()); for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++) { const BlockLayoutEntry &blockLayout = layout.blocks[blockNdx]; const int bufferSize = bufferSizes[blockNdx]; storage.pointers[blockNdx] = getBlockDataPtr(layout, blockLayout, basePtr + curOffset, bufferSize); curOffset += bufferSize; } } } vector blockLocationsToPtrs(const BufferLayout &layout, const vector &blockLocations, const vector &bufPtrs) { vector blockPtrs(blockLocations.size()); DE_ASSERT(layout.blocks.size() == blockLocations.size()); for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++) { const BlockLayoutEntry &blockLayout = layout.blocks[blockNdx]; const BlockLocation &location = blockLocations[blockNdx]; blockPtrs[blockNdx] = getBlockDataPtr(layout, blockLayout, (uint8_t *)bufPtrs[location.index] + location.offset, location.size); } return blockPtrs; } vector mapBuffers(const glw::Functions &gl, const vector &buffers, uint32_t access) { vector mapPtrs(buffers.size(), DE_NULL); try { for (int ndx = 0; ndx < (int)buffers.size(); ndx++) { if (buffers[ndx].size > 0) { gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer); mapPtrs[ndx] = gl.mapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffers[ndx].size, access); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to map buffer"); TCU_CHECK(mapPtrs[ndx]); } else mapPtrs[ndx] = DE_NULL; } return mapPtrs; } catch (...) { for (int ndx = 0; ndx < (int)buffers.size(); ndx++) { if (mapPtrs[ndx]) { gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer); gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER); } } throw; } } void unmapBuffers(const glw::Functions &gl, const vector &buffers) { for (int ndx = 0; ndx < (int)buffers.size(); ndx++) { if (buffers[ndx].size > 0) { gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[ndx].buffer); gl.unmapBuffer(GL_SHADER_STORAGE_BUFFER); } } GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to unmap buffer"); } } // namespace class BufferManager { public: BufferManager(const glu::RenderContext &renderCtx); ~BufferManager(void); uint32_t allocBuffer(void); private: BufferManager(const BufferManager &other); BufferManager &operator=(const BufferManager &other); const glu::RenderContext &m_renderCtx; std::vector m_buffers; }; BufferManager::BufferManager(const glu::RenderContext &renderCtx) : m_renderCtx(renderCtx) { } BufferManager::~BufferManager(void) { if (!m_buffers.empty()) m_renderCtx.getFunctions().deleteBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]); } uint32_t BufferManager::allocBuffer(void) { uint32_t buf = 0; m_buffers.reserve(m_buffers.size() + 1); m_renderCtx.getFunctions().genBuffers(1, &buf); GLU_EXPECT_NO_ERROR(m_renderCtx.getFunctions().getError(), "Failed to allocate buffer"); m_buffers.push_back(buf); return buf; } } // namespace bb using namespace bb; // SSBOLayoutCase. SSBOLayoutCase::SSBOLayoutCase(tcu::TestContext &testCtx, glu::RenderContext &renderCtx, const char *name, const char *description, glu::GLSLVersion glslVersion, BufferMode bufferMode) : TestCase(testCtx, name, description) , m_renderCtx(renderCtx) , m_glslVersion(glslVersion) , m_bufferMode(bufferMode) { DE_ASSERT(glslVersion == glu::GLSL_VERSION_310_ES || glslVersion == glu::GLSL_VERSION_430); } SSBOLayoutCase::~SSBOLayoutCase(void) { } SSBOLayoutCase::IterateResult SSBOLayoutCase::iterate(void) { TestLog &log = m_testCtx.getLog(); const glw::Functions &gl = m_renderCtx.getFunctions(); BufferLayout refLayout; // std140 / std430 layout. BufferLayout glLayout; // Layout reported by GL. RefDataStorage initialData; // Initial data stored in buffer. RefDataStorage writeData; // Data written by compute shader. BufferManager bufferManager(m_renderCtx); vector buffers; // Buffers allocated for storage vector blockLocations; // Block locations in storage (index, offset) // Initialize result to pass. m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); computeReferenceLayout(refLayout, m_interface); initRefDataStorage(m_interface, refLayout, initialData); initRefDataStorage(m_interface, refLayout, writeData); generateValues(refLayout, initialData.pointers, deStringHash(getName()) ^ 0xad2f7214); generateValues(refLayout, writeData.pointers, deStringHash(getName()) ^ 0x25ca4e7); copyNonWrittenData(m_interface, refLayout, initialData.pointers, writeData.pointers); const glu::ShaderProgram program( m_renderCtx, glu::ProgramSources() << glu::ComputeSource(generateComputeShader( gl, m_glslVersion, m_interface, refLayout, initialData.pointers, writeData.pointers))); log << program; if (!program.isOk()) { // Compile failed. m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed"); return STOP; } // Query layout from GL. getGLBufferLayout(gl, glLayout, program.getProgram()); // Print layout to log. { tcu::ScopedLogSection section(log, "ActiveBufferBlocks", "Active Buffer Blocks"); for (int blockNdx = 0; blockNdx < (int)glLayout.blocks.size(); blockNdx++) log << TestLog::Message << blockNdx << ": " << glLayout.blocks[blockNdx] << TestLog::EndMessage; } { tcu::ScopedLogSection section(log, "ActiveBufferVars", "Active Buffer Variables"); for (int varNdx = 0; varNdx < (int)glLayout.bufferVars.size(); varNdx++) log << TestLog::Message << varNdx << ": " << glLayout.bufferVars[varNdx] << TestLog::EndMessage; } // Verify layouts. { if (!checkLayoutIndices(glLayout) || !checkLayoutBounds(glLayout) || !compareTypes(refLayout, glLayout)) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid layout"); return STOP; // It is not safe to use the given layout. } if (!compareStdBlocks(refLayout, glLayout)) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid std140 or std430 layout"); if (!compareSharedBlocks(refLayout, glLayout)) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid shared layout"); if (!checkIndexQueries(program.getProgram(), glLayout)) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsintent block index query results"); } // Allocate GL buffers & compute placement. { const int numBlocks = (int)glLayout.blocks.size(); const vector bufferSizes = computeBufferSizes(m_interface, glLayout); DE_ASSERT(bufferSizes.size() == glLayout.blocks.size()); blockLocations.resize(numBlocks); if (m_bufferMode == BUFFERMODE_PER_BLOCK) { buffers.resize(numBlocks); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const int bufferSize = bufferSizes[blockNdx]; buffers[blockNdx].size = bufferSize; blockLocations[blockNdx] = BlockLocation(blockNdx, 0, bufferSize); } } else { DE_ASSERT(m_bufferMode == BUFFERMODE_SINGLE); int bindingAlignment = 0; int totalSize = 0; gl.getIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &bindingAlignment); { int curOffset = 0; DE_ASSERT(bufferSizes.size() == glLayout.blocks.size()); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const int bufferSize = bufferSizes[blockNdx]; if (bindingAlignment > 0) curOffset = deRoundUp32(curOffset, bindingAlignment); blockLocations[blockNdx] = BlockLocation(0, curOffset, bufferSize); curOffset += bufferSize; } totalSize = curOffset; } buffers.resize(1); buffers[0].size = totalSize; } for (int bufNdx = 0; bufNdx < (int)buffers.size(); bufNdx++) { const int bufferSize = buffers[bufNdx].size; const uint32_t buffer = bufferManager.allocBuffer(); gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); gl.bufferData(GL_SHADER_STORAGE_BUFFER, bufferSize, DE_NULL, GL_STATIC_DRAW); GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to allocate buffer"); buffers[bufNdx].buffer = buffer; } } { const vector mapPtrs = mapBuffers(gl, buffers, GL_MAP_WRITE_BIT); const vector mappedBlockPtrs = blockLocationsToPtrs(glLayout, blockLocations, mapPtrs); copyData(glLayout, mappedBlockPtrs, refLayout, initialData.pointers); unmapBuffers(gl, buffers); } { int bindingPoint = 0; for (int blockDeclNdx = 0; blockDeclNdx < m_interface.getNumBlocks(); blockDeclNdx++) { const BufferBlock &block = m_interface.getBlock(blockDeclNdx); const int numInst = block.isArray() ? block.getArraySize() : 1; for (int instNdx = 0; instNdx < numInst; instNdx++) { const string instName = getBlockAPIName(block, instNdx); const int layoutNdx = findBlockIndex(glLayout, instName); if (layoutNdx >= 0) { const BlockLocation &blockLoc = blockLocations[layoutNdx]; if (blockLoc.size > 0) gl.bindBufferRange(GL_SHADER_STORAGE_BUFFER, bindingPoint, buffers[blockLoc.index].buffer, blockLoc.offset, blockLoc.size); } bindingPoint += 1; } } } GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to bind buffers"); { const bool execOk = execute(program.getProgram()); if (execOk) { const vector mapPtrs = mapBuffers(gl, buffers, GL_MAP_READ_BIT); const vector mappedBlockPtrs = blockLocationsToPtrs(glLayout, blockLocations, mapPtrs); const bool compareOk = compareData(m_testCtx.getLog(), refLayout, writeData.pointers, glLayout, mappedBlockPtrs); unmapBuffers(gl, buffers); if (!compareOk) m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed"); } else m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader execution failed"); } return STOP; } bool SSBOLayoutCase::compareStdBlocks(const BufferLayout &refLayout, const BufferLayout &cmpLayout) const { TestLog &log = m_testCtx.getLog(); bool isOk = true; int numBlocks = m_interface.getNumBlocks(); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BufferBlock &block = m_interface.getBlock(blockNdx); bool isArray = block.isArray(); std::string instanceName = string(block.getBlockName()) + (isArray ? "[0]" : ""); int refBlockNdx = refLayout.getBlockIndex(instanceName.c_str()); int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.c_str()); if ((block.getFlags() & (LAYOUT_STD140 | LAYOUT_STD430)) == 0) continue; // Not std* layout. DE_ASSERT(refBlockNdx >= 0); if (cmpBlockNdx < 0) { // Not found. log << TestLog::Message << "Error: Buffer block '" << instanceName << "' not found" << TestLog::EndMessage; isOk = false; continue; } const BlockLayoutEntry &refBlockLayout = refLayout.blocks[refBlockNdx]; const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx]; // \todo [2012-01-24 pyry] Verify that activeVarIndices is correct. // \todo [2012-01-24 pyry] Verify all instances. if (refBlockLayout.activeVarIndices.size() != cmpBlockLayout.activeVarIndices.size()) { log << TestLog::Message << "Error: Number of active variables differ in block '" << instanceName << "' (expected " << refBlockLayout.activeVarIndices.size() << ", got " << cmpBlockLayout.activeVarIndices.size() << ")" << TestLog::EndMessage; isOk = false; } for (vector::const_iterator ndxIter = refBlockLayout.activeVarIndices.begin(); ndxIter != refBlockLayout.activeVarIndices.end(); ndxIter++) { const BufferVarLayoutEntry &refEntry = refLayout.bufferVars[*ndxIter]; int cmpEntryNdx = cmpLayout.getVariableIndex(refEntry.name.c_str()); if (cmpEntryNdx < 0) { log << TestLog::Message << "Error: Buffer variable '" << refEntry.name << "' not found" << TestLog::EndMessage; isOk = false; continue; } const BufferVarLayoutEntry &cmpEntry = cmpLayout.bufferVars[cmpEntryNdx]; if (refEntry.type != cmpEntry.type || refEntry.arraySize != cmpEntry.arraySize || refEntry.offset != cmpEntry.offset || refEntry.arrayStride != cmpEntry.arrayStride || refEntry.matrixStride != cmpEntry.matrixStride || refEntry.topLevelArraySize != cmpEntry.topLevelArraySize || refEntry.topLevelArrayStride != cmpEntry.topLevelArrayStride || refEntry.isRowMajor != cmpEntry.isRowMajor) { log << TestLog::Message << "Error: Layout mismatch in '" << refEntry.name << "':\n" << " expected: " << refEntry << "\n" << " got: " << cmpEntry << TestLog::EndMessage; isOk = false; } } } return isOk; } bool SSBOLayoutCase::compareSharedBlocks(const BufferLayout &refLayout, const BufferLayout &cmpLayout) const { TestLog &log = m_testCtx.getLog(); bool isOk = true; int numBlocks = m_interface.getNumBlocks(); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BufferBlock &block = m_interface.getBlock(blockNdx); bool isArray = block.isArray(); std::string instanceName = string(block.getBlockName()) + (isArray ? "[0]" : ""); int refBlockNdx = refLayout.getBlockIndex(instanceName.c_str()); int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.c_str()); if ((block.getFlags() & LAYOUT_SHARED) == 0) continue; // Not shared layout. DE_ASSERT(refBlockNdx >= 0); if (cmpBlockNdx < 0) { // Not found, should it? log << TestLog::Message << "Error: Buffer block '" << instanceName << "' not found" << TestLog::EndMessage; isOk = false; continue; } const BlockLayoutEntry &refBlockLayout = refLayout.blocks[refBlockNdx]; const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx]; if (refBlockLayout.activeVarIndices.size() != cmpBlockLayout.activeVarIndices.size()) { log << TestLog::Message << "Error: Number of active variables differ in block '" << instanceName << "' (expected " << refBlockLayout.activeVarIndices.size() << ", got " << cmpBlockLayout.activeVarIndices.size() << ")" << TestLog::EndMessage; isOk = false; } for (vector::const_iterator ndxIter = refBlockLayout.activeVarIndices.begin(); ndxIter != refBlockLayout.activeVarIndices.end(); ndxIter++) { const BufferVarLayoutEntry &refEntry = refLayout.bufferVars[*ndxIter]; int cmpEntryNdx = cmpLayout.getVariableIndex(refEntry.name.c_str()); if (cmpEntryNdx < 0) { log << TestLog::Message << "Error: Buffer variable '" << refEntry.name << "' not found" << TestLog::EndMessage; isOk = false; continue; } const BufferVarLayoutEntry &cmpEntry = cmpLayout.bufferVars[cmpEntryNdx]; if (refEntry.type != cmpEntry.type || refEntry.arraySize != cmpEntry.arraySize || refEntry.topLevelArraySize != cmpEntry.topLevelArraySize || refEntry.isRowMajor != cmpEntry.isRowMajor) { log << TestLog::Message << "Error: Type / array size mismatch in '" << refEntry.name << "':\n" << " expected: " << refEntry << "\n" << " got: " << cmpEntry << TestLog::EndMessage; isOk = false; } } } return isOk; } bool SSBOLayoutCase::compareTypes(const BufferLayout &refLayout, const BufferLayout &cmpLayout) const { TestLog &log = m_testCtx.getLog(); bool isOk = true; int numBlocks = m_interface.getNumBlocks(); for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BufferBlock &block = m_interface.getBlock(blockNdx); bool isArray = block.isArray(); int numInstances = isArray ? block.getArraySize() : 1; for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) { std::ostringstream instanceName; instanceName << block.getBlockName(); if (isArray) instanceName << "[" << instanceNdx << "]"; int cmpBlockNdx = cmpLayout.getBlockIndex(instanceName.str().c_str()); if (cmpBlockNdx < 0) continue; const BlockLayoutEntry &cmpBlockLayout = cmpLayout.blocks[cmpBlockNdx]; for (vector::const_iterator ndxIter = cmpBlockLayout.activeVarIndices.begin(); ndxIter != cmpBlockLayout.activeVarIndices.end(); ndxIter++) { const BufferVarLayoutEntry &cmpEntry = cmpLayout.bufferVars[*ndxIter]; int refEntryNdx = refLayout.getVariableIndex(cmpEntry.name.c_str()); if (refEntryNdx < 0) { log << TestLog::Message << "Error: Buffer variable '" << cmpEntry.name << "' not found in reference layout" << TestLog::EndMessage; isOk = false; continue; } const BufferVarLayoutEntry &refEntry = refLayout.bufferVars[refEntryNdx]; if (refEntry.type != cmpEntry.type) { log << TestLog::Message << "Error: Buffer variable type mismatch in '" << refEntry.name << "':\n" << " expected: " << glu::getDataTypeName(refEntry.type) << "\n" << " got: " << glu::getDataTypeName(cmpEntry.type) << TestLog::EndMessage; isOk = false; } if (refEntry.arraySize < cmpEntry.arraySize) { log << TestLog::Message << "Error: Invalid array size in '" << refEntry.name << "': expected <= " << refEntry.arraySize << TestLog::EndMessage; isOk = false; } if (refEntry.topLevelArraySize < cmpEntry.topLevelArraySize) { log << TestLog::Message << "Error: Invalid top-level array size in '" << refEntry.name << "': expected <= " << refEntry.topLevelArraySize << TestLog::EndMessage; isOk = false; } } } } return isOk; } bool SSBOLayoutCase::checkLayoutIndices(const BufferLayout &layout) const { TestLog &log = m_testCtx.getLog(); int numVars = (int)layout.bufferVars.size(); int numBlocks = (int)layout.blocks.size(); bool isOk = true; // Check variable block indices. for (int varNdx = 0; varNdx < numVars; varNdx++) { const BufferVarLayoutEntry &bufVar = layout.bufferVars[varNdx]; if (bufVar.blockNdx < 0 || !deInBounds32(bufVar.blockNdx, 0, numBlocks)) { log << TestLog::Message << "Error: Invalid block index in buffer variable '" << bufVar.name << "'" << TestLog::EndMessage; isOk = false; } } // Check active variables. for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++) { const BlockLayoutEntry &block = layout.blocks[blockNdx]; for (vector::const_iterator varNdxIter = block.activeVarIndices.begin(); varNdxIter != block.activeVarIndices.end(); varNdxIter++) { if (!deInBounds32(*varNdxIter, 0, numVars)) { log << TestLog::Message << "Error: Invalid active variable index " << *varNdxIter << " in block '" << block.name << "'" << TestLog::EndMessage; isOk = false; } } } return isOk; } bool SSBOLayoutCase::checkLayoutBounds(const BufferLayout &layout) const { TestLog &log = m_testCtx.getLog(); const int numVars = (int)layout.bufferVars.size(); bool isOk = true; for (int varNdx = 0; varNdx < numVars; varNdx++) { const BufferVarLayoutEntry &var = layout.bufferVars[varNdx]; if (var.blockNdx < 0 || isUnsizedArray(var)) continue; const BlockLayoutEntry &block = layout.blocks[var.blockNdx]; const bool isMatrix = glu::isDataTypeMatrix(var.type); const int numVecs = isMatrix ? (var.isRowMajor ? glu::getDataTypeMatrixNumRows(var.type) : glu::getDataTypeMatrixNumColumns(var.type)) : 1; const int numComps = isMatrix ? (var.isRowMajor ? glu::getDataTypeMatrixNumColumns(var.type) : glu::getDataTypeMatrixNumRows(var.type)) : glu::getDataTypeScalarSize(var.type); const int numElements = var.arraySize; const int topLevelSize = var.topLevelArraySize; const int arrayStride = var.arrayStride; const int topLevelStride = var.topLevelArrayStride; const int compSize = sizeof(uint32_t); const int vecSize = numComps * compSize; int minOffset = 0; int maxOffset = 0; // For negative strides. minOffset = de::min(minOffset, (numVecs - 1) * var.matrixStride); minOffset = de::min(minOffset, (numElements - 1) * arrayStride); minOffset = de::min(minOffset, (topLevelSize - 1) * topLevelStride + (numElements - 1) * arrayStride + (numVecs - 1) * var.matrixStride); maxOffset = de::max(maxOffset, vecSize); maxOffset = de::max(maxOffset, (numVecs - 1) * var.matrixStride + vecSize); maxOffset = de::max(maxOffset, (numElements - 1) * arrayStride + vecSize); maxOffset = de::max(maxOffset, (topLevelSize - 1) * topLevelStride + (numElements - 1) * arrayStride + vecSize); maxOffset = de::max(maxOffset, (topLevelSize - 1) * topLevelStride + (numElements - 1) * arrayStride + (numVecs - 1) * var.matrixStride + vecSize); if (var.offset + minOffset < 0 || var.offset + maxOffset > block.size) { log << TestLog::Message << "Error: Variable '" << var.name << "' out of block bounds" << TestLog::EndMessage; isOk = false; } } return isOk; } bool SSBOLayoutCase::checkIndexQueries(uint32_t program, const BufferLayout &layout) const { tcu::TestLog &log = m_testCtx.getLog(); const glw::Functions &gl = m_renderCtx.getFunctions(); bool allOk = true; // \note Spec mandates that buffer blocks are assigned consecutive locations from 0. // BlockLayoutEntries are stored in that order in UniformLayout. for (int blockNdx = 0; blockNdx < (int)layout.blocks.size(); blockNdx++) { const BlockLayoutEntry &block = layout.blocks[blockNdx]; const int queriedNdx = gl.getProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, block.name.c_str()); if (queriedNdx != blockNdx) { log << TestLog::Message << "ERROR: glGetProgramResourceIndex(" << block.name << ") returned " << queriedNdx << ", expected " << blockNdx << "!" << TestLog::EndMessage; allOk = false; } GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformBlockIndex()"); } return allOk; } bool SSBOLayoutCase::execute(uint32_t program) { const glw::Functions &gl = m_renderCtx.getFunctions(); const uint32_t numPassedLoc = gl.getProgramResourceIndex(program, GL_UNIFORM, "ac_numPassed"); const glu::InterfaceVariableInfo acVarInfo = numPassedLoc != GL_INVALID_INDEX ? glu::getProgramInterfaceVariableInfo(gl, program, GL_UNIFORM, numPassedLoc) : glu::InterfaceVariableInfo(); const glu::InterfaceBlockInfo acBufferInfo = acVarInfo.atomicCounterBufferIndex != GL_INVALID_INDEX ? glu::getProgramInterfaceBlockInfo(gl, program, GL_ATOMIC_COUNTER_BUFFER, acVarInfo.atomicCounterBufferIndex) : glu::InterfaceBlockInfo(); const glu::Buffer acBuffer(m_renderCtx); bool isOk = true; if (numPassedLoc == GL_INVALID_INDEX) throw tcu::TestError("No location for ac_numPassed found"); if (acBufferInfo.index == GL_INVALID_INDEX) throw tcu::TestError("ac_numPassed buffer index is GL_INVALID_INDEX"); if (acBufferInfo.dataSize == 0) throw tcu::TestError("ac_numPassed buffer size = 0"); // Initialize atomic counter buffer. { vector emptyData(acBufferInfo.dataSize, 0); gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *acBuffer); gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, (glw::GLsizeiptr)emptyData.size(), &emptyData[0], GL_STATIC_READ); gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, acBufferInfo.index, *acBuffer); GLU_EXPECT_NO_ERROR(gl.getError(), "Setting up buffer for ac_numPassed failed"); } gl.useProgram(program); gl.dispatchCompute(1, 1, 1); GLU_EXPECT_NO_ERROR(gl.getError(), "glDispatchCompute() failed"); // Read back ac_numPassed data. { const void *mapPtr = gl.mapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, acBufferInfo.dataSize, GL_MAP_READ_BIT); const int refCount = 1; int resCount = 0; GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER) failed"); TCU_CHECK(mapPtr); resCount = *(const int *)((const uint8_t *)mapPtr + acVarInfo.offset); gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER); GLU_EXPECT_NO_ERROR(gl.getError(), "glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER) failed"); if (refCount != resCount) { m_testCtx.getLog() << TestLog::Message << "ERROR: ac_numPassed = " << resCount << ", expected " << refCount << TestLog::EndMessage; isOk = false; } } GLU_EXPECT_NO_ERROR(gl.getError(), "Shader execution failed"); return isOk; } } // namespace gles31 } // namespace deqp