| /*------------------------------------------------------------------------- |
| * 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 Opaque type (sampler, buffer, atomic counter, ...) indexing tests. |
| * |
| * \todo [2014-03-05 pyry] Extend with following: |
| * + sampler: different filtering modes, multiple sizes, incomplete textures |
| * + SSBO: write, atomic op, unsized array .length() |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es31fOpaqueTypeIndexingTests.hpp" |
| #include "tcuTexture.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuFormatUtil.hpp" |
| #include "tcuVectorUtil.hpp" |
| #include "gluShaderUtil.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluObjectWrapper.hpp" |
| #include "gluTextureUtil.hpp" |
| #include "gluRenderContext.hpp" |
| #include "gluProgramInterfaceQuery.hpp" |
| #include "gluContextInfo.hpp" |
| #include "glsShaderExecUtil.hpp" |
| #include "glwFunctions.hpp" |
| #include "glwEnums.hpp" |
| #include "deUniquePtr.hpp" |
| #include "deStringUtil.hpp" |
| #include "deRandom.hpp" |
| |
| #include <sstream> |
| |
| namespace deqp |
| { |
| namespace gles31 |
| { |
| namespace Functional |
| { |
| |
| namespace |
| { |
| |
| using namespace gls::ShaderExecUtil; |
| using namespace glu; |
| using std::string; |
| using std::vector; |
| using tcu::TestLog; |
| using tcu::TextureFormat; |
| |
| typedef de::UniquePtr<ShaderExecutor> ShaderExecutorPtr; |
| |
| enum IndexExprType |
| { |
| INDEX_EXPR_TYPE_CONST_LITERAL = 0, |
| INDEX_EXPR_TYPE_CONST_EXPRESSION, |
| INDEX_EXPR_TYPE_UNIFORM, |
| INDEX_EXPR_TYPE_DYNAMIC_UNIFORM, |
| |
| INDEX_EXPR_TYPE_LAST |
| }; |
| |
| enum TextureType |
| { |
| TEXTURE_TYPE_1D = 0, |
| TEXTURE_TYPE_2D, |
| TEXTURE_TYPE_CUBE, |
| TEXTURE_TYPE_2D_ARRAY, |
| TEXTURE_TYPE_3D, |
| TEXTURE_TYPE_CUBE_ARRAY, |
| |
| TEXTURE_TYPE_LAST |
| }; |
| |
| static void declareUniformIndexVars(std::ostream &str, const char *varPrefix, int numVars) |
| { |
| for (int varNdx = 0; varNdx < numVars; varNdx++) |
| str << "uniform highp int " << varPrefix << varNdx << ";\n"; |
| } |
| |
| static void uploadUniformIndices(const glw::Functions &gl, uint32_t program, const char *varPrefix, int numIndices, |
| const int *indices) |
| { |
| for (int varNdx = 0; varNdx < numIndices; varNdx++) |
| { |
| const string varName = varPrefix + de::toString(varNdx); |
| const int loc = gl.getUniformLocation(program, varName.c_str()); |
| TCU_CHECK_MSG(loc >= 0, ("No location assigned for uniform '" + varName + "'").c_str()); |
| |
| gl.uniform1i(loc, indices[varNdx]); |
| } |
| } |
| |
| template <typename T> |
| static T maxElement(const std::vector<T> &elements) |
| { |
| T maxElem = elements[0]; |
| |
| for (size_t ndx = 1; ndx < elements.size(); ndx++) |
| maxElem = de::max(maxElem, elements[ndx]); |
| |
| return maxElem; |
| } |
| |
| static TextureType getTextureType(glu::DataType samplerType) |
| { |
| switch (samplerType) |
| { |
| case glu::TYPE_SAMPLER_1D: |
| case glu::TYPE_INT_SAMPLER_1D: |
| case glu::TYPE_UINT_SAMPLER_1D: |
| case glu::TYPE_SAMPLER_1D_SHADOW: |
| return TEXTURE_TYPE_1D; |
| |
| case glu::TYPE_SAMPLER_2D: |
| case glu::TYPE_INT_SAMPLER_2D: |
| case glu::TYPE_UINT_SAMPLER_2D: |
| case glu::TYPE_SAMPLER_2D_SHADOW: |
| return TEXTURE_TYPE_2D; |
| |
| case glu::TYPE_SAMPLER_CUBE: |
| case glu::TYPE_INT_SAMPLER_CUBE: |
| case glu::TYPE_UINT_SAMPLER_CUBE: |
| case glu::TYPE_SAMPLER_CUBE_SHADOW: |
| return TEXTURE_TYPE_CUBE; |
| |
| case glu::TYPE_SAMPLER_2D_ARRAY: |
| case glu::TYPE_INT_SAMPLER_2D_ARRAY: |
| case glu::TYPE_UINT_SAMPLER_2D_ARRAY: |
| case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW: |
| return TEXTURE_TYPE_2D_ARRAY; |
| |
| case glu::TYPE_SAMPLER_3D: |
| case glu::TYPE_INT_SAMPLER_3D: |
| case glu::TYPE_UINT_SAMPLER_3D: |
| return TEXTURE_TYPE_3D; |
| |
| case glu::TYPE_SAMPLER_CUBE_ARRAY: |
| case glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW: |
| case glu::TYPE_INT_SAMPLER_CUBE_ARRAY: |
| case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY: |
| return TEXTURE_TYPE_CUBE_ARRAY; |
| |
| default: |
| TCU_THROW(InternalError, "Invalid sampler type"); |
| } |
| } |
| |
| static bool isShadowSampler(glu::DataType samplerType) |
| { |
| return samplerType == glu::TYPE_SAMPLER_1D_SHADOW || samplerType == glu::TYPE_SAMPLER_2D_SHADOW || |
| samplerType == glu::TYPE_SAMPLER_2D_ARRAY_SHADOW || samplerType == glu::TYPE_SAMPLER_CUBE_SHADOW || |
| samplerType == glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW; |
| } |
| |
| static glu::DataType getSamplerOutputType(glu::DataType samplerType) |
| { |
| switch (samplerType) |
| { |
| case glu::TYPE_SAMPLER_1D: |
| case glu::TYPE_SAMPLER_2D: |
| case glu::TYPE_SAMPLER_CUBE: |
| case glu::TYPE_SAMPLER_2D_ARRAY: |
| case glu::TYPE_SAMPLER_3D: |
| case glu::TYPE_SAMPLER_CUBE_ARRAY: |
| return glu::TYPE_FLOAT_VEC4; |
| |
| case glu::TYPE_SAMPLER_1D_SHADOW: |
| case glu::TYPE_SAMPLER_2D_SHADOW: |
| case glu::TYPE_SAMPLER_CUBE_SHADOW: |
| case glu::TYPE_SAMPLER_2D_ARRAY_SHADOW: |
| case glu::TYPE_SAMPLER_CUBE_ARRAY_SHADOW: |
| return glu::TYPE_FLOAT; |
| |
| case glu::TYPE_INT_SAMPLER_1D: |
| case glu::TYPE_INT_SAMPLER_2D: |
| case glu::TYPE_INT_SAMPLER_CUBE: |
| case glu::TYPE_INT_SAMPLER_2D_ARRAY: |
| case glu::TYPE_INT_SAMPLER_3D: |
| case glu::TYPE_INT_SAMPLER_CUBE_ARRAY: |
| return glu::TYPE_INT_VEC4; |
| |
| case glu::TYPE_UINT_SAMPLER_1D: |
| case glu::TYPE_UINT_SAMPLER_2D: |
| case glu::TYPE_UINT_SAMPLER_CUBE: |
| case glu::TYPE_UINT_SAMPLER_2D_ARRAY: |
| case glu::TYPE_UINT_SAMPLER_3D: |
| case glu::TYPE_UINT_SAMPLER_CUBE_ARRAY: |
| return glu::TYPE_UINT_VEC4; |
| |
| default: |
| TCU_THROW(InternalError, "Invalid sampler type"); |
| } |
| } |
| |
| static tcu::TextureFormat getSamplerTextureFormat(glu::DataType samplerType) |
| { |
| const glu::DataType outType = getSamplerOutputType(samplerType); |
| const glu::DataType outScalarType = glu::getDataTypeScalarType(outType); |
| |
| switch (outScalarType) |
| { |
| case glu::TYPE_FLOAT: |
| if (isShadowSampler(samplerType)) |
| return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16); |
| else |
| return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8); |
| |
| case glu::TYPE_INT: |
| return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT8); |
| case glu::TYPE_UINT: |
| return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT8); |
| |
| default: |
| TCU_THROW(InternalError, "Invalid sampler type"); |
| } |
| } |
| |
| static glu::DataType getSamplerCoordType(glu::DataType samplerType) |
| { |
| const TextureType texType = getTextureType(samplerType); |
| int numCoords = 0; |
| |
| switch (texType) |
| { |
| case TEXTURE_TYPE_1D: |
| numCoords = 1; |
| break; |
| case TEXTURE_TYPE_2D: |
| numCoords = 2; |
| break; |
| case TEXTURE_TYPE_2D_ARRAY: |
| numCoords = 3; |
| break; |
| case TEXTURE_TYPE_CUBE: |
| numCoords = 3; |
| break; |
| case TEXTURE_TYPE_3D: |
| numCoords = 3; |
| break; |
| case TEXTURE_TYPE_CUBE_ARRAY: |
| numCoords = 4; |
| break; |
| default: |
| TCU_THROW(InternalError, "Invalid texture type"); |
| } |
| |
| if (isShadowSampler(samplerType) && samplerType != TYPE_SAMPLER_CUBE_ARRAY_SHADOW) |
| numCoords += 1; |
| |
| DE_ASSERT(de::inRange(numCoords, 1, 4)); |
| |
| return numCoords == 1 ? glu::TYPE_FLOAT : glu::getDataTypeFloatVec(numCoords); |
| } |
| |
| static uint32_t getGLTextureTarget(TextureType texType) |
| { |
| switch (texType) |
| { |
| case TEXTURE_TYPE_1D: |
| return GL_TEXTURE_1D; |
| case TEXTURE_TYPE_2D: |
| return GL_TEXTURE_2D; |
| case TEXTURE_TYPE_2D_ARRAY: |
| return GL_TEXTURE_2D_ARRAY; |
| case TEXTURE_TYPE_CUBE: |
| return GL_TEXTURE_CUBE_MAP; |
| case TEXTURE_TYPE_3D: |
| return GL_TEXTURE_3D; |
| case TEXTURE_TYPE_CUBE_ARRAY: |
| return GL_TEXTURE_CUBE_MAP_ARRAY; |
| default: |
| TCU_THROW(InternalError, "Invalid texture type"); |
| } |
| } |
| |
| static void setupTexture(const glw::Functions &gl, uint32_t texture, glu::DataType samplerType, |
| tcu::TextureFormat texFormat, const void *color) |
| { |
| const TextureType texType = getTextureType(samplerType); |
| const uint32_t texTarget = getGLTextureTarget(texType); |
| const uint32_t intFormat = glu::getInternalFormat(texFormat); |
| const glu::TransferFormat transferFmt = glu::getTransferFormat(texFormat); |
| |
| // \todo [2014-03-04 pyry] Use larger than 1x1 textures? |
| |
| gl.bindTexture(texTarget, texture); |
| |
| switch (texType) |
| { |
| case TEXTURE_TYPE_1D: |
| gl.texStorage1D(texTarget, 1, intFormat, 1); |
| gl.texSubImage1D(texTarget, 0, 0, 1, transferFmt.format, transferFmt.dataType, color); |
| break; |
| |
| case TEXTURE_TYPE_2D: |
| gl.texStorage2D(texTarget, 1, intFormat, 1, 1); |
| gl.texSubImage2D(texTarget, 0, 0, 0, 1, 1, transferFmt.format, transferFmt.dataType, color); |
| break; |
| |
| case TEXTURE_TYPE_2D_ARRAY: |
| case TEXTURE_TYPE_3D: |
| gl.texStorage3D(texTarget, 1, intFormat, 1, 1, 1); |
| gl.texSubImage3D(texTarget, 0, 0, 0, 0, 1, 1, 1, transferFmt.format, transferFmt.dataType, color); |
| break; |
| |
| case TEXTURE_TYPE_CUBE_ARRAY: |
| gl.texStorage3D(texTarget, 1, intFormat, 1, 1, 6); |
| for (int zoffset = 0; zoffset < 6; ++zoffset) |
| for (int face = 0; face < tcu::CUBEFACE_LAST; face++) |
| gl.texSubImage3D(texTarget, 0, 0, 0, zoffset, 1, 1, 1, transferFmt.format, transferFmt.dataType, color); |
| break; |
| |
| case TEXTURE_TYPE_CUBE: |
| gl.texStorage2D(texTarget, 1, intFormat, 1, 1); |
| for (int face = 0; face < tcu::CUBEFACE_LAST; face++) |
| gl.texSubImage2D(glu::getGLCubeFace((tcu::CubeFace)face), 0, 0, 0, 1, 1, transferFmt.format, |
| transferFmt.dataType, color); |
| break; |
| |
| default: |
| TCU_THROW(InternalError, "Invalid texture type"); |
| } |
| |
| gl.texParameteri(texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| gl.texParameteri(texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| if (isShadowSampler(samplerType)) |
| gl.texParameteri(texTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Texture setup failed"); |
| } |
| |
| class SamplerIndexingCase : public TestCase |
| { |
| public: |
| SamplerIndexingCase(Context &context, const char *name, const char *description, glu::ShaderType shaderType, |
| glu::DataType samplerType, IndexExprType indexExprType); |
| ~SamplerIndexingCase(void); |
| |
| void init(void); |
| IterateResult iterate(void); |
| |
| private: |
| SamplerIndexingCase(const SamplerIndexingCase &); |
| SamplerIndexingCase &operator=(const SamplerIndexingCase &); |
| |
| void getShaderSpec(ShaderSpec *spec, int numSamplers, int numLookups, const int *lookupIndices, |
| const RenderContext &renderContext) const; |
| |
| const glu::ShaderType m_shaderType; |
| const glu::DataType m_samplerType; |
| const IndexExprType m_indexExprType; |
| }; |
| |
| SamplerIndexingCase::SamplerIndexingCase(Context &context, const char *name, const char *description, |
| glu::ShaderType shaderType, glu::DataType samplerType, |
| IndexExprType indexExprType) |
| : TestCase(context, name, description) |
| , m_shaderType(shaderType) |
| , m_samplerType(samplerType) |
| , m_indexExprType(indexExprType) |
| { |
| } |
| |
| SamplerIndexingCase::~SamplerIndexingCase(void) |
| { |
| } |
| |
| void SamplerIndexingCase::init(void) |
| { |
| const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5), |
| "GL_ARB_ES3_2_compatibility"); |
| |
| if (!supportsES32) |
| { |
| if (m_shaderType == SHADERTYPE_GEOMETRY) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"), |
| "GL_EXT_geometry_shader extension is required to run geometry shader tests."); |
| |
| if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"), |
| "GL_EXT_tessellation_shader extension is required to run tessellation shader tests."); |
| |
| if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"), |
| "GL_EXT_gpu_shader5 extension is required for dynamic indexing of sampler arrays."); |
| |
| if (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW || |
| m_samplerType == TYPE_INT_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_UINT_SAMPLER_CUBE_ARRAY) |
| { |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array"), |
| "GL_EXT_texture_cube_map_array extension is required for cube map arrays."); |
| } |
| } |
| } |
| |
| void SamplerIndexingCase::getShaderSpec(ShaderSpec *spec, int numSamplers, int numLookups, const int *lookupIndices, |
| const RenderContext &renderContext) const |
| { |
| const char *samplersName = "sampler"; |
| const char *coordsName = "coords"; |
| const char *indicesPrefix = "index"; |
| const char *resultPrefix = "result"; |
| const DataType coordType = getSamplerCoordType(m_samplerType); |
| const DataType outType = getSamplerOutputType(m_samplerType); |
| const bool supportsES32 = |
| contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility"); |
| std::ostringstream global; |
| std::ostringstream code; |
| |
| spec->inputs.push_back(Symbol(coordsName, VarType(coordType, PRECISION_HIGHP))); |
| |
| if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && |
| m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "#extension GL_EXT_gpu_shader5 : require\n"; |
| |
| if (!supportsES32 && |
| (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW || |
| m_samplerType == TYPE_INT_SAMPLER_CUBE_ARRAY || m_samplerType == TYPE_UINT_SAMPLER_CUBE_ARRAY)) |
| { |
| global << "#extension GL_EXT_texture_cube_map_array: require\n"; |
| } |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "const highp int indexBase = 1;\n"; |
| |
| global << "uniform highp " << getDataTypeName(m_samplerType) << " " << samplersName << "[" << numSamplers << "];\n"; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| const string varName = indicesPrefix + de::toString(lookupNdx); |
| spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP))); |
| } |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| declareUniformIndexVars(global, indicesPrefix, numLookups); |
| |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| const string varName = resultPrefix + de::toString(lookupNdx); |
| spec->outputs.push_back(Symbol(varName, VarType(outType, PRECISION_HIGHP))); |
| } |
| |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| code << resultPrefix << "" << lookupNdx << " = texture(" << samplersName << "["; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL) |
| code << lookupIndices[lookupNdx]; |
| else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| code << "indexBase + " << (lookupIndices[lookupNdx] - 1); |
| else |
| code << indicesPrefix << lookupNdx; |
| |
| code << "], " << coordsName << (m_samplerType == TYPE_SAMPLER_CUBE_ARRAY_SHADOW ? ", 0.0" : "") << ");\n"; |
| } |
| |
| spec->version = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES; |
| spec->globalDeclarations = global.str(); |
| spec->source = code.str(); |
| } |
| |
| static void fillTextureData(const tcu::PixelBufferAccess &access, de::Random &rnd) |
| { |
| DE_ASSERT(access.getHeight() == 1 && access.getDepth() == 1); |
| |
| if (access.getFormat().order == TextureFormat::D) |
| { |
| // \note Texture uses odd values, lookup even values to avoid precision issues. |
| const float values[] = {0.1f, 0.3f, 0.5f, 0.7f, 0.9f}; |
| |
| for (int ndx = 0; ndx < access.getWidth(); ndx++) |
| access.setPixDepth(rnd.choose<float>(DE_ARRAY_BEGIN(values), DE_ARRAY_END(values)), ndx, 0); |
| } |
| else |
| { |
| TCU_CHECK_INTERNAL(access.getFormat().order == TextureFormat::RGBA && access.getFormat().getPixelSize() == 4); |
| |
| for (int ndx = 0; ndx < access.getWidth(); ndx++) |
| *((uint32_t *)access.getDataPtr() + ndx) = rnd.getUint32(); |
| } |
| } |
| |
| SamplerIndexingCase::IterateResult SamplerIndexingCase::iterate(void) |
| { |
| const int numInvocations = 64; |
| const int numSamplers = 8; |
| const int numLookups = 4; |
| const DataType coordType = getSamplerCoordType(m_samplerType); |
| const DataType outputType = getSamplerOutputType(m_samplerType); |
| const TextureFormat texFormat = getSamplerTextureFormat(m_samplerType); |
| const int outLookupStride = numInvocations * getDataTypeScalarSize(outputType); |
| vector<int> lookupIndices(numLookups); |
| vector<float> coords; |
| vector<uint32_t> outData; |
| vector<uint8_t> texData(numSamplers * texFormat.getPixelSize()); |
| const tcu::PixelBufferAccess refTexAccess(texFormat, numSamplers, 1, 1, &texData[0]); |
| |
| ShaderSpec shaderSpec; |
| de::Random rnd(deInt32Hash(m_samplerType) ^ deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType)); |
| |
| for (int ndx = 0; ndx < numLookups; ndx++) |
| lookupIndices[ndx] = rnd.getInt(0, numSamplers - 1); |
| |
| getShaderSpec(&shaderSpec, numSamplers, numLookups, &lookupIndices[0], m_context.getRenderContext()); |
| |
| coords.resize(numInvocations * getDataTypeScalarSize(coordType)); |
| |
| if (m_samplerType != TYPE_SAMPLER_CUBE_ARRAY_SHADOW && isShadowSampler(m_samplerType)) |
| { |
| // Use different comparison value per invocation. |
| // \note Texture uses odd values, comparison even values. |
| const int numCoordComps = getDataTypeScalarSize(coordType); |
| const float cmpValues[] = {0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f}; |
| |
| for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++) |
| coords[invocationNdx * numCoordComps + (numCoordComps - 1)] = |
| rnd.choose<float>(DE_ARRAY_BEGIN(cmpValues), DE_ARRAY_END(cmpValues)); |
| } |
| |
| fillTextureData(refTexAccess, rnd); |
| |
| outData.resize(numLookups * outLookupStride); |
| |
| { |
| const RenderContext &renderCtx = m_context.getRenderContext(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| ShaderExecutorPtr executor(createExecutor(m_context.getRenderContext(), m_shaderType, shaderSpec)); |
| TextureVector textures(renderCtx, numSamplers); |
| vector<void *> inputs; |
| vector<void *> outputs; |
| vector<int> expandedIndices; |
| const int maxIndex = maxElement(lookupIndices); |
| |
| m_testCtx.getLog() << *executor; |
| |
| if (!executor->isOk()) |
| TCU_FAIL("Compile failed"); |
| |
| executor->useProgram(); |
| |
| // \todo [2014-03-05 pyry] Do we want to randomize tex unit assignments? |
| for (int samplerNdx = 0; samplerNdx < numSamplers; samplerNdx++) |
| { |
| const string samplerName = string("sampler[") + de::toString(samplerNdx) + "]"; |
| const int samplerLoc = gl.getUniformLocation(executor->getProgram(), samplerName.c_str()); |
| |
| if (samplerNdx > maxIndex && samplerLoc < 0) |
| continue; // Unused uniform eliminated by compiler |
| |
| TCU_CHECK_MSG(samplerLoc >= 0, (string("No location for uniform '") + samplerName + "' found").c_str()); |
| |
| gl.activeTexture(GL_TEXTURE0 + samplerNdx); |
| setupTexture(gl, textures[samplerNdx], m_samplerType, texFormat, |
| &texData[samplerNdx * texFormat.getPixelSize()]); |
| |
| gl.uniform1i(samplerLoc, samplerNdx); |
| } |
| |
| inputs.push_back(&coords[0]); |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| expandedIndices.resize(numInvocations * lookupIndices.size()); |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| for (int invNdx = 0; invNdx < numInvocations; invNdx++) |
| expandedIndices[lookupNdx * numInvocations + invNdx] = lookupIndices[lookupNdx]; |
| } |
| |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| inputs.push_back(&expandedIndices[lookupNdx * numInvocations]); |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| uploadUniformIndices(gl, executor->getProgram(), "index", numLookups, &lookupIndices[0]); |
| |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| outputs.push_back(&outData[outLookupStride * lookupNdx]); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed"); |
| |
| executor->execute(numInvocations, &inputs[0], &outputs[0]); |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| |
| if (isShadowSampler(m_samplerType)) |
| { |
| const tcu::Sampler refSampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE, |
| tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST, tcu::Sampler::NEAREST, 0.0f, |
| false /* non-normalized */, tcu::Sampler::COMPAREMODE_LESS); |
| const int numCoordComps = getDataTypeScalarSize(coordType); |
| |
| TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 1); |
| |
| // Each invocation may have different results. |
| for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++) |
| { |
| const float coord = coords[invocationNdx * numCoordComps + (numCoordComps - 1)]; |
| |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| const int texNdx = lookupIndices[lookupNdx]; |
| const float result = |
| *((const float *)(const uint8_t *)&outData[lookupNdx * outLookupStride + invocationNdx]); |
| const float reference = refTexAccess.sample2DCompare(refSampler, tcu::Sampler::NEAREST, coord, |
| (float)texNdx, 0.0f, tcu::IVec3(0)); |
| |
| if (de::abs(result - reference) > 0.005f) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", lookup " |
| << lookupNdx << ": expected " << reference << ", got " << result |
| << TestLog::EndMessage; |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result"); |
| } |
| } |
| } |
| } |
| else |
| { |
| TCU_CHECK_INTERNAL(getDataTypeScalarSize(outputType) == 4); |
| |
| // Validate results from first invocation |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| const int texNdx = lookupIndices[lookupNdx]; |
| const uint8_t *resPtr = (const uint8_t *)&outData[lookupNdx * outLookupStride]; |
| bool isOk; |
| |
| if (outputType == TYPE_FLOAT_VEC4) |
| { |
| const float threshold = 1.0f / 256.0f; |
| const tcu::Vec4 reference = refTexAccess.getPixel(texNdx, 0); |
| const float *floatPtr = (const float *)resPtr; |
| const tcu::Vec4 result(floatPtr[0], floatPtr[1], floatPtr[2], floatPtr[3]); |
| |
| isOk = boolAll(lessThanEqual(abs(reference - result), tcu::Vec4(threshold))); |
| |
| if (!isOk) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected " |
| << reference << ", got " << result << TestLog::EndMessage; |
| } |
| } |
| else |
| { |
| const tcu::UVec4 reference = refTexAccess.getPixelUint(texNdx, 0); |
| const uint32_t *uintPtr = (const uint32_t *)resPtr; |
| const tcu::UVec4 result(uintPtr[0], uintPtr[1], uintPtr[2], uintPtr[3]); |
| |
| isOk = boolAll(equal(reference, result)); |
| |
| if (!isOk) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: at lookup " << lookupNdx << ": expected " |
| << reference << ", got " << result << TestLog::EndMessage; |
| } |
| } |
| |
| if (!isOk && m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid lookup result"); |
| } |
| |
| // Check results of other invocations against first one |
| for (int invocationNdx = 1; invocationNdx < numInvocations; invocationNdx++) |
| { |
| for (int lookupNdx = 0; lookupNdx < numLookups; lookupNdx++) |
| { |
| const uint32_t *refPtr = &outData[lookupNdx * outLookupStride]; |
| const uint32_t *resPtr = refPtr + invocationNdx * 4; |
| bool isOk = true; |
| |
| for (int ndx = 0; ndx < 4; ndx++) |
| isOk = isOk && (refPtr[ndx] == resPtr[ndx]); |
| |
| if (!isOk) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: invocation " << invocationNdx << " result " |
| << tcu::formatArray(tcu::Format::HexIterator<uint32_t>(resPtr), |
| tcu::Format::HexIterator<uint32_t>(resPtr + 4)) |
| << " for lookup " << lookupNdx << " doesn't match result from first invocation " |
| << tcu::formatArray(tcu::Format::HexIterator<uint32_t>(refPtr), |
| tcu::Format::HexIterator<uint32_t>(refPtr + 4)) |
| << TestLog::EndMessage; |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Inconsistent lookup results"); |
| } |
| } |
| } |
| } |
| |
| return STOP; |
| } |
| |
| class BlockArrayIndexingCase : public TestCase |
| { |
| public: |
| enum BlockType |
| { |
| BLOCKTYPE_UNIFORM = 0, |
| BLOCKTYPE_BUFFER, |
| |
| BLOCKTYPE_LAST |
| }; |
| BlockArrayIndexingCase(Context &context, const char *name, const char *description, BlockType blockType, |
| IndexExprType indexExprType, ShaderType shaderType); |
| ~BlockArrayIndexingCase(void); |
| |
| void init(void); |
| IterateResult iterate(void); |
| |
| private: |
| BlockArrayIndexingCase(const BlockArrayIndexingCase &); |
| BlockArrayIndexingCase &operator=(const BlockArrayIndexingCase &); |
| |
| void getShaderSpec(ShaderSpec *spec, int numInstances, int numReads, const int *readIndices, |
| const RenderContext &renderContext) const; |
| |
| const BlockType m_blockType; |
| const IndexExprType m_indexExprType; |
| const ShaderType m_shaderType; |
| |
| const int m_numInstances; |
| }; |
| |
| BlockArrayIndexingCase::BlockArrayIndexingCase(Context &context, const char *name, const char *description, |
| BlockType blockType, IndexExprType indexExprType, ShaderType shaderType) |
| : TestCase(context, name, description) |
| , m_blockType(blockType) |
| , m_indexExprType(indexExprType) |
| , m_shaderType(shaderType) |
| , m_numInstances(4) |
| { |
| } |
| |
| BlockArrayIndexingCase::~BlockArrayIndexingCase(void) |
| { |
| } |
| |
| void BlockArrayIndexingCase::init(void) |
| { |
| const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5), |
| "GL_ARB_ES3_2_compatibility"); |
| |
| if (!supportsES32) |
| { |
| if (m_shaderType == SHADERTYPE_GEOMETRY) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"), |
| "GL_EXT_geometry_shader extension is required to run geometry shader tests."); |
| |
| if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"), |
| "GL_EXT_tessellation_shader extension is required to run tessellation shader tests."); |
| |
| if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"), |
| "GL_EXT_gpu_shader5 extension is required for dynamic indexing of interface blocks."); |
| } |
| |
| if (m_blockType == BLOCKTYPE_BUFFER) |
| { |
| const uint32_t limitPnames[] = { |
| GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, |
| GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, |
| GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS}; |
| |
| const glw::Functions &gl = m_context.getRenderContext().getFunctions(); |
| int maxBlocks = 0; |
| |
| gl.getIntegerv(limitPnames[m_shaderType], &maxBlocks); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()"); |
| |
| if (maxBlocks < 2 + m_numInstances) |
| throw tcu::NotSupportedError("Not enough shader storage blocks supported for shader type"); |
| } |
| } |
| |
| void BlockArrayIndexingCase::getShaderSpec(ShaderSpec *spec, int numInstances, int numReads, const int *readIndices, |
| const RenderContext &renderContext) const |
| { |
| const int binding = 2; |
| const char *blockName = "Block"; |
| const char *instanceName = "block"; |
| const char *indicesPrefix = "index"; |
| const char *resultPrefix = "result"; |
| const char *interfaceName = m_blockType == BLOCKTYPE_UNIFORM ? "uniform" : "buffer"; |
| const char *layout = m_blockType == BLOCKTYPE_UNIFORM ? "std140" : "std430"; |
| const bool supportsES32 = |
| contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility"); |
| std::ostringstream global; |
| std::ostringstream code; |
| |
| if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && |
| m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "#extension GL_EXT_gpu_shader5 : require\n"; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "const highp int indexBase = 1;\n"; |
| |
| global << "layout(" << layout << ", binding = " << binding << ") " << interfaceName << " " << blockName |
| << "\n" |
| "{\n" |
| " uint value;\n" |
| "} " |
| << instanceName << "[" << numInstances << "];\n"; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| { |
| const string varName = indicesPrefix + de::toString(readNdx); |
| spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP))); |
| } |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| declareUniformIndexVars(global, indicesPrefix, numReads); |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| { |
| const string varName = resultPrefix + de::toString(readNdx); |
| spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP))); |
| } |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| { |
| code << resultPrefix << readNdx << " = " << instanceName << "["; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL) |
| code << readIndices[readNdx]; |
| else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| code << "indexBase + " << (readIndices[readNdx] - 1); |
| else |
| code << indicesPrefix << readNdx; |
| |
| code << "].value;\n"; |
| } |
| |
| spec->version = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES; |
| spec->globalDeclarations = global.str(); |
| spec->source = code.str(); |
| } |
| |
| BlockArrayIndexingCase::IterateResult BlockArrayIndexingCase::iterate(void) |
| { |
| const int numInvocations = 32; |
| const int numInstances = m_numInstances; |
| const int numReads = 4; |
| vector<int> readIndices(numReads); |
| vector<uint32_t> inValues(numInstances); |
| vector<uint32_t> outValues(numInvocations * numReads); |
| ShaderSpec shaderSpec; |
| de::Random rnd(deInt32Hash(m_shaderType) ^ deInt32Hash(m_blockType) ^ deInt32Hash(m_indexExprType)); |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| readIndices[readNdx] = rnd.getInt(0, numInstances - 1); |
| |
| for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) |
| inValues[instanceNdx] = rnd.getUint32(); |
| |
| getShaderSpec(&shaderSpec, numInstances, numReads, &readIndices[0], m_context.getRenderContext()); |
| |
| { |
| const RenderContext &renderCtx = m_context.getRenderContext(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| const int baseBinding = 2; |
| const BufferVector buffers(renderCtx, numInstances); |
| const uint32_t bufTarget = m_blockType == BLOCKTYPE_BUFFER ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER; |
| ShaderExecutorPtr shaderExecutor(createExecutor(renderCtx, m_shaderType, shaderSpec)); |
| vector<int> expandedIndices; |
| vector<void *> inputs; |
| vector<void *> outputs; |
| |
| m_testCtx.getLog() << *shaderExecutor; |
| |
| if (!shaderExecutor->isOk()) |
| TCU_FAIL("Compile failed"); |
| |
| shaderExecutor->useProgram(); |
| |
| for (int instanceNdx = 0; instanceNdx < numInstances; instanceNdx++) |
| { |
| gl.bindBuffer(bufTarget, buffers[instanceNdx]); |
| gl.bufferData(bufTarget, (glw::GLsizeiptr)sizeof(uint32_t), &inValues[instanceNdx], GL_STATIC_DRAW); |
| gl.bindBufferBase(bufTarget, baseBinding + instanceNdx, buffers[instanceNdx]); |
| } |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| expandedIndices.resize(numInvocations * readIndices.size()); |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| { |
| int *dst = &expandedIndices[numInvocations * readNdx]; |
| std::fill(dst, dst + numInvocations, readIndices[readNdx]); |
| } |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| inputs.push_back(&expandedIndices[readNdx * numInvocations]); |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numReads, &readIndices[0]); |
| |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| outputs.push_back(&outValues[readNdx * numInvocations]); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed"); |
| |
| shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]); |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| |
| for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++) |
| { |
| for (int readNdx = 0; readNdx < numReads; readNdx++) |
| { |
| const uint32_t refValue = inValues[readIndices[readNdx]]; |
| const uint32_t resValue = outValues[readNdx * numInvocations + invocationNdx]; |
| |
| if (refValue != resValue) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", read " |
| << readNdx << ": expected " << tcu::toHex(refValue) << ", got " |
| << tcu::toHex(resValue) << TestLog::EndMessage; |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value"); |
| } |
| } |
| } |
| |
| return STOP; |
| } |
| |
| class AtomicCounterIndexingCase : public TestCase |
| { |
| public: |
| AtomicCounterIndexingCase(Context &context, const char *name, const char *description, IndexExprType indexExprType, |
| ShaderType shaderType); |
| ~AtomicCounterIndexingCase(void); |
| |
| void init(void); |
| IterateResult iterate(void); |
| |
| private: |
| AtomicCounterIndexingCase(const AtomicCounterIndexingCase &); |
| AtomicCounterIndexingCase &operator=(const AtomicCounterIndexingCase &); |
| |
| void getShaderSpec(ShaderSpec *spec, int numCounters, int numOps, const int *opIndices, |
| const RenderContext &renderContext) const; |
| |
| const IndexExprType m_indexExprType; |
| const glu::ShaderType m_shaderType; |
| int32_t m_numCounters; |
| }; |
| |
| AtomicCounterIndexingCase::AtomicCounterIndexingCase(Context &context, const char *name, const char *description, |
| IndexExprType indexExprType, ShaderType shaderType) |
| : TestCase(context, name, description) |
| , m_indexExprType(indexExprType) |
| , m_shaderType(shaderType) |
| , m_numCounters(0) |
| { |
| } |
| |
| AtomicCounterIndexingCase::~AtomicCounterIndexingCase(void) |
| { |
| } |
| |
| uint32_t getMaxAtomicCounterEnum(glu::ShaderType type) |
| { |
| switch (type) |
| { |
| case glu::SHADERTYPE_VERTEX: |
| return GL_MAX_VERTEX_ATOMIC_COUNTERS; |
| case glu::SHADERTYPE_FRAGMENT: |
| return GL_MAX_FRAGMENT_ATOMIC_COUNTERS; |
| case glu::SHADERTYPE_GEOMETRY: |
| return GL_MAX_GEOMETRY_ATOMIC_COUNTERS; |
| case glu::SHADERTYPE_COMPUTE: |
| return GL_MAX_COMPUTE_ATOMIC_COUNTERS; |
| case glu::SHADERTYPE_TESSELLATION_CONTROL: |
| return GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS; |
| case glu::SHADERTYPE_TESSELLATION_EVALUATION: |
| return GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS; |
| |
| default: |
| DE_FATAL("Unknown shader type"); |
| return -1; |
| } |
| } |
| |
| void AtomicCounterIndexingCase::init(void) |
| { |
| const bool supportsES32 = contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(m_context.getRenderContext().getFunctions(), glu::ApiType::core(4, 5), |
| "GL_ARB_ES3_2_compatibility"); |
| |
| if (!supportsES32) |
| { |
| if (m_shaderType == SHADERTYPE_GEOMETRY) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"), |
| "GL_EXT_geometry_shader extension is required to run geometry shader tests."); |
| |
| if (m_shaderType == SHADERTYPE_TESSELLATION_CONTROL || m_shaderType == SHADERTYPE_TESSELLATION_EVALUATION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"), |
| "GL_EXT_tessellation_shader extension is required to run tessellation shader tests."); |
| |
| if (m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| TCU_CHECK_AND_THROW(NotSupportedError, |
| m_context.getContextInfo().isExtensionSupported("GL_EXT_gpu_shader5"), |
| "GL_EXT_gpu_shader5 extension is required for dynamic indexing of atomic counters."); |
| } |
| |
| { |
| m_context.getRenderContext().getFunctions().getIntegerv(getMaxAtomicCounterEnum(m_shaderType), &m_numCounters); |
| |
| if (m_numCounters < 1) |
| { |
| const string message = |
| "Atomic counters not supported in " + string(glu::getShaderTypeName(m_shaderType)) + " shader"; |
| TCU_THROW(NotSupportedError, message.c_str()); |
| } |
| } |
| } |
| |
| void AtomicCounterIndexingCase::getShaderSpec(ShaderSpec *spec, int numCounters, int numOps, const int *opIndices, |
| const RenderContext &renderContext) const |
| { |
| const char *indicesPrefix = "index"; |
| const char *resultPrefix = "result"; |
| const bool supportsES32 = |
| contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) || |
| hasExtension(renderContext.getFunctions(), glu::ApiType::core(4, 5), "GL_ARB_ES3_2_compatibility"); |
| std::ostringstream global; |
| std::ostringstream code; |
| |
| if (!supportsES32 && m_indexExprType != INDEX_EXPR_TYPE_CONST_LITERAL && |
| m_indexExprType != INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "#extension GL_EXT_gpu_shader5 : require\n"; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| global << "const highp int indexBase = 1;\n"; |
| |
| global << "layout(binding = 0) uniform atomic_uint counter[" << numCounters << "];\n"; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| { |
| const string varName = indicesPrefix + de::toString(opNdx); |
| spec->inputs.push_back(Symbol(varName, VarType(TYPE_INT, PRECISION_HIGHP))); |
| } |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| declareUniformIndexVars(global, indicesPrefix, numOps); |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| { |
| const string varName = resultPrefix + de::toString(opNdx); |
| spec->outputs.push_back(Symbol(varName, VarType(TYPE_UINT, PRECISION_HIGHP))); |
| } |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| { |
| code << resultPrefix << opNdx << " = atomicCounterIncrement(counter["; |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL) |
| code << opIndices[opNdx]; |
| else if (m_indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| code << "indexBase + " << (opIndices[opNdx] - 1); |
| else |
| code << indicesPrefix << opNdx; |
| |
| code << "]);\n"; |
| } |
| |
| spec->version = supportsES32 ? GLSL_VERSION_320_ES : GLSL_VERSION_310_ES; |
| spec->globalDeclarations = global.str(); |
| spec->source = code.str(); |
| } |
| |
| AtomicCounterIndexingCase::IterateResult AtomicCounterIndexingCase::iterate(void) |
| { |
| const RenderContext &renderCtx = m_context.getRenderContext(); |
| const glw::Functions &gl = renderCtx.getFunctions(); |
| const Buffer counterBuffer(renderCtx); |
| |
| const int numInvocations = 32; |
| const int numOps = 4; |
| vector<int> opIndices(numOps); |
| vector<uint32_t> outValues(numInvocations * numOps); |
| ShaderSpec shaderSpec; |
| de::Random rnd(deInt32Hash(m_shaderType) ^ deInt32Hash(m_indexExprType)); |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| opIndices[opNdx] = rnd.getInt(0, numOps - 1); |
| |
| getShaderSpec(&shaderSpec, m_numCounters, numOps, &opIndices[0], m_context.getRenderContext()); |
| |
| { |
| const BufferVector buffers(renderCtx, m_numCounters); |
| ShaderExecutorPtr shaderExecutor(createExecutor(renderCtx, m_shaderType, shaderSpec)); |
| vector<int> expandedIndices; |
| vector<void *> inputs; |
| vector<void *> outputs; |
| |
| m_testCtx.getLog() << *shaderExecutor; |
| |
| if (!shaderExecutor->isOk()) |
| TCU_FAIL("Compile failed"); |
| |
| { |
| const int bufSize = getProgramResourceInt(gl, shaderExecutor->getProgram(), GL_ATOMIC_COUNTER_BUFFER, 0, |
| GL_BUFFER_DATA_SIZE); |
| const int maxNdx = maxElement(opIndices); |
| std::vector<uint8_t> emptyData(m_numCounters * 4, 0); |
| |
| if (bufSize < (maxNdx + 1) * 4) |
| TCU_FAIL((string("GL reported invalid buffer size " + de::toString(bufSize)).c_str())); |
| |
| gl.bindBuffer(GL_ATOMIC_COUNTER_BUFFER, *counterBuffer); |
| gl.bufferData(GL_ATOMIC_COUNTER_BUFFER, (glw::GLsizeiptr)emptyData.size(), &emptyData[0], GL_STATIC_DRAW); |
| gl.bindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, *counterBuffer); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Atomic counter buffer initialization failed"); |
| } |
| |
| shaderExecutor->useProgram(); |
| |
| if (m_indexExprType == INDEX_EXPR_TYPE_DYNAMIC_UNIFORM) |
| { |
| expandedIndices.resize(numInvocations * opIndices.size()); |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| { |
| int *dst = &expandedIndices[numInvocations * opNdx]; |
| std::fill(dst, dst + numInvocations, opIndices[opNdx]); |
| } |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| inputs.push_back(&expandedIndices[opNdx * numInvocations]); |
| } |
| else if (m_indexExprType == INDEX_EXPR_TYPE_UNIFORM) |
| uploadUniformIndices(gl, shaderExecutor->getProgram(), "index", numOps, &opIndices[0]); |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| outputs.push_back(&outValues[opNdx * numInvocations]); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed"); |
| |
| shaderExecutor->execute(numInvocations, inputs.empty() ? DE_NULL : &inputs[0], &outputs[0]); |
| } |
| |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| |
| { |
| vector<int> numHits(m_numCounters, 0); // Number of hits per counter. |
| vector<uint32_t> counterValues(m_numCounters); |
| vector<vector<bool>> counterMasks(m_numCounters); |
| |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| numHits[opIndices[opNdx]] += 1; |
| |
| // Read counter values |
| { |
| const void *mapPtr = DE_NULL; |
| |
| try |
| { |
| mapPtr = gl.mapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, m_numCounters * 4, GL_MAP_READ_BIT); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER)"); |
| TCU_CHECK(mapPtr); |
| std::copy((const uint32_t *)mapPtr, (const uint32_t *)mapPtr + m_numCounters, &counterValues[0]); |
| gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER); |
| } |
| catch (...) |
| { |
| if (mapPtr) |
| gl.unmapBuffer(GL_ATOMIC_COUNTER_BUFFER); |
| throw; |
| } |
| } |
| |
| // Verify counter values |
| for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++) |
| { |
| const uint32_t refCount = (uint32_t)(numHits[counterNdx] * numInvocations); |
| const uint32_t resCount = counterValues[counterNdx]; |
| |
| if (refCount != resCount) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: atomic counter " << counterNdx << " has value " |
| << resCount << ", expected " << refCount << TestLog::EndMessage; |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid atomic counter value"); |
| } |
| } |
| |
| // Allocate bitmasks - one bit per each valid result value |
| for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++) |
| { |
| const int counterValue = numHits[counterNdx] * numInvocations; |
| counterMasks[counterNdx].resize(counterValue, false); |
| } |
| |
| // Verify result values from shaders |
| for (int invocationNdx = 0; invocationNdx < numInvocations; invocationNdx++) |
| { |
| for (int opNdx = 0; opNdx < numOps; opNdx++) |
| { |
| const int counterNdx = opIndices[opNdx]; |
| const uint32_t resValue = outValues[opNdx * numInvocations + invocationNdx]; |
| const bool rangeOk = de::inBounds(resValue, 0u, (uint32_t)counterMasks[counterNdx].size()); |
| const bool notSeen = rangeOk && !counterMasks[counterNdx][resValue]; |
| const bool isOk = rangeOk && notSeen; |
| |
| if (!isOk) |
| { |
| m_testCtx.getLog() << TestLog::Message << "ERROR: at invocation " << invocationNdx << ", op " |
| << opNdx << ": got invalid result value " << resValue << TestLog::EndMessage; |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result value"); |
| } |
| else |
| { |
| // Mark as used - no other invocation should see this value from same counter. |
| counterMasks[counterNdx][resValue] = true; |
| } |
| } |
| } |
| |
| if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS) |
| { |
| // Consistency check - all masks should be 1 now |
| for (int counterNdx = 0; counterNdx < m_numCounters; counterNdx++) |
| { |
| for (vector<bool>::const_iterator i = counterMasks[counterNdx].begin(); |
| i != counterMasks[counterNdx].end(); i++) |
| TCU_CHECK_INTERNAL(*i); |
| } |
| } |
| } |
| |
| return STOP; |
| } |
| |
| } // namespace |
| |
| OpaqueTypeIndexingTests::OpaqueTypeIndexingTests(Context &context) |
| : TestCaseGroup(context, "opaque_type_indexing", "Opaque Type Indexing Tests") |
| { |
| } |
| |
| OpaqueTypeIndexingTests::~OpaqueTypeIndexingTests(void) |
| { |
| } |
| |
| void OpaqueTypeIndexingTests::init(void) |
| { |
| static const struct |
| { |
| IndexExprType type; |
| const char *name; |
| const char *description; |
| } indexingTypes[] = { |
| {INDEX_EXPR_TYPE_CONST_LITERAL, "const_literal", "Indexing by constant literal"}, |
| {INDEX_EXPR_TYPE_CONST_EXPRESSION, "const_expression", "Indexing by constant expression"}, |
| {INDEX_EXPR_TYPE_UNIFORM, "uniform", "Indexing by uniform value"}, |
| {INDEX_EXPR_TYPE_DYNAMIC_UNIFORM, "dynamically_uniform", "Indexing by dynamically uniform expression"}}; |
| |
| static const struct |
| { |
| ShaderType type; |
| const char *name; |
| } shaderTypes[] = {{SHADERTYPE_VERTEX, "vertex"}, |
| {SHADERTYPE_FRAGMENT, "fragment"}, |
| {SHADERTYPE_COMPUTE, "compute"}, |
| {SHADERTYPE_GEOMETRY, "geometry"}, |
| {SHADERTYPE_TESSELLATION_CONTROL, "tessellation_control"}, |
| {SHADERTYPE_TESSELLATION_EVALUATION, "tessellation_evaluation"}}; |
| |
| // .sampler |
| { |
| static const DataType samplerTypes[] = { |
| // \note 1D images will be added by a later extension. |
| // TYPE_SAMPLER_1D, |
| TYPE_SAMPLER_2D, TYPE_SAMPLER_CUBE, TYPE_SAMPLER_2D_ARRAY, TYPE_SAMPLER_3D, |
| // TYPE_SAMPLER_1D_SHADOW, |
| TYPE_SAMPLER_2D_SHADOW, TYPE_SAMPLER_CUBE_SHADOW, TYPE_SAMPLER_2D_ARRAY_SHADOW, |
| // TYPE_INT_SAMPLER_1D, |
| TYPE_INT_SAMPLER_2D, TYPE_INT_SAMPLER_CUBE, TYPE_INT_SAMPLER_2D_ARRAY, TYPE_INT_SAMPLER_3D, |
| // TYPE_UINT_SAMPLER_1D, |
| TYPE_UINT_SAMPLER_2D, TYPE_UINT_SAMPLER_CUBE, TYPE_UINT_SAMPLER_2D_ARRAY, TYPE_UINT_SAMPLER_3D, |
| TYPE_SAMPLER_CUBE_ARRAY, TYPE_SAMPLER_CUBE_ARRAY_SHADOW, TYPE_INT_SAMPLER_CUBE_ARRAY, |
| TYPE_UINT_SAMPLER_CUBE_ARRAY}; |
| |
| tcu::TestCaseGroup *const samplerGroup = |
| new tcu::TestCaseGroup(m_testCtx, "sampler", "Sampler Array Indexing Tests"); |
| addChild(samplerGroup); |
| |
| for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++) |
| { |
| const IndexExprType indexExprType = indexingTypes[indexTypeNdx].type; |
| tcu::TestCaseGroup *const indexGroup = new tcu::TestCaseGroup(m_testCtx, indexingTypes[indexTypeNdx].name, |
| indexingTypes[indexTypeNdx].description); |
| samplerGroup->addChild(indexGroup); |
| |
| for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++) |
| { |
| const ShaderType shaderType = shaderTypes[shaderTypeNdx].type; |
| tcu::TestCaseGroup *const shaderGroup = |
| new tcu::TestCaseGroup(m_testCtx, shaderTypes[shaderTypeNdx].name, ""); |
| indexGroup->addChild(shaderGroup); |
| |
| for (int samplerTypeNdx = 0; samplerTypeNdx < DE_LENGTH_OF_ARRAY(samplerTypes); samplerTypeNdx++) |
| { |
| const DataType samplerType = samplerTypes[samplerTypeNdx]; |
| const char *samplerName = getDataTypeName(samplerType); |
| const string caseName = de::toLower(samplerName); |
| |
| shaderGroup->addChild(new SamplerIndexingCase(m_context, caseName.c_str(), "", shaderType, |
| samplerType, indexExprType)); |
| } |
| } |
| } |
| } |
| |
| // .ubo / .ssbo / .atomic_counter |
| { |
| tcu::TestCaseGroup *const uboGroup = |
| new tcu::TestCaseGroup(m_testCtx, "ubo", "Uniform Block Instance Array Indexing Tests"); |
| tcu::TestCaseGroup *const ssboGroup = |
| new tcu::TestCaseGroup(m_testCtx, "ssbo", "Buffer Block Instance Array Indexing Tests"); |
| tcu::TestCaseGroup *const acGroup = |
| new tcu::TestCaseGroup(m_testCtx, "atomic_counter", "Atomic Counter Array Indexing Tests"); |
| addChild(uboGroup); |
| addChild(ssboGroup); |
| addChild(acGroup); |
| |
| for (int indexTypeNdx = 0; indexTypeNdx < DE_LENGTH_OF_ARRAY(indexingTypes); indexTypeNdx++) |
| { |
| const IndexExprType indexExprType = indexingTypes[indexTypeNdx].type; |
| const char *indexExprName = indexingTypes[indexTypeNdx].name; |
| const char *indexExprDesc = indexingTypes[indexTypeNdx].description; |
| |
| for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++) |
| { |
| const ShaderType shaderType = shaderTypes[shaderTypeNdx].type; |
| const string name = string(indexExprName) + "_" + shaderTypes[shaderTypeNdx].name; |
| |
| uboGroup->addChild(new BlockArrayIndexingCase(m_context, name.c_str(), indexExprDesc, |
| BlockArrayIndexingCase::BLOCKTYPE_UNIFORM, indexExprType, |
| shaderType)); |
| acGroup->addChild( |
| new AtomicCounterIndexingCase(m_context, name.c_str(), indexExprDesc, indexExprType, shaderType)); |
| |
| if (indexExprType == INDEX_EXPR_TYPE_CONST_LITERAL || indexExprType == INDEX_EXPR_TYPE_CONST_EXPRESSION) |
| ssboGroup->addChild(new BlockArrayIndexingCase(m_context, name.c_str(), indexExprDesc, |
| BlockArrayIndexingCase::BLOCKTYPE_BUFFER, |
| indexExprType, shaderType)); |
| } |
| } |
| } |
| } |
| |
| } // namespace Functional |
| } // namespace gles31 |
| } // namespace deqp |