| /*------------------------------------------------------------------------- |
| * drawElements Quality Program OpenGL ES 3.1 Module |
| * ------------------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Shader Image Load & Store Tests. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "es31fShaderImageLoadStoreTests.hpp" |
| #include "glsTextureTestUtil.hpp" |
| #include "gluContextInfo.hpp" |
| #include "gluRenderContext.hpp" |
| #include "gluShaderProgram.hpp" |
| #include "gluObjectWrapper.hpp" |
| #include "gluPixelTransfer.hpp" |
| #include "gluTextureUtil.hpp" |
| #include "gluStrUtil.hpp" |
| #include "gluCallLogWrapper.hpp" |
| #include "gluProgramInterfaceQuery.hpp" |
| #include "gluDrawUtil.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuVector.hpp" |
| #include "tcuImageCompare.hpp" |
| #include "tcuFloat.hpp" |
| #include "tcuVectorUtil.hpp" |
| #include "deStringUtil.hpp" |
| #include "deSharedPtr.hpp" |
| #include "deUniquePtr.hpp" |
| #include "deRandom.hpp" |
| #include "deMemory.h" |
| #include "glwFunctions.hpp" |
| #include "glwDefs.hpp" |
| #include "glwEnums.hpp" |
| |
| #include <vector> |
| #include <string> |
| #include <algorithm> |
| #include <map> |
| |
| using glu::RenderContext; |
| using tcu::TestLog; |
| using tcu::Vec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| using tcu::IVec2; |
| using tcu::IVec3; |
| using tcu::IVec4; |
| using tcu::UVec2; |
| using tcu::UVec3; |
| using tcu::UVec4; |
| using tcu::TextureFormat; |
| using tcu::ConstPixelBufferAccess; |
| using tcu::PixelBufferAccess; |
| using de::toString; |
| using de::SharedPtr; |
| using de::UniquePtr; |
| |
| using std::vector; |
| using std::string; |
| |
| namespace deqp |
| { |
| |
| using namespace gls::TextureTestUtil; |
| using namespace glu::TextureTestUtil; |
| |
| namespace gles31 |
| { |
| namespace Functional |
| { |
| |
| //! Default image sizes used in most test cases. |
| static inline IVec3 defaultImageSize (TextureType type) |
| { |
| switch (type) |
| { |
| case TEXTURETYPE_BUFFER: return IVec3(64, 1, 1); |
| case TEXTURETYPE_2D: return IVec3(64, 64, 1); |
| case TEXTURETYPE_CUBE: return IVec3(64, 64, 1); |
| case TEXTURETYPE_3D: return IVec3(64, 64, 8); |
| case TEXTURETYPE_2D_ARRAY: return IVec3(64, 64, 8); |
| default: |
| DE_ASSERT(false); |
| return IVec3(-1); |
| } |
| } |
| |
| template <typename T, int Size> |
| static string arrayStr (const T (&arr)[Size]) |
| { |
| string result = "{ "; |
| for (int i = 0; i < Size; i++) |
| result += (i > 0 ? ", " : "") + toString(arr[i]); |
| result += " }"; |
| return result; |
| } |
| |
| template <typename T, int N> |
| static int arrayIndexOf (const T (&arr)[N], const T& e) |
| { |
| return (int)(std::find(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), e) - DE_ARRAY_BEGIN(arr)); |
| } |
| |
| static const char* getTextureTypeName (TextureType type) |
| { |
| switch (type) |
| { |
| case TEXTURETYPE_BUFFER: return "buffer"; |
| case TEXTURETYPE_2D: return "2d"; |
| case TEXTURETYPE_CUBE: return "cube"; |
| case TEXTURETYPE_3D: return "3d"; |
| case TEXTURETYPE_2D_ARRAY: return "2d_array"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| static inline bool isFormatTypeUnsignedInteger (TextureFormat::ChannelType type) |
| { |
| return type == TextureFormat::UNSIGNED_INT8 || |
| type == TextureFormat::UNSIGNED_INT16 || |
| type == TextureFormat::UNSIGNED_INT32; |
| } |
| |
| static inline bool isFormatTypeSignedInteger (TextureFormat::ChannelType type) |
| { |
| return type == TextureFormat::SIGNED_INT8 || |
| type == TextureFormat::SIGNED_INT16 || |
| type == TextureFormat::SIGNED_INT32; |
| } |
| |
| static inline bool isFormatTypeInteger (TextureFormat::ChannelType type) |
| { |
| return isFormatTypeUnsignedInteger(type) || isFormatTypeSignedInteger(type); |
| } |
| |
| static inline bool isFormatTypeUnorm (TextureFormat::ChannelType type) |
| { |
| return type == TextureFormat::UNORM_INT8 || |
| type == TextureFormat::UNORM_INT16 || |
| type == TextureFormat::UNORM_INT32; |
| } |
| |
| static inline bool isFormatTypeSnorm (TextureFormat::ChannelType type) |
| { |
| return type == TextureFormat::SNORM_INT8 || |
| type == TextureFormat::SNORM_INT16 || |
| type == TextureFormat::SNORM_INT32; |
| } |
| |
| static inline bool isFormatSupportedForTextureBuffer (const TextureFormat& format) |
| { |
| switch (format.order) |
| { |
| case TextureFormat::RGB: |
| return format.type == TextureFormat::FLOAT || |
| format.type == TextureFormat::SIGNED_INT32 || |
| format.type == TextureFormat::UNSIGNED_INT32; |
| |
| // \note Fallthroughs. |
| case TextureFormat::R: |
| case TextureFormat::RG: |
| case TextureFormat::RGBA: |
| return format.type == TextureFormat::UNORM_INT8 || |
| format.type == TextureFormat::HALF_FLOAT || |
| format.type == TextureFormat::FLOAT || |
| format.type == TextureFormat::SIGNED_INT8 || |
| format.type == TextureFormat::SIGNED_INT16 || |
| format.type == TextureFormat::SIGNED_INT32 || |
| format.type == TextureFormat::UNSIGNED_INT8 || |
| format.type == TextureFormat::UNSIGNED_INT16 || |
| format.type == TextureFormat::UNSIGNED_INT32; |
| |
| default: |
| return false; |
| } |
| } |
| |
| static inline string getShaderImageFormatQualifier (const TextureFormat& format) |
| { |
| const char* orderPart; |
| const char* typePart; |
| |
| switch (format.order) |
| { |
| case TextureFormat::R: orderPart = "r"; break; |
| case TextureFormat::RGBA: orderPart = "rgba"; break; |
| default: |
| DE_ASSERT(false); |
| orderPart = DE_NULL; |
| } |
| |
| switch (format.type) |
| { |
| case TextureFormat::FLOAT: typePart = "32f"; break; |
| case TextureFormat::HALF_FLOAT: typePart = "16f"; break; |
| |
| case TextureFormat::UNSIGNED_INT32: typePart = "32ui"; break; |
| case TextureFormat::UNSIGNED_INT16: typePart = "16ui"; break; |
| case TextureFormat::UNSIGNED_INT8: typePart = "8ui"; break; |
| |
| case TextureFormat::SIGNED_INT32: typePart = "32i"; break; |
| case TextureFormat::SIGNED_INT16: typePart = "16i"; break; |
| case TextureFormat::SIGNED_INT8: typePart = "8i"; break; |
| |
| case TextureFormat::UNORM_INT16: typePart = "16"; break; |
| case TextureFormat::UNORM_INT8: typePart = "8"; break; |
| |
| case TextureFormat::SNORM_INT16: typePart = "16_snorm"; break; |
| case TextureFormat::SNORM_INT8: typePart = "8_snorm"; break; |
| |
| default: |
| DE_ASSERT(false); |
| typePart = DE_NULL; |
| } |
| |
| return string() + orderPart + typePart; |
| } |
| |
| static inline string getShaderSamplerOrImageType (TextureFormat::ChannelType formatType, TextureType textureType, bool isSampler) |
| { |
| const char* const formatPart = isFormatTypeUnsignedInteger(formatType) ? "u" |
| : isFormatTypeSignedInteger(formatType) ? "i" |
| : ""; |
| |
| const char* const imageTypePart = textureType == TEXTURETYPE_BUFFER ? "Buffer" |
| : textureType == TEXTURETYPE_2D ? "2D" |
| : textureType == TEXTURETYPE_3D ? "3D" |
| : textureType == TEXTURETYPE_CUBE ? "Cube" |
| : textureType == TEXTURETYPE_2D_ARRAY ? "2DArray" |
| : DE_NULL; |
| |
| return string() + formatPart + (isSampler ? "sampler" : "image") + imageTypePart; |
| } |
| |
| static inline string getShaderImageType (TextureFormat::ChannelType formatType, TextureType imageType) |
| { |
| return getShaderSamplerOrImageType(formatType, imageType, false); |
| } |
| |
| static inline string getShaderSamplerType (TextureFormat::ChannelType formatType, TextureType imageType) |
| { |
| return getShaderSamplerOrImageType(formatType, imageType, true); |
| } |
| |
| static inline deUint32 getGLTextureTarget (TextureType texType) |
| { |
| switch (texType) |
| { |
| case TEXTURETYPE_BUFFER: return GL_TEXTURE_BUFFER; |
| case TEXTURETYPE_2D: return GL_TEXTURE_2D; |
| case TEXTURETYPE_3D: return GL_TEXTURE_3D; |
| case TEXTURETYPE_CUBE: return GL_TEXTURE_CUBE_MAP; |
| case TEXTURETYPE_2D_ARRAY: return GL_TEXTURE_2D_ARRAY; |
| default: |
| DE_ASSERT(false); |
| return (deUint32)-1; |
| } |
| } |
| |
| static deUint32 cubeFaceToGLFace (tcu::CubeFace face) |
| { |
| switch (face) |
| { |
| case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X; |
| case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X; |
| case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; |
| case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y; |
| case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; |
| case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z; |
| default: |
| DE_ASSERT(false); |
| return GL_NONE; |
| } |
| } |
| |
| static inline tcu::Texture1D* newOneLevelTexture1D (const tcu::TextureFormat& format, int w) |
| { |
| tcu::Texture1D* const res = new tcu::Texture1D(format, w); |
| res->allocLevel(0); |
| return res; |
| } |
| |
| static inline tcu::Texture2D* newOneLevelTexture2D (const tcu::TextureFormat& format, int w, int h) |
| { |
| tcu::Texture2D* const res = new tcu::Texture2D(format, w, h); |
| res->allocLevel(0); |
| return res; |
| } |
| |
| static inline tcu::TextureCube* newOneLevelTextureCube (const tcu::TextureFormat& format, int size) |
| { |
| tcu::TextureCube* const res = new tcu::TextureCube(format, size); |
| for (int i = 0; i < tcu::CUBEFACE_LAST; i++) |
| res->allocLevel((tcu::CubeFace)i, 0); |
| return res; |
| } |
| |
| static inline tcu::Texture3D* newOneLevelTexture3D (const tcu::TextureFormat& format, int w, int h, int d) |
| { |
| tcu::Texture3D* const res = new tcu::Texture3D(format, w, h, d); |
| res->allocLevel(0); |
| return res; |
| } |
| |
| static inline tcu::Texture2DArray* newOneLevelTexture2DArray (const tcu::TextureFormat& format, int w, int h, int d) |
| { |
| tcu::Texture2DArray* const res = new tcu::Texture2DArray(format, w, h, d); |
| res->allocLevel(0); |
| return res; |
| } |
| |
| static inline TextureType textureLayerType (TextureType entireTextureType) |
| { |
| switch (entireTextureType) |
| { |
| // Single-layer types. |
| // \note Fallthrough. |
| case TEXTURETYPE_BUFFER: |
| case TEXTURETYPE_2D: |
| return entireTextureType; |
| |
| // Multi-layer types with 2d layers. |
| case TEXTURETYPE_3D: |
| case TEXTURETYPE_CUBE: |
| case TEXTURETYPE_2D_ARRAY: |
| return TEXTURETYPE_2D; |
| |
| default: |
| DE_ASSERT(false); |
| return TEXTURETYPE_LAST; |
| } |
| } |
| |
| static const char* const s_texBufExtString = "GL_EXT_texture_buffer"; |
| |
| static inline void checkTextureTypeExtensions (const glu::ContextInfo& contextInfo, TextureType type, const RenderContext& renderContext) |
| { |
| if (type == TEXTURETYPE_BUFFER && !contextInfo.isExtensionSupported(s_texBufExtString) && !glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2))) |
| throw tcu::NotSupportedError("Test requires " + string(s_texBufExtString) + " extension"); |
| } |
| |
| static inline string textureTypeExtensionShaderRequires (TextureType type, const RenderContext& renderContext) |
| { |
| if (!glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) && (type == TEXTURETYPE_BUFFER)) |
| return "#extension " + string(s_texBufExtString) + " : require\n"; |
| else |
| return ""; |
| } |
| |
| static const char* const s_imageAtomicExtString = "GL_OES_shader_image_atomic"; |
| |
| static inline string imageAtomicExtensionShaderRequires (const RenderContext& renderContext) |
| { |
| if (!glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2))) |
| return "#extension " + string(s_imageAtomicExtString) + " : require\n"; |
| else |
| return ""; |
| } |
| |
| namespace |
| { |
| |
| enum AtomicOperation |
| { |
| ATOMIC_OPERATION_ADD = 0, |
| ATOMIC_OPERATION_MIN, |
| ATOMIC_OPERATION_MAX, |
| ATOMIC_OPERATION_AND, |
| ATOMIC_OPERATION_OR, |
| ATOMIC_OPERATION_XOR, |
| ATOMIC_OPERATION_EXCHANGE, |
| ATOMIC_OPERATION_COMP_SWAP, |
| |
| ATOMIC_OPERATION_LAST |
| }; |
| |
| //! An order-independent operation is one for which the end result doesn't depend on the order in which the operations are carried (i.e. is both commutative and associative). |
| static bool isOrderIndependentAtomicOperation (AtomicOperation op) |
| { |
| return op == ATOMIC_OPERATION_ADD || |
| op == ATOMIC_OPERATION_MIN || |
| op == ATOMIC_OPERATION_MAX || |
| op == ATOMIC_OPERATION_AND || |
| op == ATOMIC_OPERATION_OR || |
| op == ATOMIC_OPERATION_XOR; |
| } |
| |
| //! Computes the result of an atomic operation where "a" is the data operated on and "b" is the parameter to the atomic function. |
| int computeBinaryAtomicOperationResult (AtomicOperation op, int a, int b) |
| { |
| switch (op) |
| { |
| case ATOMIC_OPERATION_ADD: return a + b; |
| case ATOMIC_OPERATION_MIN: return de::min(a, b); |
| case ATOMIC_OPERATION_MAX: return de::max(a, b); |
| case ATOMIC_OPERATION_AND: return a & b; |
| case ATOMIC_OPERATION_OR: return a | b; |
| case ATOMIC_OPERATION_XOR: return a ^ b; |
| case ATOMIC_OPERATION_EXCHANGE: return b; |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| //! \note For floats, only the exchange operation is supported. |
| float computeBinaryAtomicOperationResult (AtomicOperation op, float /*a*/, float b) |
| { |
| switch (op) |
| { |
| case ATOMIC_OPERATION_EXCHANGE: return b; |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| static const char* getAtomicOperationCaseName (AtomicOperation op) |
| { |
| switch (op) |
| { |
| case ATOMIC_OPERATION_ADD: return "add"; |
| case ATOMIC_OPERATION_MIN: return "min"; |
| case ATOMIC_OPERATION_MAX: return "max"; |
| case ATOMIC_OPERATION_AND: return "and"; |
| case ATOMIC_OPERATION_OR: return "or"; |
| case ATOMIC_OPERATION_XOR: return "xor"; |
| case ATOMIC_OPERATION_EXCHANGE: return "exchange"; |
| case ATOMIC_OPERATION_COMP_SWAP: return "comp_swap"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| static const char* getAtomicOperationShaderFuncName (AtomicOperation op) |
| { |
| switch (op) |
| { |
| case ATOMIC_OPERATION_ADD: return "imageAtomicAdd"; |
| case ATOMIC_OPERATION_MIN: return "imageAtomicMin"; |
| case ATOMIC_OPERATION_MAX: return "imageAtomicMax"; |
| case ATOMIC_OPERATION_AND: return "imageAtomicAnd"; |
| case ATOMIC_OPERATION_OR: return "imageAtomicOr"; |
| case ATOMIC_OPERATION_XOR: return "imageAtomicXor"; |
| case ATOMIC_OPERATION_EXCHANGE: return "imageAtomicExchange"; |
| case ATOMIC_OPERATION_COMP_SWAP: return "imageAtomicCompSwap"; |
| default: |
| DE_ASSERT(false); |
| return DE_NULL; |
| } |
| } |
| |
| //! In GLSL, when accessing cube images, the z coordinate is mapped to a cube face. |
| //! \note This is _not_ the same as casting the z to a tcu::CubeFace. |
| static inline tcu::CubeFace glslImageFuncZToCubeFace (int z) |
| { |
| static const tcu::CubeFace faces[6] = |
| { |
| tcu::CUBEFACE_POSITIVE_X, |
| tcu::CUBEFACE_NEGATIVE_X, |
| tcu::CUBEFACE_POSITIVE_Y, |
| tcu::CUBEFACE_NEGATIVE_Y, |
| tcu::CUBEFACE_POSITIVE_Z, |
| tcu::CUBEFACE_NEGATIVE_Z |
| }; |
| |
| DE_ASSERT(de::inBounds(z, 0, DE_LENGTH_OF_ARRAY(faces))); |
| return faces[z]; |
| } |
| |
| class BufferMemMap |
| { |
| public: |
| BufferMemMap (const glw::Functions& gl, deUint32 target, int offset, int size, deUint32 access) |
| : m_gl (gl) |
| , m_target (target) |
| , m_ptr (DE_NULL) |
| { |
| m_ptr = gl.mapBufferRange(target, offset, size, access); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glMapBufferRange()"); |
| TCU_CHECK(m_ptr); |
| } |
| |
| ~BufferMemMap (void) |
| { |
| m_gl.unmapBuffer(m_target); |
| } |
| |
| void* getPtr (void) const { return m_ptr; } |
| void* operator* (void) const { return m_ptr; } |
| |
| private: |
| BufferMemMap (const BufferMemMap& other); |
| BufferMemMap& operator= (const BufferMemMap& other); |
| |
| const glw::Functions& m_gl; |
| const deUint32 m_target; |
| void* m_ptr; |
| }; |
| |
| //! Utility for more readable uniform assignment logging; logs the name of the uniform when assigning. Handles the locations, querying them the first time they're assigned |
| // \note Assumes that the appropriate program is in use when assigning uniforms. |
| class UniformAccessLogger |
| { |
| public: |
| UniformAccessLogger (const glw::Functions& gl, TestLog& log, deUint32 programGL) |
| : m_gl (gl) |
| , m_log (log) |
| , m_programGL (programGL) |
| { |
| } |
| |
| void assign1i (const string& name, int x); |
| void assign3f (const string& name, float x, float y, float z); |
| |
| private: |
| int getLocation (const string& name); |
| |
| const glw::Functions& m_gl; |
| TestLog& m_log; |
| const deUint32 m_programGL; |
| |
| std::map<string, int> m_uniformLocations; |
| }; |
| |
| int UniformAccessLogger::getLocation (const string& name) |
| { |
| if (m_uniformLocations.find(name) == m_uniformLocations.end()) |
| { |
| const int loc = m_gl.getUniformLocation(m_programGL, name.c_str()); |
| TCU_CHECK(loc != -1); |
| m_uniformLocations[name] = loc; |
| } |
| return m_uniformLocations[name]; |
| } |
| |
| void UniformAccessLogger::assign1i (const string& name, int x) |
| { |
| const int loc = getLocation(name); |
| m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << x << TestLog::EndMessage; |
| m_gl.uniform1i(loc, x); |
| } |
| |
| void UniformAccessLogger::assign3f (const string& name, float x, float y, float z) |
| { |
| const int loc = getLocation(name); |
| m_log << TestLog::Message << "// Assigning to uniform " << name << ": " << Vec3(x, y, z) << TestLog::EndMessage; |
| m_gl.uniform3f(loc, x, y, z); |
| } |
| |
| //! Class containing a (single-level) texture of a given type. Supports accessing pixels with coordinate convention similar to that in imageStore() and imageLoad() in shaders; useful especially for cube maps. |
| class LayeredImage |
| { |
| public: |
| LayeredImage (TextureType type, const TextureFormat& format, int w, int h, int d); |
| |
| TextureType getImageType (void) const { return m_type; } |
| const IVec3& getSize (void) const { return m_size; } |
| const TextureFormat& getFormat (void) const { return m_format; } |
| |
| // \note For cube maps, set/getPixel's z parameter specifies the cube face in the same manner as in imageStore/imageLoad in GL shaders (see glslImageFuncZToCubeFace), instead of directly as a tcu::CubeFace. |
| |
| template <typename ColorT> |
| void setPixel (int x, int y, int z, const ColorT& color) const; |
| |
| Vec4 getPixel (int x, int y, int z) const; |
| IVec4 getPixelInt (int x, int y, int z) const; |
| UVec4 getPixelUint (int x, int y, int z) const { return getPixelInt(x, y, z).asUint(); } |
| |
| PixelBufferAccess getAccess (void) { return getAccessInternal(); } |
| PixelBufferAccess getSliceAccess (int slice) { return getSliceAccessInternal(slice); } |
| PixelBufferAccess getCubeFaceAccess (tcu::CubeFace face) { return getCubeFaceAccessInternal(face); } |
| |
| ConstPixelBufferAccess getAccess (void) const { return getAccessInternal(); } |
| ConstPixelBufferAccess getSliceAccess (int slice) const { return getSliceAccessInternal(slice); } |
| ConstPixelBufferAccess getCubeFaceAccess (tcu::CubeFace face) const { return getCubeFaceAccessInternal(face); } |
| |
| private: |
| LayeredImage (const LayeredImage&); |
| LayeredImage& operator= (const LayeredImage&); |
| |
| // Some helpers to reduce code duplication between const/non-const versions of getAccess and others. |
| PixelBufferAccess getAccessInternal (void) const; |
| PixelBufferAccess getSliceAccessInternal (int slice) const; |
| PixelBufferAccess getCubeFaceAccessInternal (tcu::CubeFace face) const; |
| |
| const TextureType m_type; |
| const IVec3 m_size; |
| const TextureFormat m_format; |
| |
| // \note Depending on m_type, exactly one of the following will contain non-null. |
| const SharedPtr<tcu::Texture1D> m_texBuffer; |
| const SharedPtr<tcu::Texture2D> m_tex2D; |
| const SharedPtr<tcu::TextureCube> m_texCube; |
| const SharedPtr<tcu::Texture3D> m_tex3D; |
| const SharedPtr<tcu::Texture2DArray> m_tex2DArray; |
| }; |
| |
| LayeredImage::LayeredImage (TextureType type, const TextureFormat& format, int w, int h, int d) |
| : m_type (type) |
| , m_size (w, h, d) |
| , m_format (format) |
| , m_texBuffer (type == TEXTURETYPE_BUFFER ? SharedPtr<tcu::Texture1D> (newOneLevelTexture1D (format, w)) : SharedPtr<tcu::Texture1D>()) |
| , m_tex2D (type == TEXTURETYPE_2D ? SharedPtr<tcu::Texture2D> (newOneLevelTexture2D (format, w, h)) : SharedPtr<tcu::Texture2D>()) |
| , m_texCube (type == TEXTURETYPE_CUBE ? SharedPtr<tcu::TextureCube> (newOneLevelTextureCube (format, w)) : SharedPtr<tcu::TextureCube>()) |
| , m_tex3D (type == TEXTURETYPE_3D ? SharedPtr<tcu::Texture3D> (newOneLevelTexture3D (format, w, h, d)) : SharedPtr<tcu::Texture3D>()) |
| , m_tex2DArray (type == TEXTURETYPE_2D_ARRAY ? SharedPtr<tcu::Texture2DArray> (newOneLevelTexture2DArray (format, w, h, d)) : SharedPtr<tcu::Texture2DArray>()) |
| { |
| DE_ASSERT(m_size.z() == 1 || |
| m_type == TEXTURETYPE_3D || |
| m_type == TEXTURETYPE_2D_ARRAY); |
| |
| DE_ASSERT(m_size.y() == 1 || |
| m_type == TEXTURETYPE_2D || |
| m_type == TEXTURETYPE_CUBE || |
| m_type == TEXTURETYPE_3D || |
| m_type == TEXTURETYPE_2D_ARRAY); |
| |
| DE_ASSERT(w == h || type != TEXTURETYPE_CUBE); |
| |
| DE_ASSERT(m_texBuffer != DE_NULL || |
| m_tex2D != DE_NULL || |
| m_texCube != DE_NULL || |
| m_tex3D != DE_NULL || |
| m_tex2DArray != DE_NULL); |
| } |
| |
| template <typename ColorT> |
| void LayeredImage::setPixel (int x, int y, int z, const ColorT& color) const |
| { |
| const PixelBufferAccess access = m_type == TEXTURETYPE_BUFFER ? m_texBuffer->getLevel(0) |
| : m_type == TEXTURETYPE_2D ? m_tex2D->getLevel(0) |
| : m_type == TEXTURETYPE_CUBE ? m_texCube->getLevelFace(0, glslImageFuncZToCubeFace(z)) |
| : m_type == TEXTURETYPE_3D ? m_tex3D->getLevel(0) |
| : m_type == TEXTURETYPE_2D_ARRAY ? m_tex2DArray->getLevel(0) |
| : PixelBufferAccess(); |
| |
| access.setPixel(color, x, y, m_type == TEXTURETYPE_CUBE ? 0 : z); |
| } |
| |
| Vec4 LayeredImage::getPixel (int x, int y, int z) const |
| { |
| const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess(); |
| return access.getPixel(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z); |
| } |
| |
| IVec4 LayeredImage::getPixelInt (int x, int y, int z) const |
| { |
| const ConstPixelBufferAccess access = m_type == TEXTURETYPE_CUBE ? getCubeFaceAccess(glslImageFuncZToCubeFace(z)) : getAccess(); |
| return access.getPixelInt(x, y, m_type == TEXTURETYPE_CUBE ? 0 : z); |
| } |
| |
| PixelBufferAccess LayeredImage::getAccessInternal (void) const |
| { |
| DE_ASSERT(m_type == TEXTURETYPE_BUFFER || m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_3D || m_type == TEXTURETYPE_2D_ARRAY); |
| |
| return m_type == TEXTURETYPE_BUFFER ? m_texBuffer->getLevel(0) |
| : m_type == TEXTURETYPE_2D ? m_tex2D->getLevel(0) |
| : m_type == TEXTURETYPE_3D ? m_tex3D->getLevel(0) |
| : m_type == TEXTURETYPE_2D_ARRAY ? m_tex2DArray->getLevel(0) |
| : PixelBufferAccess(); |
| } |
| |
| PixelBufferAccess LayeredImage::getSliceAccessInternal (int slice) const |
| { |
| const PixelBufferAccess srcAccess = getAccessInternal(); |
| return tcu::getSubregion(srcAccess, 0, 0, slice, srcAccess.getWidth(), srcAccess.getHeight(), 1); |
| } |
| |
| PixelBufferAccess LayeredImage::getCubeFaceAccessInternal (tcu::CubeFace face) const |
| { |
| DE_ASSERT(m_type == TEXTURETYPE_CUBE); |
| return m_texCube->getLevelFace(0, face); |
| } |
| |
| //! Set texture storage or, if using buffer texture, setup buffer and attach to texture. |
| static void setTextureStorage (glu::CallLogWrapper& glLog, TextureType imageType, deUint32 internalFormat, const IVec3& imageSize, deUint32 textureBufGL) |
| { |
| const deUint32 textureTarget = getGLTextureTarget(imageType); |
| |
| switch (imageType) |
| { |
| case TEXTURETYPE_BUFFER: |
| { |
| const TextureFormat format = glu::mapGLInternalFormat(internalFormat); |
| const int numBytes = format.getPixelSize() * imageSize.x(); |
| DE_ASSERT(isFormatSupportedForTextureBuffer(format)); |
| glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL); |
| glLog.glBufferData(GL_TEXTURE_BUFFER, numBytes, DE_NULL, GL_STATIC_DRAW); |
| glLog.glTexBuffer(GL_TEXTURE_BUFFER, internalFormat, textureBufGL); |
| DE_ASSERT(imageSize.y() == 1 && imageSize.z() == 1); |
| break; |
| } |
| |
| // \note Fall-throughs. |
| |
| case TEXTURETYPE_2D: |
| case TEXTURETYPE_CUBE: |
| glLog.glTexStorage2D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y()); |
| DE_ASSERT(imageSize.z() == 1); |
| break; |
| |
| case TEXTURETYPE_3D: |
| case TEXTURETYPE_2D_ARRAY: |
| glLog.glTexStorage3D(textureTarget, 1, internalFormat, imageSize.x(), imageSize.y(), imageSize.z()); |
| break; |
| |
| default: |
| DE_ASSERT(false); |
| } |
| } |
| |
| static void uploadTexture (glu::CallLogWrapper& glLog, const LayeredImage& src, deUint32 textureBufGL) |
| { |
| const deUint32 internalFormat = glu::getInternalFormat(src.getFormat()); |
| const glu::TransferFormat transferFormat = glu::getTransferFormat(src.getFormat()); |
| const IVec3& imageSize = src.getSize(); |
| |
| setTextureStorage(glLog, src.getImageType(), internalFormat, imageSize, textureBufGL); |
| |
| { |
| const int pixelSize = src.getFormat().getPixelSize(); |
| int unpackAlignment; |
| |
| if (deIsPowerOfTwo32(pixelSize)) |
| unpackAlignment = 8; |
| else |
| unpackAlignment = 1; |
| |
| glLog.glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment); |
| } |
| |
| if (src.getImageType() == TEXTURETYPE_BUFFER) |
| { |
| glLog.glBindBuffer(GL_TEXTURE_BUFFER, textureBufGL); |
| glLog.glBufferData(GL_TEXTURE_BUFFER, src.getFormat().getPixelSize() * imageSize.x(), src.getAccess().getDataPtr(), GL_STATIC_DRAW); |
| } |
| else if (src.getImageType() == TEXTURETYPE_2D) |
| glLog.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr()); |
| else if (src.getImageType() == TEXTURETYPE_CUBE) |
| { |
| for (int faceI = 0; faceI < tcu::CUBEFACE_LAST; faceI++) |
| { |
| const tcu::CubeFace face = (tcu::CubeFace)faceI; |
| glLog.glTexSubImage2D(cubeFaceToGLFace(face), 0, 0, 0, imageSize.x(), imageSize.y(), transferFormat.format, transferFormat.dataType, src.getCubeFaceAccess(face).getDataPtr()); |
| } |
| } |
| else |
| { |
| DE_ASSERT(src.getImageType() == TEXTURETYPE_3D || src.getImageType() == TEXTURETYPE_2D_ARRAY); |
| const deUint32 textureTarget = getGLTextureTarget(src.getImageType()); |
| glLog.glTexSubImage3D(textureTarget, 0, 0, 0, 0, imageSize.x(), imageSize.y(), imageSize.z(), transferFormat.format, transferFormat.dataType, src.getAccess().getDataPtr()); |
| } |
| } |
| |
| static void readPixelsRGBAInteger32 (const PixelBufferAccess& dst, int originX, int originY, glu::CallLogWrapper& glLog) |
| { |
| DE_ASSERT(dst.getDepth() == 1); |
| |
| if (isFormatTypeUnsignedInteger(dst.getFormat().type)) |
| { |
| vector<UVec4> data(dst.getWidth()*dst.getHeight()); |
| |
| glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_UNSIGNED_INT, &data[0]); |
| |
| for (int y = 0; y < dst.getHeight(); y++) |
| for (int x = 0; x < dst.getWidth(); x++) |
| dst.setPixel(data[y*dst.getWidth() + x], x, y); |
| } |
| else if (isFormatTypeSignedInteger(dst.getFormat().type)) |
| { |
| vector<IVec4> data(dst.getWidth()*dst.getHeight()); |
| |
| glLog.glReadPixels(originX, originY, dst.getWidth(), dst.getHeight(), GL_RGBA_INTEGER, GL_INT, &data[0]); |
| |
| for (int y = 0; y < dst.getHeight(); y++) |
| for (int x = 0; x < dst.getWidth(); x++) |
| dst.setPixel(data[y*dst.getWidth() + x], x, y); |
| } |
| else |
| DE_ASSERT(false); |
| } |
| |
| //! Base for a functor for verifying and logging a 2d texture layer (2d image, cube face, 3d slice, 2d layer). |
| class ImageLayerVerifier |
| { |
| public: |
| virtual bool operator() (TestLog&, const ConstPixelBufferAccess&, int sliceOrFaceNdx) const = 0; |
| virtual ~ImageLayerVerifier (void) {} |
| }; |
| |
| static void setTexParameteri (glu::CallLogWrapper& glLog, deUint32 target) |
| { |
| if (target != GL_TEXTURE_BUFFER) |
| { |
| glLog.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glLog.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| } |
| } |
| |
| //! Binds texture (one layer at a time) to color attachment of FBO and does glReadPixels(). Calls the verifier for each layer. |
| //! \note Not for buffer textures. |
| static bool readIntegerTextureViaFBOAndVerify (const RenderContext& renderCtx, |
| glu::CallLogWrapper& glLog, |
| deUint32 textureGL, |
| TextureType textureType, |
| const TextureFormat& textureFormat, |
| const IVec3& textureSize, |
| const ImageLayerVerifier& verifyLayer) |
| { |
| DE_ASSERT(isFormatTypeInteger(textureFormat.type)); |
| DE_ASSERT(textureType != TEXTURETYPE_BUFFER); |
| |
| TestLog& log = glLog.getLog(); |
| |
| const tcu::ScopedLogSection section(log, "Verification", "Result verification (bind texture layer-by-layer to FBO, read with glReadPixels())"); |
| |
| const int numSlicesOrFaces = textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z(); |
| const deUint32 textureTargetGL = getGLTextureTarget(textureType); |
| glu::Framebuffer fbo (renderCtx); |
| tcu::TextureLevel resultSlice (textureFormat, textureSize.x(), textureSize.y()); |
| |
| glLog.glBindFramebuffer(GL_FRAMEBUFFER, *fbo); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind FBO"); |
| |
| glLog.glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glMemoryBarrier"); |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, textureGL); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++) |
| { |
| if (textureType == TEXTURETYPE_CUBE) |
| glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)), textureGL, 0); |
| else if (textureType == TEXTURETYPE_2D) |
| glLog.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureGL, 0); |
| else if (textureType == TEXTURETYPE_3D || textureType == TEXTURETYPE_2D_ARRAY) |
| glLog.glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureGL, 0, sliceOrFaceNdx); |
| else |
| DE_ASSERT(false); |
| |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "Bind texture to framebuffer color attachment 0"); |
| |
| TCU_CHECK(glLog.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); |
| |
| readPixelsRGBAInteger32(resultSlice.getAccess(), 0, 0, glLog); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glReadPixels"); |
| |
| if (!verifyLayer(log, resultSlice, sliceOrFaceNdx)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| //! Reads texture with texture() in compute shader, one layer at a time, putting values into a SSBO and reading with a mapping. Calls the verifier for each layer. |
| //! \note Not for buffer textures. |
| static bool readFloatOrNormTextureWithLookupsAndVerify (const RenderContext& renderCtx, |
| glu::CallLogWrapper& glLog, |
| deUint32 textureGL, |
| TextureType textureType, |
| const TextureFormat& textureFormat, |
| const IVec3& textureSize, |
| const ImageLayerVerifier& verifyLayer) |
| { |
| DE_ASSERT(!isFormatTypeInteger(textureFormat.type)); |
| DE_ASSERT(textureType != TEXTURETYPE_BUFFER); |
| |
| TestLog& log = glLog.getLog(); |
| |
| const tcu::ScopedLogSection section(log, "Verification", "Result verification (read texture layer-by-layer in compute shader with texture() into SSBO)"); |
| const std::string glslVersionDeclaration = getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType())); |
| |
| const glu::ShaderProgram program(renderCtx, |
| glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "layout (binding = 0) buffer Output\n" |
| "{\n" |
| " vec4 color[" + toString(textureSize.x()*textureSize.y()) + "];\n" |
| "} sb_out;\n" |
| "\n" |
| "precision highp " + getShaderSamplerType(textureFormat.type, textureType) + ";\n" |
| "\n" |
| "uniform highp " + getShaderSamplerType(textureFormat.type, textureType) + " u_texture;\n" |
| "uniform highp vec3 u_texCoordLD;\n" |
| "uniform highp vec3 u_texCoordRD;\n" |
| "uniform highp vec3 u_texCoordLU;\n" |
| "uniform highp vec3 u_texCoordRU;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " int gx = int(gl_GlobalInvocationID.x);\n" |
| " int gy = int(gl_GlobalInvocationID.y);\n" |
| " highp float s = (float(gx) + 0.5) / float(" + toString(textureSize.x()) + ");\n" |
| " highp float t = (float(gy) + 0.5) / float(" + toString(textureType == TEXTURETYPE_CUBE ? textureSize.x() : textureSize.y()) + ");\n" |
| " highp vec3 texCoord = u_texCoordLD*(1.0-s)*(1.0-t)\n" |
| " + u_texCoordRD*( s)*(1.0-t)\n" |
| " + u_texCoordLU*(1.0-s)*( t)\n" |
| " + u_texCoordRU*( s)*( t);\n" |
| " int ndx = gy*" + toString(textureSize.x()) + " + gx;\n" |
| " sb_out.color[ndx] = texture(u_texture, texCoord" + (textureType == TEXTURETYPE_2D ? ".xy" : "") + ");\n" |
| "}\n")); |
| |
| glLog.glUseProgram(program.getProgram()); |
| |
| log << program; |
| |
| if (!program.isOk()) |
| { |
| log << TestLog::Message << "// Failure: failed to compile program" << TestLog::EndMessage; |
| TCU_FAIL("Program compilation failed"); |
| } |
| |
| { |
| const deUint32 textureTargetGL = getGLTextureTarget(textureType); |
| const glu::Buffer outputBuffer (renderCtx); |
| UniformAccessLogger uniforms (renderCtx.getFunctions(), log, program.getProgram()); |
| |
| // Setup texture. |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, textureGL); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| uniforms.assign1i("u_texture", 0); |
| |
| // Setup output buffer. |
| { |
| const deUint32 blockIndex = glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output"); |
| const int blockSize = glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE); |
| |
| log << TestLog::Message << "// Got buffer data size = " << blockSize << TestLog::EndMessage; |
| TCU_CHECK(blockSize > 0); |
| |
| glLog.glBindBuffer(GL_SHADER_STORAGE_BUFFER, *outputBuffer); |
| glLog.glBufferData(GL_SHADER_STORAGE_BUFFER, blockSize, DE_NULL, GL_STREAM_READ); |
| glLog.glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, *outputBuffer); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "SSB setup failed"); |
| } |
| |
| // Dispatch one layer at a time, read back and verify. |
| { |
| const int numSlicesOrFaces = textureType == TEXTURETYPE_CUBE ? 6 : textureSize.z(); |
| tcu::TextureLevel resultSlice (textureFormat, textureSize.x(), textureSize.y()); |
| const PixelBufferAccess resultSliceAccess = resultSlice.getAccess(); |
| const deUint32 blockIndex = glLog.glGetProgramResourceIndex(program.getProgram(), GL_SHADER_STORAGE_BLOCK, "Output"); |
| const int blockSize = glu::getProgramResourceInt(renderCtx.getFunctions(), program.getProgram(), GL_SHADER_STORAGE_BLOCK, blockIndex, GL_BUFFER_DATA_SIZE); |
| const deUint32 valueIndex = glLog.glGetProgramResourceIndex(program.getProgram(), GL_BUFFER_VARIABLE, "Output.color"); |
| const glu::InterfaceVariableInfo valueInfo = glu::getProgramInterfaceVariableInfo(renderCtx.getFunctions(), program.getProgram(), GL_BUFFER_VARIABLE, valueIndex); |
| |
| TCU_CHECK(valueInfo.arraySize == (deUint32)(textureSize.x()*textureSize.y())); |
| |
| glLog.glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT); |
| |
| for (int sliceOrFaceNdx = 0; sliceOrFaceNdx < numSlicesOrFaces; sliceOrFaceNdx++) |
| { |
| if (textureType == TEXTURETYPE_CUBE) |
| { |
| vector<float> coords; |
| computeQuadTexCoordCube(coords, glslImageFuncZToCubeFace(sliceOrFaceNdx)); |
| uniforms.assign3f("u_texCoordLD", coords[3*0 + 0], coords[3*0 + 1], coords[3*0 + 2]); |
| uniforms.assign3f("u_texCoordRD", coords[3*2 + 0], coords[3*2 + 1], coords[3*2 + 2]); |
| uniforms.assign3f("u_texCoordLU", coords[3*1 + 0], coords[3*1 + 1], coords[3*1 + 2]); |
| uniforms.assign3f("u_texCoordRU", coords[3*3 + 0], coords[3*3 + 1], coords[3*3 + 2]); |
| } |
| else |
| { |
| const float z = textureType == TEXTURETYPE_3D ? |
| ((float)sliceOrFaceNdx + 0.5f) / (float)numSlicesOrFaces : |
| (float)sliceOrFaceNdx; |
| uniforms.assign3f("u_texCoordLD", 0.0f, 0.0f, z); |
| uniforms.assign3f("u_texCoordRD", 1.0f, 0.0f, z); |
| uniforms.assign3f("u_texCoordLU", 0.0f, 1.0f, z); |
| uniforms.assign3f("u_texCoordRU", 1.0f, 1.0f, z); |
| } |
| |
| glLog.glDispatchCompute(textureSize.x(), textureSize.y(), 1); |
| |
| { |
| log << TestLog::Message << "// Note: mapping buffer and reading color values written" << TestLog::EndMessage; |
| |
| const BufferMemMap bufMap(renderCtx.getFunctions(), GL_SHADER_STORAGE_BUFFER, 0, blockSize, GL_MAP_READ_BIT); |
| |
| for (int y = 0; y < textureSize.y(); y++) |
| for (int x = 0; x < textureSize.x(); x++) |
| { |
| const int ndx = y*textureSize.x() + x; |
| const float* const clrData = (const float*)((const deUint8*)bufMap.getPtr() + valueInfo.offset + valueInfo.arrayStride*ndx); |
| |
| switch (textureFormat.order) |
| { |
| case TextureFormat::R: resultSliceAccess.setPixel(Vec4(clrData[0]), x, y); break; |
| case TextureFormat::RGBA: resultSliceAccess.setPixel(Vec4(clrData[0], clrData[1], clrData[2], clrData[3]), x, y); break; |
| default: |
| DE_ASSERT(false); |
| } |
| } |
| } |
| |
| if (!verifyLayer(log, resultSliceAccess, sliceOrFaceNdx)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| } |
| |
| //! Read buffer texture by reading the corresponding buffer with a mapping. |
| static bool readBufferTextureWithMappingAndVerify (const RenderContext& renderCtx, |
| glu::CallLogWrapper& glLog, |
| deUint32 bufferGL, |
| const TextureFormat& textureFormat, |
| int imageSize, |
| const ImageLayerVerifier& verifyLayer) |
| { |
| tcu::TextureLevel result (textureFormat, imageSize, 1); |
| const PixelBufferAccess resultAccess = result.getAccess(); |
| const int dataSize = imageSize * textureFormat.getPixelSize(); |
| |
| const tcu::ScopedLogSection section(glLog.getLog(), "Verification", "Result verification (read texture's buffer with a mapping)"); |
| glLog.glBindBuffer(GL_TEXTURE_BUFFER, bufferGL); |
| |
| { |
| const BufferMemMap bufMap(renderCtx.getFunctions(), GL_TEXTURE_BUFFER, 0, dataSize, GL_MAP_READ_BIT); |
| deMemcpy(resultAccess.getDataPtr(), bufMap.getPtr(), dataSize); |
| } |
| |
| return verifyLayer(glLog.getLog(), resultAccess, 0); |
| } |
| |
| //! Calls the appropriate texture verification function depending on texture format or type. |
| static bool readTextureAndVerify (const RenderContext& renderCtx, |
| glu::CallLogWrapper& glLog, |
| deUint32 textureGL, |
| deUint32 bufferGL, |
| TextureType textureType, |
| const TextureFormat& textureFormat, |
| const IVec3& imageSize, |
| const ImageLayerVerifier& verifyLayer) |
| { |
| if (textureType == TEXTURETYPE_BUFFER) |
| return readBufferTextureWithMappingAndVerify(renderCtx, glLog, bufferGL, textureFormat, imageSize.x(), verifyLayer); |
| else |
| return isFormatTypeInteger(textureFormat.type) ? readIntegerTextureViaFBOAndVerify (renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer) |
| : readFloatOrNormTextureWithLookupsAndVerify (renderCtx, glLog, textureGL, textureType, textureFormat, imageSize, verifyLayer); |
| } |
| |
| //! An ImageLayerVerifier that simply compares the result slice to a slice in a reference image. |
| //! \note Holds the reference image as a reference (no pun intended) instead of a copy; caller must be aware of lifetime issues. |
| class ImageLayerComparer : public ImageLayerVerifier |
| { |
| public: |
| ImageLayerComparer (const LayeredImage& reference, |
| const IVec2& relevantRegion = IVec2(0) /* If given, only check this region of each slice. */) |
| : m_reference (reference) |
| , m_relevantRegion (relevantRegion.x() > 0 && relevantRegion.y() > 0 ? relevantRegion : reference.getSize().swizzle(0, 1)) |
| { |
| } |
| |
| bool operator() (TestLog& log, const tcu::ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const |
| { |
| const bool isCube = m_reference.getImageType() == TEXTURETYPE_CUBE; |
| const ConstPixelBufferAccess referenceSlice = tcu::getSubregion(isCube ? m_reference.getCubeFaceAccess(glslImageFuncZToCubeFace(sliceOrFaceNdx)) |
| : m_reference.getSliceAccess(sliceOrFaceNdx), |
| 0, 0, m_relevantRegion.x(), m_relevantRegion.y()); |
| |
| const string comparisonName = "Comparison" + toString(sliceOrFaceNdx); |
| const string comparisonDesc = "Image Comparison, " |
| + (isCube ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)))) |
| : "slice " + toString(sliceOrFaceNdx)); |
| |
| if (isFormatTypeInteger(m_reference.getFormat().type)) |
| return tcu::intThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, UVec4(0), tcu::COMPARE_LOG_RESULT); |
| else |
| return tcu::floatThresholdCompare(log, comparisonName.c_str(), comparisonDesc.c_str(), referenceSlice, resultSlice, Vec4(0.01f), tcu::COMPARE_LOG_RESULT); |
| } |
| |
| private: |
| const LayeredImage& m_reference; |
| const IVec2 m_relevantRegion; |
| }; |
| |
| //! Case that just stores some computation results into an image. |
| class ImageStoreCase : public TestCase |
| { |
| public: |
| enum CaseFlag |
| { |
| CASEFLAG_SINGLE_LAYER_BIND = 1 << 0 //!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched. |
| }; |
| |
| ImageStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0) |
| : TestCase (context, name, description) |
| , m_format (format) |
| , m_textureType (textureType) |
| , m_singleLayerBind ((caseFlags & CASEFLAG_SINGLE_LAYER_BIND) != 0) |
| { |
| } |
| |
| void init (void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType, m_context.getRenderContext()); } |
| IterateResult iterate (void); |
| |
| private: |
| const TextureFormat m_format; |
| const TextureType m_textureType; |
| const bool m_singleLayerBind; |
| }; |
| |
| ImageStoreCase::IterateResult ImageStoreCase::iterate (void) |
| { |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| TestLog& log (m_testCtx.getLog()); |
| glu::CallLogWrapper glLog (renderCtx.getFunctions(), log); |
| const deUint32 internalFormatGL = glu::getInternalFormat(m_format); |
| const deUint32 textureTargetGL = getGLTextureTarget(m_textureType); |
| const IVec3& imageSize = defaultImageSize(m_textureType); |
| const int numSlicesOrFaces = m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z(); |
| const int maxImageDimension = de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z())); |
| const float storeColorScale = isFormatTypeUnorm(m_format.type) ? 1.0f / (float)(maxImageDimension - 1) |
| : isFormatTypeSnorm(m_format.type) ? 2.0f / (float)(maxImageDimension - 1) |
| : 1.0f; |
| const float storeColorBias = isFormatTypeSnorm(m_format.type) ? -1.0f : 0.0f; |
| const glu::Buffer textureBuf (renderCtx); // \note Only really used if using buffer texture. |
| const glu::Texture texture (renderCtx); |
| |
| glLog.enableLogging(true); |
| |
| // Setup texture. |
| |
| log << TestLog::Message << "// Created a texture (name " << *texture << ")" << TestLog::EndMessage; |
| if (m_textureType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created a buffer for the texture (name " << *textureBuf << ")" << TestLog::EndMessage; |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, *texture); |
| setTexParameteri(glLog, textureTargetGL); |
| setTextureStorage(glLog, m_textureType, internalFormatGL, imageSize, *textureBuf); |
| |
| // Perform image stores in compute shader. |
| |
| { |
| // Generate compute shader. |
| |
| const string shaderImageFormatStr = getShaderImageFormatQualifier(m_format); |
| const TextureType shaderImageType = m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType; |
| const string shaderImageTypeStr = getShaderImageType(m_format.type, shaderImageType); |
| const bool isUintFormat = isFormatTypeUnsignedInteger(m_format.type); |
| const bool isIntFormat = isFormatTypeSignedInteger(m_format.type); |
| const string colorBaseExpr = string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4(gx^gy^gz, " |
| "(" + toString(imageSize.x()-1) + "-gx)^gy^gz, " |
| "gx^(" + toString(imageSize.y()-1) + "-gy)^gz, " |
| "(" + toString(imageSize.x()-1) + "-gx)^(" + toString(imageSize.y()-1) + "-gy)^gz)"; |
| const string colorExpr = colorBaseExpr + (storeColorScale == 1.0f ? "" : "*" + toString(storeColorScale)) |
| + (storeColorBias == 0.0f ? "" : " + float(" + toString(storeColorBias) + ")"); |
| const std::string glslVersionDeclaration = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType())); |
| |
| const glu::ShaderProgram program(renderCtx, |
| glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n" |
| + textureTypeExtensionShaderRequires(shaderImageType, renderCtx) + |
| "\n" |
| "precision highp " + shaderImageTypeStr + ";\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "layout (" + shaderImageFormatStr + ", binding=0) writeonly uniform " + shaderImageTypeStr + " u_image;\n" |
| + (m_singleLayerBind ? "uniform int u_layerNdx;\n" : "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " int gx = int(gl_GlobalInvocationID.x);\n" |
| " int gy = int(gl_GlobalInvocationID.y);\n" |
| " int gz = " + (m_singleLayerBind ? "u_layerNdx" : "int(gl_GlobalInvocationID.z)") + ";\n" |
| + (shaderImageType == TEXTURETYPE_BUFFER ? |
| " imageStore(u_image, gx, " + colorExpr + ");\n" |
| : shaderImageType == TEXTURETYPE_2D ? |
| " imageStore(u_image, ivec2(gx, gy), " + colorExpr + ");\n" |
| : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ? |
| " imageStore(u_image, ivec3(gx, gy, gz), " + colorExpr + ");\n" |
| : deFatalStr("Invalid TextureType")) + |
| "}\n")); |
| |
| UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram()); |
| |
| log << program; |
| |
| if (!program.isOk()) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed"); |
| return STOP; |
| } |
| |
| // Setup and dispatch. |
| |
| glLog.glUseProgram(program.getProgram()); |
| |
| if (m_singleLayerBind) |
| { |
| for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++) |
| { |
| if (layerNdx > 0) |
| glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| |
| uniforms.assign1i("u_layerNdx", layerNdx); |
| |
| glLog.glBindImageTexture(0, *texture, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| } |
| else |
| { |
| glLog.glBindImageTexture(0, *texture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| } |
| |
| // Create reference, read texture and compare to reference. |
| { |
| const int isIntegerFormat = isFormatTypeInteger(m_format.type); |
| LayeredImage reference (m_textureType, m_format, imageSize.x(), imageSize.y(), imageSize.z()); |
| |
| DE_ASSERT(!isIntegerFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f)); |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| { |
| const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z); |
| |
| if (isIntegerFormat) |
| reference.setPixel(x, y, z, color); |
| else |
| reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias); |
| } |
| |
| const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture, *textureBuf, m_textureType, m_format, imageSize, ImageLayerComparer(reference)); |
| |
| m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed"); |
| return STOP; |
| } |
| } |
| |
| //! Case that copies an image to another, using imageLoad() and imageStore(). Texture formats don't necessarily match image formats. |
| class ImageLoadAndStoreCase : public TestCase |
| { |
| public: |
| enum CaseFlag |
| { |
| CASEFLAG_SINGLE_LAYER_BIND = 1 << 0, //!< If given, glBindImageTexture() is called with GL_FALSE <layered> argument, and for each layer the compute shader is separately dispatched. |
| CASEFLAG_RESTRICT_IMAGES = 1 << 1 //!< If given, images in shader will be qualified with "restrict". |
| }; |
| |
| ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType textureType, deUint32 caseFlags = 0) |
| : TestCase (context, name, description) |
| , m_textureFormat (format) |
| , m_imageFormat (format) |
| , m_textureType (textureType) |
| , m_restrictImages ((caseFlags & CASEFLAG_RESTRICT_IMAGES) != 0) |
| , m_singleLayerBind ((caseFlags & CASEFLAG_SINGLE_LAYER_BIND) != 0) |
| { |
| } |
| |
| ImageLoadAndStoreCase (Context& context, const char* name, const char* description, const TextureFormat& textureFormat, const TextureFormat& imageFormat, TextureType textureType, deUint32 caseFlags = 0) |
| : TestCase (context, name, description) |
| , m_textureFormat (textureFormat) |
| , m_imageFormat (imageFormat) |
| , m_textureType (textureType) |
| , m_restrictImages ((caseFlags & CASEFLAG_RESTRICT_IMAGES) != 0) |
| , m_singleLayerBind ((caseFlags & CASEFLAG_SINGLE_LAYER_BIND) != 0) |
| { |
| DE_ASSERT(textureFormat.getPixelSize() == imageFormat.getPixelSize()); |
| } |
| |
| void init (void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_textureType, m_context.getRenderContext()); } |
| IterateResult iterate (void); |
| |
| private: |
| template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatStorageType> |
| static void replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat); |
| |
| const TextureFormat m_textureFormat; |
| const TextureFormat m_imageFormat; |
| const TextureType m_textureType; |
| const bool m_restrictImages; |
| const bool m_singleLayerBind; |
| }; |
| |
| template <TextureFormat::ChannelType ImageFormatType, typename TcuFloatType, typename TcuFloatTypeStorageType> |
| void ImageLoadAndStoreCase::replaceBadFloatReinterpretValues (LayeredImage& image, const TextureFormat& imageFormat) |
| { |
| // Find potential bad values, such as nan or inf, and replace with something else. |
| const int pixelSize = imageFormat.getPixelSize(); |
| const int imageNumChannels = imageFormat.order == tcu::TextureFormat::R ? 1 |
| : imageFormat.order == tcu::TextureFormat::RGBA ? 4 |
| : 0; |
| const IVec3 imageSize = image.getSize(); |
| const int numSlicesOrFaces = image.getImageType() == TEXTURETYPE_CUBE ? 6 : imageSize.z(); |
| |
| DE_ASSERT(pixelSize % imageNumChannels == 0); |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| { |
| const PixelBufferAccess sliceAccess = image.getImageType() == TEXTURETYPE_CUBE ? image.getCubeFaceAccess((tcu::CubeFace)z) : image.getSliceAccess(z); |
| const int rowPitch = sliceAccess.getRowPitch(); |
| void *const data = sliceAccess.getDataPtr(); |
| |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| { |
| void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize; |
| |
| for (int c = 0; c < imageNumChannels; c++) |
| { |
| void *const channelData = (deUint8*)pixelData + c*pixelSize/imageNumChannels; |
| const TcuFloatType f (*(TcuFloatTypeStorageType*)channelData); |
| |
| if (f.isDenorm() || f.isInf() || f.isNaN()) |
| *(TcuFloatTypeStorageType*)channelData = TcuFloatType(0.0f).bits(); |
| } |
| } |
| } |
| } |
| |
| ImageLoadAndStoreCase::IterateResult ImageLoadAndStoreCase::iterate (void) |
| { |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| TestLog& log (m_testCtx.getLog()); |
| glu::CallLogWrapper glLog (renderCtx.getFunctions(), log); |
| const deUint32 textureInternalFormatGL = glu::getInternalFormat(m_textureFormat); |
| const deUint32 imageInternalFormatGL = glu::getInternalFormat(m_imageFormat); |
| const deUint32 textureTargetGL = getGLTextureTarget(m_textureType); |
| const IVec3& imageSize = defaultImageSize(m_textureType); |
| const int maxImageDimension = de::max(imageSize.x(), de::max(imageSize.y(), imageSize.z())); |
| const float storeColorScale = isFormatTypeUnorm(m_textureFormat.type) ? 1.0f / (float)(maxImageDimension - 1) |
| : isFormatTypeSnorm(m_textureFormat.type) ? 2.0f / (float)(maxImageDimension - 1) |
| : 1.0f; |
| const float storeColorBias = isFormatTypeSnorm(m_textureFormat.type) ? -1.0f : 0.0f; |
| const int numSlicesOrFaces = m_textureType == TEXTURETYPE_CUBE ? 6 : imageSize.z(); |
| const bool isIntegerTextureFormat = isFormatTypeInteger(m_textureFormat.type); |
| const glu::Buffer texture0Buf (renderCtx); |
| const glu::Buffer texture1Buf (renderCtx); |
| const glu::Texture texture0 (renderCtx); |
| const glu::Texture texture1 (renderCtx); |
| LayeredImage reference (m_textureType, m_textureFormat, imageSize.x(), imageSize.y(), imageSize.z()); |
| |
| glLog.enableLogging(true); |
| |
| // Setup textures. |
| |
| log << TestLog::Message << "// Created 2 textures (names " << *texture0 << " and " << *texture1 << ")" << TestLog::EndMessage; |
| if (m_textureType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created buffers for the textures (names " << *texture0Buf << " and " << *texture1Buf << ")" << TestLog::EndMessage; |
| |
| // First, fill reference with (a fairly arbitrary) initial pattern. This will be used as texture upload source data as well as for actual reference computation later on. |
| |
| DE_ASSERT(!isIntegerTextureFormat || (storeColorScale == 1.0f && storeColorBias == 0.0f)); |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| { |
| const IVec4 color(x^y^z, (imageSize.x()-1-x)^y^z, x^(imageSize.y()-1-y)^z, (imageSize.x()-1-x)^(imageSize.y()-1-y)^z); |
| |
| if (isIntegerTextureFormat) |
| reference.setPixel(x, y, z, color); |
| else |
| reference.setPixel(x, y, z, color.asFloat()*storeColorScale + storeColorBias); |
| } |
| |
| // If re-interpreting the texture contents as floating point values, need to get rid of inf, nan etc. |
| if (m_imageFormat.type == TextureFormat::HALF_FLOAT && m_textureFormat.type != TextureFormat::HALF_FLOAT) |
| replaceBadFloatReinterpretValues<TextureFormat::HALF_FLOAT, tcu::Float16, deUint16>(reference, m_imageFormat); |
| else if (m_imageFormat.type == TextureFormat::FLOAT && m_textureFormat.type != TextureFormat::FLOAT) |
| replaceBadFloatReinterpretValues<TextureFormat::FLOAT, tcu::Float32, deUint32>(reference, m_imageFormat); |
| |
| // Upload initial pattern to texture 0. |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, *texture0); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| log << TestLog::Message << "// Filling texture " << *texture0 << " with xor pattern" << TestLog::EndMessage; |
| |
| uploadTexture(glLog, reference, *texture0Buf); |
| |
| // Set storage for texture 1. |
| |
| glLog.glActiveTexture(GL_TEXTURE1); |
| glLog.glBindTexture(textureTargetGL, *texture1); |
| setTexParameteri(glLog, textureTargetGL); |
| setTextureStorage(glLog, m_textureType, textureInternalFormatGL, imageSize, *texture1Buf); |
| |
| // Perform image loads and stores in compute shader and finalize reference computation. |
| |
| { |
| // Generate compute shader. |
| |
| const char* const maybeRestrict = m_restrictImages ? "restrict" : ""; |
| const string shaderImageFormatStr = getShaderImageFormatQualifier(m_imageFormat); |
| const TextureType shaderImageType = m_singleLayerBind ? textureLayerType(m_textureType) : m_textureType; |
| const string shaderImageTypeStr = getShaderImageType(m_imageFormat.type, shaderImageType); |
| const std::string glslVersionDeclaration = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType())); |
| |
| const glu::ShaderProgram program(renderCtx, |
| glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n" |
| + textureTypeExtensionShaderRequires(shaderImageType, renderCtx) + |
| "\n" |
| "precision highp " + shaderImageTypeStr + ";\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "layout (" + shaderImageFormatStr + ", binding=0) " + maybeRestrict + " readonly uniform " + shaderImageTypeStr + " u_image0;\n" |
| "layout (" + shaderImageFormatStr + ", binding=1) " + maybeRestrict + " writeonly uniform " + shaderImageTypeStr + " u_image1;\n" |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| + (shaderImageType == TEXTURETYPE_BUFFER ? |
| " int pos = int(gl_GlobalInvocationID.x);\n" |
| " imageStore(u_image1, pos, imageLoad(u_image0, " + toString(imageSize.x()-1) + "-pos));\n" |
| : shaderImageType == TEXTURETYPE_2D ? |
| " ivec2 pos = ivec2(gl_GlobalInvocationID.xy);\n" |
| " imageStore(u_image1, pos, imageLoad(u_image0, ivec2(" + toString(imageSize.x()-1) + "-pos.x, pos.y)));\n" |
| : shaderImageType == TEXTURETYPE_3D || shaderImageType == TEXTURETYPE_CUBE || shaderImageType == TEXTURETYPE_2D_ARRAY ? |
| " ivec3 pos = ivec3(gl_GlobalInvocationID);\n" |
| " imageStore(u_image1, pos, imageLoad(u_image0, ivec3(" + toString(imageSize.x()-1) + "-pos.x, pos.y, pos.z)));\n" |
| : deFatalStr("Invalid TextureType")) + |
| "}\n")); |
| |
| UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram()); |
| |
| log << program; |
| |
| if (!program.isOk()) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed"); |
| return STOP; |
| } |
| |
| // Setup and dispatch. |
| |
| glLog.glUseProgram(program.getProgram()); |
| |
| if (m_singleLayerBind) |
| { |
| for (int layerNdx = 0; layerNdx < numSlicesOrFaces; layerNdx++) |
| { |
| if (layerNdx > 0) |
| glLog.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); |
| |
| glLog.glBindImageTexture(0, *texture0, 0, GL_FALSE, layerNdx, GL_READ_ONLY, imageInternalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glBindImageTexture(1, *texture1, 0, GL_FALSE, layerNdx, GL_WRITE_ONLY, imageInternalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glDispatchCompute(imageSize.x(), imageSize.y(), 1); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| } |
| else |
| { |
| glLog.glBindImageTexture(0, *texture0, 0, GL_TRUE, 0, GL_READ_ONLY, imageInternalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glBindImageTexture(1, *texture1, 0, GL_TRUE, 0, GL_WRITE_ONLY, imageInternalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| glLog.glDispatchCompute(imageSize.x(), imageSize.y(), numSlicesOrFaces); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| |
| // Finalize reference. |
| |
| if (m_textureFormat != m_imageFormat) |
| { |
| // Format re-interpretation case. Read data with image format and write back, with the same image format. |
| // We do this because the data may change a little during lookups (e.g. unorm8 -> float; not all unorms can be exactly represented as floats). |
| |
| const int pixelSize = m_imageFormat.getPixelSize(); |
| tcu::TextureLevel scratch (m_imageFormat, 1, 1); |
| const PixelBufferAccess scratchAccess = scratch.getAccess(); |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| { |
| const PixelBufferAccess sliceAccess = m_textureType == TEXTURETYPE_CUBE ? reference.getCubeFaceAccess((tcu::CubeFace)z) : reference.getSliceAccess(z); |
| const int rowPitch = sliceAccess.getRowPitch(); |
| void *const data = sliceAccess.getDataPtr(); |
| |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| { |
| void *const pixelData = (deUint8*)data + y*rowPitch + x*pixelSize; |
| |
| deMemcpy(scratchAccess.getDataPtr(), pixelData, pixelSize); |
| |
| if (isFormatTypeInteger(m_imageFormat.type)) |
| scratchAccess.setPixel(scratchAccess.getPixelUint(0, 0), 0, 0); |
| else |
| scratchAccess.setPixel(scratchAccess.getPixel(0, 0), 0, 0); |
| |
| deMemcpy(pixelData, scratchAccess.getDataPtr(), pixelSize); |
| } |
| } |
| } |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x()/2; x++) |
| { |
| if (isIntegerTextureFormat) |
| { |
| const UVec4 temp = reference.getPixelUint(imageSize.x()-1-x, y, z); |
| reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixelUint(x, y, z)); |
| reference.setPixel(x, y, z, temp); |
| } |
| else |
| { |
| const Vec4 temp = reference.getPixel(imageSize.x()-1-x, y, z); |
| reference.setPixel(imageSize.x()-1-x, y, z, reference.getPixel(x, y, z)); |
| reference.setPixel(x, y, z, temp); |
| } |
| } |
| } |
| |
| // Read texture 1 and compare to reference. |
| |
| const bool compareOk = readTextureAndVerify(renderCtx, glLog, *texture1, *texture1Buf, m_textureType, m_textureFormat, imageSize, ImageLayerComparer(reference)); |
| |
| m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, compareOk ? "Pass" : "Image comparison failed"); |
| return STOP; |
| } |
| |
| enum AtomicOperationCaseType |
| { |
| ATOMIC_OPERATION_CASE_TYPE_END_RESULT = 0, //!< Atomic case checks the end result of the operations, and not the return values. |
| ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES, //!< Atomic case checks the return values of the atomic function, and not the end result. |
| |
| ATOMIC_OPERATION_CASE_TYPE_LAST |
| }; |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Binary atomic operation case. |
| * |
| * Case that performs binary atomic operations (i.e. any but compSwap) and |
| * verifies according to the given AtomicOperationCaseType. |
| * |
| * For the "end result" case type, a single texture (and image) is created, |
| * upon which the atomic operations operate. A compute shader is dispatched |
| * with dimensions equal to the image size, except with a bigger X size |
| * so that every pixel is operated on by multiple invocations. The end |
| * results are verified in BinaryAtomicOperationCase::EndResultVerifier. |
| * The return values of the atomic function calls are ignored. |
| * |
| * For the "return value" case type, the case does much the same operations |
| * as in the "end result" case, but also creates an additional texture, |
| * of size equal to the dispatch size, into which the return values of the |
| * atomic functions are stored (with imageStore()). The return values are |
| * verified in BinaryAtomicOperationCase::ReturnValueVerifier. |
| * The end result values are not checked. |
| * |
| * The compute shader invocations contributing to a pixel (X, Y, Z) in the |
| * end result image are the invocations with global IDs |
| * (X, Y, Z), (X+W, Y, Z), (X+2*W, Y, Z), ..., (X+(N-1)*W, Y, W), where W |
| * is the width of the end result image and N is |
| * BinaryAtomicOperationCase::NUM_INVOCATIONS_PER_PIXEL. |
| *//*--------------------------------------------------------------------*/ |
| class BinaryAtomicOperationCase : public TestCase |
| { |
| public: |
| BinaryAtomicOperationCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperation operation, AtomicOperationCaseType caseType) |
| : TestCase (context, name, description) |
| , m_format (format) |
| , m_imageType (imageType) |
| , m_operation (operation) |
| , m_caseType (caseType) |
| { |
| DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32) || |
| m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32) || |
| (m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT) && m_operation == ATOMIC_OPERATION_EXCHANGE)); |
| |
| DE_ASSERT(m_operation != ATOMIC_OPERATION_COMP_SWAP); |
| } |
| |
| void init (void); |
| IterateResult iterate (void); |
| |
| private: |
| class EndResultVerifier; |
| class ReturnValueVerifier; |
| |
| static int getOperationInitialValue (AtomicOperation op); //!< Appropriate value with which to initialize the texture. |
| //! Compute the argument given to the atomic function at the given invocation ID, when the entire dispatch has the given width and height. |
| static int getAtomicFuncArgument (AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY); |
| //! Generate the shader expression for the argument given to the atomic function. x, y and z are the identifiers for the invocation ID components. |
| static string getAtomicFuncArgumentShaderStr (AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY); |
| |
| static const int NUM_INVOCATIONS_PER_PIXEL = 5; |
| |
| const TextureFormat m_format; |
| const TextureType m_imageType; |
| const AtomicOperation m_operation; |
| const AtomicOperationCaseType m_caseType; |
| }; |
| |
| int BinaryAtomicOperationCase::getOperationInitialValue (AtomicOperation op) |
| { |
| switch (op) |
| { |
| // \note 18 is just an arbitrary small nonzero value. |
| case ATOMIC_OPERATION_ADD: return 18; |
| case ATOMIC_OPERATION_MIN: return (1<<15) - 1; |
| case ATOMIC_OPERATION_MAX: return 18; |
| case ATOMIC_OPERATION_AND: return (1<<15) - 1; |
| case ATOMIC_OPERATION_OR: return 18; |
| case ATOMIC_OPERATION_XOR: return 18; |
| case ATOMIC_OPERATION_EXCHANGE: return 18; |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| int BinaryAtomicOperationCase::getAtomicFuncArgument (AtomicOperation op, const IVec3& invocationID, const IVec2& dispatchSizeXY) |
| { |
| const int x = invocationID.x(); |
| const int y = invocationID.y(); |
| const int z = invocationID.z(); |
| const int wid = dispatchSizeXY.x(); |
| const int hei = dispatchSizeXY.y(); |
| |
| switch (op) |
| { |
| // \note Fall-throughs. |
| case ATOMIC_OPERATION_ADD: |
| case ATOMIC_OPERATION_MIN: |
| case ATOMIC_OPERATION_MAX: |
| case ATOMIC_OPERATION_AND: |
| case ATOMIC_OPERATION_OR: |
| case ATOMIC_OPERATION_XOR: |
| return x*x + y*y + z*z; |
| |
| case ATOMIC_OPERATION_EXCHANGE: |
| return (z*wid + x)*hei + y; |
| |
| default: |
| DE_ASSERT(false); |
| return -1; |
| } |
| } |
| |
| string BinaryAtomicOperationCase::getAtomicFuncArgumentShaderStr (AtomicOperation op, const string& x, const string& y, const string& z, const IVec2& dispatchSizeXY) |
| { |
| switch (op) |
| { |
| // \note Fall-throughs. |
| case ATOMIC_OPERATION_ADD: |
| case ATOMIC_OPERATION_MIN: |
| case ATOMIC_OPERATION_MAX: |
| case ATOMIC_OPERATION_AND: |
| case ATOMIC_OPERATION_OR: |
| case ATOMIC_OPERATION_XOR: |
| return "("+ x+"*"+x +" + "+ y+"*"+y +" + "+ z+"*"+z +")"; |
| |
| case ATOMIC_OPERATION_EXCHANGE: |
| return "((" + z + "*" + toString(dispatchSizeXY.x()) + " + " + x + ")*" + toString(dispatchSizeXY.y()) + " + " + y + ")"; |
| |
| default: |
| DE_ASSERT(false); |
| return ""; |
| } |
| } |
| |
| class BinaryAtomicOperationCase::EndResultVerifier : public ImageLayerVerifier |
| { |
| public: |
| EndResultVerifier (AtomicOperation operation, TextureType imageType) : m_operation(operation), m_imageType(imageType) {} |
| |
| bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const |
| { |
| const bool isIntegerFormat = isFormatTypeInteger(resultSlice.getFormat().type); |
| const IVec2 dispatchSizeXY (NUM_INVOCATIONS_PER_PIXEL*resultSlice.getWidth(), resultSlice.getHeight()); |
| |
| log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx), |
| "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)))) |
| : "slice " + toString(sliceOrFaceNdx)), |
| resultSlice); |
| |
| for (int y = 0; y < resultSlice.getHeight(); y++) |
| for (int x = 0; x < resultSlice.getWidth(); x++) |
| { |
| union |
| { |
| int i; |
| float f; |
| } result; |
| |
| if (isIntegerFormat) |
| result.i = resultSlice.getPixelInt(x, y).x(); |
| else |
| result.f = resultSlice.getPixel(x, y).x(); |
| |
| // Compute the arguments that were given to the atomic function in the invocations that contribute to this pixel. |
| |
| IVec3 invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL]; |
| int atomicArgs[NUM_INVOCATIONS_PER_PIXEL]; |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx); |
| |
| invocationGlobalIDs[i] = gid; |
| atomicArgs[i] = getAtomicFuncArgument(m_operation, gid, dispatchSizeXY); |
| } |
| |
| if (isOrderIndependentAtomicOperation(m_operation)) |
| { |
| // Just accumulate the atomic args (and the initial value) according to the operation, and compare. |
| |
| DE_ASSERT(isIntegerFormat); |
| |
| int reference = getOperationInitialValue(m_operation); |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| reference = computeBinaryAtomicOperationResult(m_operation, reference, atomicArgs[i]); |
| |
| if (result.i != reference) |
| { |
| log << TestLog::Message << "// Failure: end result at pixel " << IVec2(x, y) << " of current layer is " << result.i << TestLog::EndMessage |
| << TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: data expression values for the IDs are " << arrayStr(atomicArgs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: reference value is " << reference << TestLog::EndMessage; |
| return false; |
| } |
| } |
| else if (m_operation == ATOMIC_OPERATION_EXCHANGE) |
| { |
| // Check that the end result equals one of the atomic args. |
| |
| bool matchFound = false; |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++) |
| matchFound = isIntegerFormat ? result.i == atomicArgs[i] |
| : de::abs(result.f - (float)atomicArgs[i]) <= 0.01f; |
| |
| if (!matchFound) |
| { |
| log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << (isIntegerFormat ? toString(result.i) : toString(result.f)) << TestLog::EndMessage |
| << TestLog::Message << "// Note: expected one of " << arrayStr(atomicArgs) << TestLog::EndMessage; |
| |
| return false; |
| } |
| } |
| else |
| DE_ASSERT(false); |
| } |
| |
| return true; |
| } |
| |
| private: |
| const AtomicOperation m_operation; |
| const TextureType m_imageType; |
| }; |
| |
| class BinaryAtomicOperationCase::ReturnValueVerifier : public ImageLayerVerifier |
| { |
| public: |
| //! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored. |
| ReturnValueVerifier (AtomicOperation operation, TextureType imageType, const IVec2& endResultImageLayerSize) : m_operation(operation), m_imageType(imageType), m_endResultImageLayerSize(endResultImageLayerSize) {} |
| |
| bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const |
| { |
| const bool isIntegerFormat (isFormatTypeInteger(resultSlice.getFormat().type)); |
| const IVec2 dispatchSizeXY (resultSlice.getWidth(), resultSlice.getHeight()); |
| |
| DE_ASSERT(resultSlice.getWidth() == NUM_INVOCATIONS_PER_PIXEL*m_endResultImageLayerSize.x() && |
| resultSlice.getHeight() == m_endResultImageLayerSize.y() && |
| resultSlice.getDepth() == 1); |
| |
| log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx), |
| "Per-Invocation Return Values, " |
| + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)))) |
| : "slice " + toString(sliceOrFaceNdx)), |
| resultSlice); |
| |
| for (int y = 0; y < m_endResultImageLayerSize.y(); y++) |
| for (int x = 0; x < m_endResultImageLayerSize.x(); x++) |
| { |
| union IntFloatArr |
| { |
| int i[NUM_INVOCATIONS_PER_PIXEL]; |
| float f[NUM_INVOCATIONS_PER_PIXEL]; |
| }; |
| |
| // Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice. |
| |
| IntFloatArr returnValues; |
| IntFloatArr atomicArgs; |
| IVec3 invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL]; |
| IVec2 pixelCoords[NUM_INVOCATIONS_PER_PIXEL]; |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| const IVec2 pixCoord (x + i*m_endResultImageLayerSize.x(), y); |
| const IVec3 gid (pixCoord.x(), pixCoord.y(), sliceOrFaceNdx); |
| |
| invocationGlobalIDs[i] = gid; |
| pixelCoords[i] = pixCoord; |
| |
| if (isIntegerFormat) |
| { |
| returnValues.i[i] = resultSlice.getPixelInt(gid.x(), y).x(); |
| atomicArgs.i[i] = getAtomicFuncArgument(m_operation, gid, dispatchSizeXY); |
| } |
| else |
| { |
| returnValues.f[i] = resultSlice.getPixel(gid.x(), y).x(); |
| atomicArgs.f[i] = (float)getAtomicFuncArgument(m_operation, gid, dispatchSizeXY); |
| } |
| } |
| |
| // Verify that the return values form a valid sequence. |
| |
| { |
| const bool success = isIntegerFormat ? verifyOperationAccumulationIntermediateValues(m_operation, |
| getOperationInitialValue(m_operation), |
| atomicArgs.i, |
| returnValues.i) |
| |
| : verifyOperationAccumulationIntermediateValues(m_operation, |
| (float)getOperationInitialValue(m_operation), |
| atomicArgs.f, |
| returnValues.f); |
| |
| if (!success) |
| { |
| log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are " |
| << (isIntegerFormat ? arrayStr(returnValues.i) : arrayStr(returnValues.f)) << TestLog::EndMessage |
| << TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: data expression values for the IDs are " |
| << (isIntegerFormat ? arrayStr(atomicArgs.i) : arrayStr(atomicArgs.f)) |
| << "; return values are not a valid result for any order of operations" << TestLog::EndMessage; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private: |
| const AtomicOperation m_operation; |
| const TextureType m_imageType; |
| const IVec2 m_endResultImageLayerSize; |
| |
| //! Check whether there exists an ordering of args such that { init*A", init*A*B, ..., init*A*B*...*LAST } is the "returnValues" sequence, where { A, B, ..., LAST } is args, and * denotes the operation. |
| // That is, whether "returnValues" is a valid sequence of intermediate return values when "operation" has been accumulated on "args" (and "init") in some arbitrary order. |
| template <typename T> |
| static bool verifyOperationAccumulationIntermediateValues (AtomicOperation operation, T init, const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL]) |
| { |
| bool argsUsed[NUM_INVOCATIONS_PER_PIXEL] = { false }; |
| |
| return verifyRecursive(operation, 0, init, argsUsed, args, returnValues); |
| } |
| |
| static bool compare (int a, int b) { return a == b; } |
| static bool compare (float a, float b) { return de::abs(a - b) <= 0.01f; } |
| |
| //! Depth-first search for verifying the return value sequence. |
| template <typename T> |
| static bool verifyRecursive (AtomicOperation operation, int index, T valueSoFar, bool (&argsUsed)[NUM_INVOCATIONS_PER_PIXEL], const T (&args)[NUM_INVOCATIONS_PER_PIXEL], const T (&returnValues)[NUM_INVOCATIONS_PER_PIXEL]) |
| { |
| if (index < NUM_INVOCATIONS_PER_PIXEL) |
| { |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| if (!argsUsed[i] && compare(returnValues[i], valueSoFar)) |
| { |
| argsUsed[i] = true; |
| if (verifyRecursive(operation, index+1, computeBinaryAtomicOperationResult(operation, valueSoFar, args[i]), argsUsed, args, returnValues)) |
| return true; |
| argsUsed[i] = false; |
| } |
| } |
| |
| return false; |
| } |
| else |
| return true; |
| } |
| }; |
| |
| void BinaryAtomicOperationCase::init (void) |
| { |
| if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic") && !glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2))) |
| throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension"); |
| |
| checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext()); |
| } |
| |
| BinaryAtomicOperationCase::IterateResult BinaryAtomicOperationCase::iterate (void) |
| { |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| TestLog& log (m_testCtx.getLog()); |
| glu::CallLogWrapper glLog (renderCtx.getFunctions(), log); |
| const deUint32 internalFormatGL = glu::getInternalFormat(m_format); |
| const deUint32 textureTargetGL = getGLTextureTarget(m_imageType); |
| const IVec3& imageSize = defaultImageSize(m_imageType); |
| const int numSlicesOrFaces = m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z(); |
| const bool isUintFormat = isFormatTypeUnsignedInteger(m_format.type); |
| const bool isIntFormat = isFormatTypeSignedInteger(m_format.type); |
| const glu::Buffer endResultTextureBuf (renderCtx); |
| const glu::Buffer returnValueTextureBuf (renderCtx); |
| const glu::Texture endResultTexture (renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize. |
| const glu::Texture returnValueTexture (renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES. |
| // Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL. |
| |
| glLog.enableLogging(true); |
| |
| // Setup textures. |
| |
| log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage; |
| if (m_imageType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage; |
| |
| if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES) |
| { |
| log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage; |
| if (m_imageType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage; |
| } |
| |
| // Fill endResultTexture with initial pattern. |
| |
| { |
| const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z()); |
| |
| { |
| const IVec4 initial(getOperationInitialValue(m_operation)); |
| |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| imageData.setPixel(x, y, z, initial); |
| } |
| |
| // Upload initial pattern to endResultTexture and bind to image. |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, *endResultTexture); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| log << TestLog::Message << "// Filling end-result texture with initial pattern (initial value " << getOperationInitialValue(m_operation) << ")" << TestLog::EndMessage; |
| |
| uploadTexture(glLog, imageData, *endResultTextureBuf); |
| } |
| |
| glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES) |
| { |
| // Set storage for returnValueTexture and bind to image. |
| |
| glLog.glActiveTexture(GL_TEXTURE1); |
| glLog.glBindTexture(textureTargetGL, *returnValueTexture); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage; |
| setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL, 1) |
| : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1, 1)), |
| *returnValueTextureBuf); |
| |
| glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| } |
| |
| // Perform image stores in compute shader and finalize reference computation. |
| |
| { |
| // Generate compute shader. |
| |
| const string colorVecTypeName = string(isUintFormat ? "u" : isIntFormat ? "i" : "") + "vec4"; |
| const string atomicCoord = m_imageType == TEXTURETYPE_BUFFER ? "gx % " + toString(imageSize.x()) |
| : m_imageType == TEXTURETYPE_2D ? "ivec2(gx % " + toString(imageSize.x()) + ", gy)" |
| : "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)"; |
| const string invocationCoord = m_imageType == TEXTURETYPE_BUFFER ? "gx" |
| : m_imageType == TEXTURETYPE_2D ? "ivec2(gx, gy)" |
| : "ivec3(gx, gy, gz)"; |
| const string atomicArgExpr = (isUintFormat ? "uint" |
| : isIntFormat ? "" |
| : "float") |
| + getAtomicFuncArgumentShaderStr(m_operation, "gx", "gy", "gz", IVec2(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y())); |
| const string atomicInvocation = string() + getAtomicOperationShaderFuncName(m_operation) + "(u_results, " + atomicCoord + ", " + atomicArgExpr + ")"; |
| const string shaderImageFormatStr = getShaderImageFormatQualifier(m_format); |
| const string shaderImageTypeStr = getShaderImageType(m_format.type, m_imageType); |
| const std::string glslVersionDeclaration = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType())); |
| |
| const glu::ShaderProgram program(renderCtx, |
| glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n" |
| + imageAtomicExtensionShaderRequires(renderCtx) |
| + textureTypeExtensionShaderRequires(m_imageType, renderCtx) + |
| "\n" |
| "precision highp " + shaderImageTypeStr + ";\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n" |
| + (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? |
| "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n" |
| : "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " int gx = int(gl_GlobalInvocationID.x);\n" |
| " int gy = int(gl_GlobalInvocationID.y);\n" |
| " int gz = int(gl_GlobalInvocationID.z);\n" |
| + (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? |
| " imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(" + atomicInvocation + "));\n" |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? |
| " " + atomicInvocation + ";\n" |
| : deFatalStr("Invalid AtomicOperationCaseType")) + |
| "}\n")); |
| |
| UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram()); |
| |
| log << program; |
| |
| if (!program.isOk()) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed"); |
| return STOP; |
| } |
| |
| // Setup and dispatch. |
| |
| glLog.glUseProgram(program.getProgram()); |
| |
| glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| |
| // Read texture and check. |
| |
| { |
| const deUint32 textureToCheckGL = m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? *endResultTexture |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? *returnValueTexture |
| : (deUint32)-1; |
| const deUint32 textureToCheckBufGL = m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? *endResultTextureBuf |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? *returnValueTextureBuf |
| : (deUint32)-1; |
| |
| const IVec3 textureToCheckSize = imageSize * IVec3(m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? 1 : NUM_INVOCATIONS_PER_PIXEL, 1, 1); |
| const UniquePtr<const ImageLayerVerifier> verifier (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? new EndResultVerifier(m_operation, m_imageType) |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? new ReturnValueVerifier(m_operation, m_imageType, imageSize.swizzle(0, 1)) |
| : (ImageLayerVerifier*)DE_NULL); |
| |
| if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, textureToCheckSize, *verifier)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| |
| return STOP; |
| } |
| } |
| |
| /*--------------------------------------------------------------------*//*! |
| * \brief Atomic compSwap operation case. |
| * |
| * Similar in principle to BinaryAtomicOperationCase, but separated for |
| * convenience, since the atomic function is somewhat different. Like |
| * BinaryAtomicOperationCase, this has separate cases for checking end |
| * result and return values. |
| *//*--------------------------------------------------------------------*/ |
| class AtomicCompSwapCase : public TestCase |
| { |
| public: |
| AtomicCompSwapCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, AtomicOperationCaseType caseType) |
| : TestCase (context, name, description) |
| , m_format (format) |
| , m_imageType (imageType) |
| , m_caseType (caseType) |
| { |
| DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32) || |
| m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32)); |
| } |
| |
| void init (void); |
| IterateResult iterate (void); |
| |
| private: |
| class EndResultVerifier; |
| class ReturnValueVerifier; |
| |
| static int getCompareArg (const IVec3& invocationID, int imageWidth); |
| static int getAssignArg (const IVec3& invocationID, int imageWidth); |
| static string getCompareArgShaderStr (const string& x, const string& y, const string& z, int imageWidth); |
| static string getAssignArgShaderStr (const string& x, const string& y, const string& z, int imageWidth); |
| |
| static const int NUM_INVOCATIONS_PER_PIXEL = 5; |
| |
| const TextureFormat m_format; |
| const TextureType m_imageType; |
| const AtomicOperationCaseType m_caseType; |
| }; |
| |
| int AtomicCompSwapCase::getCompareArg (const IVec3& invocationID, int imageWidth) |
| { |
| const int x = invocationID.x(); |
| const int y = invocationID.y(); |
| const int z = invocationID.z(); |
| const int wrapX = x % imageWidth; |
| const int curPixelInvocationNdx = x / imageWidth; |
| |
| return wrapX*wrapX + y*y + z*z + curPixelInvocationNdx*42; |
| } |
| |
| int AtomicCompSwapCase::getAssignArg (const IVec3& invocationID, int imageWidth) |
| { |
| return getCompareArg(IVec3(invocationID.x() + imageWidth, invocationID.y(), invocationID.z()), imageWidth); |
| } |
| |
| string AtomicCompSwapCase::getCompareArgShaderStr (const string& x, const string& y, const string& z, int imageWidth) |
| { |
| const string wrapX = "(" + x + "%" + toString(imageWidth) + ")"; |
| const string curPixelInvocationNdx = "(" + x + "/" + toString(imageWidth) + ")"; |
| |
| return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)"; |
| } |
| |
| string AtomicCompSwapCase::getAssignArgShaderStr (const string& x, const string& y, const string& z, int imageWidth) |
| { |
| const string wrapX = "(" + x + "%" + toString(imageWidth) + ")"; |
| const string curPixelInvocationNdx = "(" + x + "/" + toString(imageWidth) + " + 1)"; |
| |
| return "(" +wrapX+"*"+wrapX+ " + " +y+"*"+y+ " + " +z+"*"+z+ " + " + curPixelInvocationNdx + "*42)"; |
| } |
| |
| void AtomicCompSwapCase::init (void) |
| { |
| if (!m_context.getContextInfo().isExtensionSupported("GL_OES_shader_image_atomic") && !glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2))) |
| throw tcu::NotSupportedError("Test requires OES_shader_image_atomic extension"); |
| |
| checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext()); |
| } |
| |
| class AtomicCompSwapCase::EndResultVerifier : public ImageLayerVerifier |
| { |
| public: |
| EndResultVerifier (TextureType imageType, int imageWidth) : m_imageType(imageType), m_imageWidth(imageWidth) {} |
| |
| bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const |
| { |
| DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type)); |
| DE_ASSERT(resultSlice.getWidth() == m_imageWidth); |
| |
| log << TestLog::Image("EndResults" + toString(sliceOrFaceNdx), |
| "Result Values, " + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)))) |
| : "slice " + toString(sliceOrFaceNdx)), |
| resultSlice); |
| |
| for (int y = 0; y < resultSlice.getHeight(); y++) |
| for (int x = 0; x < resultSlice.getWidth(); x++) |
| { |
| // Compute the value-to-assign arguments that were given to the atomic function in the invocations that contribute to this pixel. |
| // One of those should be the result. |
| |
| const int result = resultSlice.getPixelInt(x, y).x(); |
| IVec3 invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL]; |
| int assignArgs[NUM_INVOCATIONS_PER_PIXEL]; |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| const IVec3 gid(x + i*resultSlice.getWidth(), y, sliceOrFaceNdx); |
| |
| invocationGlobalIDs[i] = gid; |
| assignArgs[i] = getAssignArg(gid, m_imageWidth); |
| } |
| |
| { |
| bool matchFound = false; |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL && !matchFound; i++) |
| matchFound = result == assignArgs[i]; |
| |
| if (!matchFound) |
| { |
| log << TestLog::Message << "// Failure: invalid value at pixel " << IVec2(x, y) << ": got " << result << TestLog::EndMessage |
| << TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: expected one of " << arrayStr(assignArgs) |
| << " (those are the values given as the 'data' argument in the invocations that contribute to this pixel)" |
| << TestLog::EndMessage; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private: |
| const TextureType m_imageType; |
| const int m_imageWidth; |
| }; |
| |
| class AtomicCompSwapCase::ReturnValueVerifier : public ImageLayerVerifier |
| { |
| public: |
| //! \note endResultImageLayerSize is (width, height) of the image operated on by the atomic ops, and not the size of the image where the return values are stored. |
| ReturnValueVerifier (TextureType imageType, int endResultImageWidth) : m_imageType(imageType), m_endResultImageWidth(endResultImageWidth) {} |
| |
| bool operator() (TestLog& log, const ConstPixelBufferAccess& resultSlice, int sliceOrFaceNdx) const |
| { |
| DE_ASSERT(isFormatTypeInteger(resultSlice.getFormat().type)); |
| DE_ASSERT(resultSlice.getWidth() == NUM_INVOCATIONS_PER_PIXEL*m_endResultImageWidth); |
| |
| log << TestLog::Image("ReturnValues" + toString(sliceOrFaceNdx), |
| "Per-Invocation Return Values, " |
| + (m_imageType == TEXTURETYPE_CUBE ? "face " + string(glu::getCubeMapFaceName(cubeFaceToGLFace(glslImageFuncZToCubeFace(sliceOrFaceNdx)))) |
| : "slice " + toString(sliceOrFaceNdx)), |
| resultSlice); |
| |
| for (int y = 0; y < resultSlice.getHeight(); y++) |
| for (int x = 0; x < m_endResultImageWidth; x++) |
| { |
| // Get the atomic function args and return values for all the invocations that contribute to the pixel (x, y) in the current end result slice. |
| |
| int returnValues[NUM_INVOCATIONS_PER_PIXEL]; |
| int compareArgs[NUM_INVOCATIONS_PER_PIXEL]; |
| IVec3 invocationGlobalIDs[NUM_INVOCATIONS_PER_PIXEL]; |
| IVec2 pixelCoords[NUM_INVOCATIONS_PER_PIXEL]; |
| |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| const IVec2 pixCoord (x + i*m_endResultImageWidth, y); |
| const IVec3 gid (pixCoord.x(), pixCoord.y(), sliceOrFaceNdx); |
| |
| pixelCoords[i] = pixCoord; |
| invocationGlobalIDs[i] = gid; |
| returnValues[i] = resultSlice.getPixelInt(gid.x(), y).x(); |
| compareArgs[i] = getCompareArg(gid, m_endResultImageWidth); |
| } |
| |
| // Verify that the return values form a valid sequence. |
| // Due to the way the compare and assign arguments to the atomic calls are organized |
| // among the different invocations contributing to the same pixel -- i.e. one invocation |
| // compares to A and assigns B, another compares to B and assigns C, and so on, where |
| // A<B<C etc -- the first value in the return value sequence must be A, and each following |
| // value must be either the same as or the smallest value (among A, B, C, ...) bigger than |
| // the one just before it. E.g. sequences A A A A A A A A, A B C D E F G H and |
| // A A B B B C D E are all valid sequences (if there were 8 invocations contributing |
| // to each pixel). |
| |
| { |
| int failingNdx = -1; |
| |
| { |
| int currentAtomicValueNdx = 0; |
| for (int i = 0; i < NUM_INVOCATIONS_PER_PIXEL; i++) |
| { |
| if (returnValues[i] == compareArgs[currentAtomicValueNdx]) |
| continue; |
| if (i > 0 && returnValues[i] == compareArgs[currentAtomicValueNdx+1]) |
| { |
| currentAtomicValueNdx++; |
| continue; |
| } |
| failingNdx = i; |
| break; |
| } |
| } |
| |
| if (failingNdx >= 0) |
| { |
| log << TestLog::Message << "// Failure: intermediate return values at pixels " << arrayStr(pixelCoords) << " of current layer are " |
| << arrayStr(returnValues) << TestLog::EndMessage |
| << TestLog::Message << "// Note: relevant shader invocation global IDs are " << arrayStr(invocationGlobalIDs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: 'compare' argument values for the IDs are " << arrayStr(compareArgs) << TestLog::EndMessage |
| << TestLog::Message << "// Note: expected the return value sequence to fulfill the following conditions:\n" |
| << "// - first value is " << compareArgs[0] << "\n" |
| << "// - each value other than the first is either the same as the one just before it, or the smallest value (in the sequence " |
| << arrayStr(compareArgs) << ") bigger than the one just before it" << TestLog::EndMessage; |
| if (failingNdx == 0) |
| log << TestLog::Message << "// Note: the first return value (" << returnValues[0] << ") isn't " << compareArgs[0] << TestLog::EndMessage; |
| else |
| log << TestLog::Message << "// Note: the return value at index " << failingNdx << " (value " << returnValues[failingNdx] << ") " |
| << "is neither " << returnValues[failingNdx-1] << " (the one just before it) " |
| << "nor " << compareArgs[arrayIndexOf(compareArgs, returnValues[failingNdx-1])+1] << " (the smallest value bigger than the one just before it)" |
| << TestLog::EndMessage; |
| |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private: |
| const TextureType m_imageType; |
| const int m_endResultImageWidth; |
| }; |
| |
| AtomicCompSwapCase::IterateResult AtomicCompSwapCase::iterate (void) |
| { |
| const RenderContext& renderCtx = m_context.getRenderContext(); |
| TestLog& log (m_testCtx.getLog()); |
| glu::CallLogWrapper glLog (renderCtx.getFunctions(), log); |
| const deUint32 internalFormatGL = glu::getInternalFormat(m_format); |
| const deUint32 textureTargetGL = getGLTextureTarget(m_imageType); |
| const IVec3& imageSize = defaultImageSize(m_imageType); |
| const int numSlicesOrFaces = m_imageType == TEXTURETYPE_CUBE ? 6 : imageSize.z(); |
| const bool isUintFormat = isFormatTypeUnsignedInteger(m_format.type); |
| const bool isIntFormat = isFormatTypeSignedInteger(m_format.type); |
| const glu::Buffer endResultTextureBuf (renderCtx); |
| const glu::Buffer returnValueTextureBuf (renderCtx); |
| const glu::Texture endResultTexture (renderCtx); //!< Texture for the final result; i.e. the texture on which the atomic operations are done. Size imageSize. |
| const glu::Texture returnValueTexture (renderCtx); //!< Texture into which the return values are stored if m_caseType == CASETYPE_RETURN_VALUES. |
| // Size imageSize*IVec3(N, 1, 1) or, for cube maps, imageSize*IVec3(N, N, 1) where N is NUM_INVOCATIONS_PER_PIXEL. |
| |
| DE_ASSERT(isUintFormat || isIntFormat); |
| |
| glLog.enableLogging(true); |
| |
| // Setup textures. |
| |
| log << TestLog::Message << "// Created a texture (name " << *endResultTexture << ") to act as the target of atomic operations" << TestLog::EndMessage; |
| if (m_imageType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created a buffer for the texture (name " << *endResultTextureBuf << ")" << TestLog::EndMessage; |
| |
| if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES) |
| { |
| log << TestLog::Message << "// Created a texture (name " << *returnValueTexture << ") to which the intermediate return values of the atomic operation are stored" << TestLog::EndMessage; |
| if (m_imageType == TEXTURETYPE_BUFFER) |
| log << TestLog::Message << "// Created a buffer for the texture (name " << *returnValueTextureBuf << ")" << TestLog::EndMessage; |
| } |
| |
| // Fill endResultTexture with initial pattern. |
| |
| { |
| const LayeredImage imageData(m_imageType, m_format, imageSize.x(), imageSize.y(), imageSize.z()); |
| |
| { |
| for (int z = 0; z < numSlicesOrFaces; z++) |
| for (int y = 0; y < imageSize.y(); y++) |
| for (int x = 0; x < imageSize.x(); x++) |
| imageData.setPixel(x, y, z, IVec4(getCompareArg(IVec3(x, y, z), imageSize.x()))); |
| } |
| |
| // Upload initial pattern to endResultTexture and bind to image. |
| |
| glLog.glActiveTexture(GL_TEXTURE0); |
| glLog.glBindTexture(textureTargetGL, *endResultTexture); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| log << TestLog::Message << "// Filling end-result texture with initial pattern" << TestLog::EndMessage; |
| |
| uploadTexture(glLog, imageData, *endResultTextureBuf); |
| } |
| |
| glLog.glBindImageTexture(0, *endResultTexture, 0, GL_TRUE, 0, GL_READ_WRITE, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| |
| if (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES) |
| { |
| // Set storage for returnValueTexture and bind to image. |
| |
| glLog.glActiveTexture(GL_TEXTURE1); |
| glLog.glBindTexture(textureTargetGL, *returnValueTexture); |
| setTexParameteri(glLog, textureTargetGL); |
| |
| log << TestLog::Message << "// Setting storage of return-value texture" << TestLog::EndMessage; |
| setTextureStorage(glLog, m_imageType, internalFormatGL, imageSize * (m_imageType == TEXTURETYPE_CUBE ? IVec3(NUM_INVOCATIONS_PER_PIXEL, NUM_INVOCATIONS_PER_PIXEL, 1) |
| : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1, 1)), |
| *returnValueTextureBuf); |
| |
| glLog.glBindImageTexture(1, *returnValueTexture, 0, GL_TRUE, 0, GL_WRITE_ONLY, internalFormatGL); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glBindImageTexture"); |
| } |
| |
| // Perform atomics in compute shader. |
| |
| { |
| // Generate compute shader. |
| |
| const string colorScalarTypeName = isUintFormat ? "uint" : isIntFormat ? "int" : DE_NULL; |
| const string colorVecTypeName = string(isUintFormat ? "u" : isIntFormat ? "i" : DE_NULL) + "vec4"; |
| const string atomicCoord = m_imageType == TEXTURETYPE_BUFFER ? "gx % " + toString(imageSize.x()) |
| : m_imageType == TEXTURETYPE_2D ? "ivec2(gx % " + toString(imageSize.x()) + ", gy)" |
| : "ivec3(gx % " + toString(imageSize.x()) + ", gy, gz)"; |
| const string invocationCoord = m_imageType == TEXTURETYPE_BUFFER ? "gx" |
| : m_imageType == TEXTURETYPE_2D ? "ivec2(gx, gy)" |
| : "ivec3(gx, gy, gz)"; |
| const string shaderImageFormatStr = getShaderImageFormatQualifier(m_format); |
| const string shaderImageTypeStr = getShaderImageType(m_format.type, m_imageType); |
| const string glslVersionDeclaration = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(renderCtx.getType())); |
| |
| const glu::ShaderProgram program(renderCtx, |
| glu::ProgramSources() << glu::ComputeSource(glslVersionDeclaration + "\n" |
| + imageAtomicExtensionShaderRequires(renderCtx) |
| + textureTypeExtensionShaderRequires(m_imageType, renderCtx) + |
| "\n" |
| "precision highp " + shaderImageTypeStr + ";\n" |
| "\n" |
| "layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;\n" |
| "layout (" + shaderImageFormatStr + ", binding=0) coherent uniform " + shaderImageTypeStr + " u_results;\n" |
| + (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? |
| "layout (" + shaderImageFormatStr + ", binding=1) writeonly uniform " + shaderImageTypeStr + " u_returnValues;\n" |
| : "") + |
| "\n" |
| "void main (void)\n" |
| "{\n" |
| " int gx = int(gl_GlobalInvocationID.x);\n" |
| " int gy = int(gl_GlobalInvocationID.y);\n" |
| " int gz = int(gl_GlobalInvocationID.z);\n" |
| " " + colorScalarTypeName + " compare = " + colorScalarTypeName + getCompareArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n" |
| " " + colorScalarTypeName + " data = " + colorScalarTypeName + getAssignArgShaderStr("gx", "gy", "gz", imageSize.x()) + ";\n" |
| " " + colorScalarTypeName + " status = " + colorScalarTypeName + "(-1);\n" |
| " status = imageAtomicCompSwap(u_results, " + atomicCoord + ", compare, data);\n" |
| + (m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? |
| " imageStore(u_returnValues, " + invocationCoord + ", " + colorVecTypeName + "(status));\n" : |
| "") + |
| "}\n")); |
| |
| UniformAccessLogger uniforms(renderCtx.getFunctions(), log, program.getProgram()); |
| |
| log << program; |
| |
| if (!program.isOk()) |
| { |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program compilation failed"); |
| return STOP; |
| } |
| |
| // Setup and dispatch. |
| |
| glLog.glUseProgram(program.getProgram()); |
| |
| glLog.glDispatchCompute(NUM_INVOCATIONS_PER_PIXEL*imageSize.x(), imageSize.y(), numSlicesOrFaces); |
| GLU_EXPECT_NO_ERROR(renderCtx.getFunctions().getError(), "glDispatchCompute"); |
| } |
| |
| // Create reference, read texture and compare. |
| |
| { |
| const deUint32 textureToCheckGL = m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? *endResultTexture |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? *returnValueTexture |
| : (deUint32)-1; |
| |
| const deUint32 textureToCheckBufGL = m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? *endResultTextureBuf |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? *returnValueTextureBuf |
| : (deUint32)-1; |
| |
| // The relevant region of the texture being checked (potentially |
| // different from actual texture size for cube maps, because cube maps |
| // may have unused pixels due to square size restriction). |
| const IVec3 relevantRegion = imageSize * (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? IVec3(1, 1, 1) |
| : IVec3(NUM_INVOCATIONS_PER_PIXEL, 1, 1)); |
| |
| const UniquePtr<const ImageLayerVerifier> verifier (m_caseType == ATOMIC_OPERATION_CASE_TYPE_END_RESULT ? new EndResultVerifier(m_imageType, imageSize.x()) |
| : m_caseType == ATOMIC_OPERATION_CASE_TYPE_RETURN_VALUES ? new ReturnValueVerifier(m_imageType, imageSize.x()) |
| : (ImageLayerVerifier*)DE_NULL); |
| |
| if (readTextureAndVerify(renderCtx, glLog, textureToCheckGL, textureToCheckBufGL, m_imageType, m_format, relevantRegion, *verifier)) |
| m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); |
| else |
| m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed"); |
| |
| return STOP; |
| } |
| } |
| |
| //! Case testing the "coherent" or "volatile" qualifier, along with memoryBarrier() and barrier(). |
| class CoherenceCase : public TestCase |
| { |
| public: |
| enum Qualifier |
| { |
| QUALIFIER_COHERENT = 0, |
| QUALIFIER_VOLATILE, |
| |
| QUALIFIER_LAST |
| }; |
| |
| CoherenceCase (Context& context, const char* name, const char* description, const TextureFormat& format, TextureType imageType, Qualifier qualifier) |
| : TestCase (context, name, description) |
| , m_format (format) |
| , m_imageType (imageType) |
| , m_qualifier (qualifier) |
| { |
| DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Y) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X) && |
| DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_Z) == DE_LENGTH_OF_ARRAY(CoherenceCase::SHADER_READ_OFFSETS_X)); |
| |
| DE_ASSERT(qualifier == QUALIFIER_COHERENT || qualifier == QUALIFIER_VOLATILE); |
| |
| DE_ASSERT(m_format == TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT32) || |
| m_format == TextureFormat(TextureFormat::R, TextureFormat::SIGNED_INT32) || |
| m_format == TextureFormat(TextureFormat::R, TextureFormat::FLOAT)); |
| } |
| |
| void init (void) { checkTextureTypeExtensions(m_context.getContextInfo(), m_imageType, m_context.getRenderContext()); } |
| IterateResult iterate (void); |
| |
| private: |
| static const int SHADER_READ_OFFSETS_X[4]; |
| static const int SHADER_READ_OFFSETS_Y[4]; |
| static const int SHADER_READ_OFFSETS_Z[4]; |
| static const char* const SHADER_READ_OFFSETS_X_STR; |
| |