blob: d27ed7684259f1fabd6a6df0f452783e4fd2866c [file] [log] [blame] [edit]
/*-------------------------------------------------------------------------
* 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;