blob: 0d76fa9ca9a7f625ef7b8b38aa94e130107fc8cc [file] [edit]
/*------------------------------------------------------------------------
* Vulkan Conformance Tests
* ------------------------
*
* Copyright (c) 2018 The Khronos Group Inc.
*
* 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 Robust buffer access tests for storage buffers and
* storage texel buffers with variable pointers.
*
* \note These tests are checking if accessing a memory through a variable
* pointer that points outside of accessible buffer memory is robust.
* To do this the tests are creating proper SPIRV code that creates
* variable pointers. Those pointers are either pointing into a
* memory allocated for a buffer but "not accesible" - meaning
* DescriptorBufferInfo has smaller size than a memory we access in
* shader or entirely outside of allocated memory (i.e. buffer is
* 256 bytes big but we are trying to access under offset of 1k from
* buffer start). There is a set of valid behaviours defined when
* robust buffer access extension is enabled described in chapter 32
* section 1 of Vulkan spec.
*
*//*--------------------------------------------------------------------*/
#include "vktRobustBufferAccessWithVariablePointersTests.hpp"
#include "vktRobustnessUtil.hpp"
#include "vktTestCaseUtil.hpp"
#include "vkBuilderUtil.hpp"
#include "vkImageUtil.hpp"
#include "vkPrograms.hpp"
#include "vkQueryUtil.hpp"
#include "vkDeviceUtil.hpp"
#include "vkRef.hpp"
#include "vkRefUtil.hpp"
#include "vkTypeUtil.hpp"
#include "tcuTestLog.hpp"
#include "vkDefs.hpp"
#include "deRandom.hpp"
#include <limits>
#include <sstream>
namespace vkt
{
namespace robustness
{
using namespace vk;
// keep local things local
namespace
{
// Creates a custom device with robust buffer access and variable pointer features.
Move<VkDevice> createRobustBufferAccessVariablePointersDevice(Context &context
#ifdef CTS_USES_VULKANSC
,
const vkt::CustomInstance &customInstance
#endif // CTS_USES_VULKANSC
)
{
auto pointerFeatures = context.getVariablePointersFeatures();
VkPhysicalDeviceFeatures2 features2 = initVulkanStructure();
features2.features = context.getDeviceFeatures();
features2.features.robustBufferAccess = VK_TRUE;
features2.pNext = &pointerFeatures;
return createRobustBufferAccessDevice(context,
#ifdef CTS_USES_VULKANSC
customInstance,
#endif // CTS_USES_VULKANSC
&features2);
}
// A supplementary structures that can hold information about buffer size
struct AccessRangesData
{
VkDeviceSize allocSize;
VkDeviceSize accessRange;
VkDeviceSize maxAccessRange;
};
// Pointer to function that can be used to fill a buffer with some data - it is passed as an parameter to buffer creation utility function
typedef void (*FillBufferProcPtr)(void *, vk::VkDeviceSize, const void *const);
// An utility function for creating a buffer
// This function not only allocates memory for the buffer but also fills buffer up with a data
void createTestBuffer(Context &context, const vk::DeviceInterface &deviceInterface, const VkDevice &device,
VkDeviceSize accessRange, VkBufferUsageFlags usage, SimpleAllocator &allocator,
Move<VkBuffer> &buffer, de::MovePtr<Allocation> &bufferAlloc, AccessRangesData &data,
FillBufferProcPtr fillBufferProc, const void *const blob, bool useAccessRange)
{
const VkBufferCreateInfo bufferParams = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType;
nullptr, // const void* pNext;
0u, // VkBufferCreateFlags flags;
accessRange, // VkDeviceSize size;
usage, // VkBufferUsageFlags usage;
VK_SHARING_MODE_EXCLUSIVE, // VkSharingMode sharingMode;
VK_QUEUE_FAMILY_IGNORED, // uint32_t queueFamilyIndexCount;
nullptr // const uint32_t* pQueueFamilyIndices;
};
buffer = createBuffer(deviceInterface, device, &bufferParams);
VkMemoryRequirements bufferMemoryReqs = getBufferMemoryRequirements(deviceInterface, device, *buffer);
bufferAlloc = allocator.allocate(bufferMemoryReqs, MemoryRequirement::HostVisible);
data.allocSize = bufferMemoryReqs.size;
data.accessRange = accessRange;
data.maxAccessRange = deMinu64(data.allocSize, deMinu64(bufferParams.size, accessRange));
VK_CHECK(deviceInterface.bindBufferMemory(device, *buffer, bufferAlloc->getMemory(), bufferAlloc->getOffset()));
#ifdef CTS_USES_VULKANSC
if (context.getTestContext().getCommandLine().isSubProcess())
fillBufferProc(bufferAlloc->getHostPtr(), useAccessRange ? accessRange : bufferMemoryReqs.size, blob);
#else
fillBufferProc(bufferAlloc->getHostPtr(), useAccessRange ? accessRange : bufferMemoryReqs.size, blob);
DE_UNREF(context);
#endif // CTS_USES_VULKANCSC
flushMappedMemoryRange(deviceInterface, device, bufferAlloc->getMemory(), bufferAlloc->getOffset(), VK_WHOLE_SIZE);
}
// An adapter function matching FillBufferProcPtr interface. Fills a buffer with "randomly" generated test data matching desired format.
void populateBufferWithValues(void *buffer, VkDeviceSize size, const void *const blob)
{
populateBufferWithTestValues(buffer, size, *static_cast<const vk::VkFormat *>(blob));
}
// An adapter function matching FillBufferProcPtr interface. Fills a buffer with 0xBABABABABABA... pattern. Used to fill up output buffers.
// Since this pattern cannot show up in generated test data it should not show up in the valid output.
void populateBufferWithFiller(void *buffer, VkDeviceSize size, const void *const blob)
{
DE_UNREF(blob);
deMemset(buffer, 0xBA, static_cast<size_t>(size));
}
// An adapter function matching FillBufferProcPtr interface. Fills a buffer with a copy of memory contents pointed to by blob.
void populateBufferWithCopy(void *buffer, VkDeviceSize size, const void *const blob)
{
deMemcpy(buffer, blob, static_cast<size_t>(size));
}
// A composite types used in test
// Those composites can be made of unsigned ints, signed ints or floats (except for matrices that work with floats only).
enum ShaderType
{
SHADER_TYPE_MATRIX_COPY = 0,
SHADER_TYPE_VECTOR_COPY,
SHADER_TYPE_SCALAR_COPY,
SHADER_TYPE_COUNT
};
// We are testing reads or writes
// In case of testing reads - writes are always
enum BufferAccessType
{
BUFFER_ACCESS_TYPE_READ_FROM_STORAGE = 0,
BUFFER_ACCESS_TYPE_WRITE_TO_STORAGE,
};
// Test case for checking robust buffer access with variable pointers
class RobustAccessWithPointersTest : public vkt::TestCase
{
public:
static const uint32_t s_testArraySize;
static const uint32_t s_numberOfBytesAccessed;
RobustAccessWithPointersTest(tcu::TestContext &testContext, const std::string &name, VkShaderStageFlags shaderStage,
ShaderType shaderType, VkFormat bufferFormat);
virtual ~RobustAccessWithPointersTest(void)
{
}
void checkSupport(Context &context) const override;
protected:
const VkShaderStageFlags m_shaderStage;
const ShaderType m_shaderType;
const VkFormat m_bufferFormat;
};
const uint32_t RobustAccessWithPointersTest::s_testArraySize = 1024u;
const uint32_t RobustAccessWithPointersTest::s_numberOfBytesAccessed = static_cast<uint32_t>(16ull * sizeof(float));
RobustAccessWithPointersTest::RobustAccessWithPointersTest(tcu::TestContext &testContext, const std::string &name,
VkShaderStageFlags shaderStage, ShaderType shaderType,
VkFormat bufferFormat)
: vkt::TestCase(testContext, name)
, m_shaderStage(shaderStage)
, m_shaderType(shaderType)
, m_bufferFormat(bufferFormat)
{
DE_ASSERT(m_shaderStage == VK_SHADER_STAGE_VERTEX_BIT || m_shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT ||
m_shaderStage == VK_SHADER_STAGE_COMPUTE_BIT);
}
void RobustAccessWithPointersTest::checkSupport(Context &context) const
{
const auto &pointerFeatures = context.getVariablePointersFeatures();
if (!pointerFeatures.variablePointersStorageBuffer)
TCU_THROW(NotSupportedError, "VariablePointersStorageBuffer SPIR-V capability not supported");
if (context.isDeviceFunctionalitySupported("VK_KHR_portability_subset") &&
!context.getDeviceFeatures().robustBufferAccess)
TCU_THROW(NotSupportedError,
"VK_KHR_portability_subset: robustBufferAccess not supported by this implementation");
}
// A subclass for testing reading with variable pointers
class RobustReadTest : public RobustAccessWithPointersTest
{
public:
RobustReadTest(tcu::TestContext &testContext, const std::string &name, VkShaderStageFlags shaderStage,
ShaderType shaderType, VkFormat bufferFormat, VkDeviceSize readAccessRange,
bool accessOutOfBackingMemory);
virtual ~RobustReadTest(void)
{
}
virtual TestInstance *createInstance(Context &context) const;
private:
virtual void initPrograms(SourceCollections &programCollection) const;
const VkDeviceSize m_readAccessRange;
const bool m_accessOutOfBackingMemory;
};
// A subclass for testing writing with variable pointers
class RobustWriteTest : public RobustAccessWithPointersTest
{
public:
RobustWriteTest(tcu::TestContext &testContext, const std::string &name, VkShaderStageFlags shaderStage,
ShaderType shaderType, VkFormat bufferFormat, VkDeviceSize writeAccessRange,
bool accessOutOfBackingMemory);
virtual ~RobustWriteTest(void)
{
}
virtual TestInstance *createInstance(Context &context) const;
private:
virtual void initPrograms(SourceCollections &programCollection) const;
const VkDeviceSize m_writeAccessRange;
const bool m_accessOutOfBackingMemory;
};
// In case I detect that some prerequisites are not fullfilled I am creating this lightweight empty test instance instead of AccessInstance. Should be bit faster that way.
class NotSupportedInstance : public vkt::TestInstance
{
public:
NotSupportedInstance(Context &context, const std::string &message)
: TestInstance(context)
, m_notSupportedMessage(message)
{
}
virtual ~NotSupportedInstance(void)
{
}
virtual tcu::TestStatus iterate(void)
{
TCU_THROW(NotSupportedError, m_notSupportedMessage.c_str());
}
private:
std::string m_notSupportedMessage;
};
// A superclass for instances testing reading and writing
// holds all necessary object members
class AccessInstance : public vkt::TestInstance
{
public:
AccessInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
BufferAccessType bufferAccessType, VkDeviceSize inBufferAccessRange,
VkDeviceSize outBufferAccessRange, bool accessOutOfBackingMemory);
virtual ~AccessInstance(void);
virtual tcu::TestStatus iterate(void);
virtual bool verifyResult(bool splitAccess = false);
private:
bool isExpectedValueFromInBuffer(VkDeviceSize offsetInBytes, const void *valuePtr, VkDeviceSize valueSize);
bool isOutBufferValueUnchanged(VkDeviceSize offsetInBytes, VkDeviceSize valueSize);
protected:
#ifndef CTS_USES_VULKANSC
Move<VkDevice> m_device;
de::MovePtr<vk::DeviceDriver> m_deviceDriver;
#else
// Construction needs to happen in this exact order to ensure proper resource destruction
de::MovePtr<CustomInstance> m_customInstance;
Move<VkDevice> m_device;
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> m_deviceDriver;
#endif // CTS_USES_VULKANSC
de::MovePtr<TestEnvironment> m_testEnvironment;
const ShaderType m_shaderType;
const VkShaderStageFlags m_shaderStage;
const VkFormat m_bufferFormat;
const BufferAccessType m_bufferAccessType;
AccessRangesData m_inBufferAccess;
Move<VkBuffer> m_inBuffer;
de::MovePtr<Allocation> m_inBufferAlloc;
AccessRangesData m_outBufferAccess;
Move<VkBuffer> m_outBuffer;
de::MovePtr<Allocation> m_outBufferAlloc;
Move<VkBuffer> m_indicesBuffer;
de::MovePtr<Allocation> m_indicesBufferAlloc;
Move<VkDescriptorPool> m_descriptorPool;
Move<VkDescriptorSetLayout> m_descriptorSetLayout;
Move<VkDescriptorSet> m_descriptorSet;
Move<VkFence> m_fence;
VkQueue m_queue;
// Used when m_shaderStage == VK_SHADER_STAGE_VERTEX_BIT
Move<VkBuffer> m_vertexBuffer;
de::MovePtr<Allocation> m_vertexBufferAlloc;
const bool m_accessOutOfBackingMemory;
};
// A subclass for read tests
class ReadInstance : public AccessInstance
{
public:
ReadInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
VkDeviceSize inBufferAccessRange, bool accessOutOfBackingMemory);
virtual ~ReadInstance(void)
{
}
};
// A subclass for write tests
class WriteInstance : public AccessInstance
{
public:
WriteInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
VkDeviceSize writeBufferAccessRange, bool accessOutOfBackingMemory);
virtual ~WriteInstance(void)
{
}
};
// Automatically incremented counter.
// Each read of value bumps counter up.
class Autocounter
{
public:
Autocounter() : value(0u)
{
}
uint32_t incrementAndGetValue()
{
return ++value;
}
private:
uint32_t value;
};
// A class representing SPIRV variable.
// This class internally has an unique identificator.
// When such variable is used in shader composition routine it is mapped on a in-SPIRV-code variable name.
class Variable
{
friend bool operator<(const Variable &a, const Variable &b);
public:
Variable(Autocounter &autoincrement) : value(autoincrement.incrementAndGetValue())
{
}
private:
uint32_t value;
};
bool operator<(const Variable &a, const Variable &b)
{
return a.value < b.value;
}
// A class representing SPIRV operation.
// Since those are not copyable they don't need internal id. Memory address is used instead.
class Operation
{
friend bool operator==(const Operation &a, const Operation &b);
public:
Operation(const char *text) : value(text)
{
}
const std::string &getValue() const
{
return value;
}
private:
Operation(const Operation &other);
const std::string value;
};
bool operator==(const Operation &a, const Operation &b)
{
return &a == &b; // a fast & simple address comparison - making copies was disabled
}
// A namespace containing all SPIRV operations used in those tests.
namespace op
{
#define OP(name) const Operation name("Op" #name)
OP(Capability);
OP(Extension);
OP(ExtInstImport);
OP(EntryPoint);
OP(MemoryModel);
OP(ExecutionMode);
OP(Decorate);
OP(MemberDecorate);
OP(Name);
OP(MemberName);
OP(TypeVoid);
OP(TypeBool);
OP(TypeInt);
OP(TypeFloat);
OP(TypeVector);
OP(TypeMatrix);
OP(TypeArray);
OP(TypeStruct);
OP(TypeFunction);
OP(TypePointer);
OP(TypeImage);
OP(TypeSampledImage);
OP(Constant);
OP(ConstantComposite);
OP(Variable);
OP(Function);
OP(FunctionEnd);
OP(Label);
OP(Return);
OP(LogicalEqual);
OP(IEqual);
OP(Select);
OP(AccessChain);
OP(Load);
OP(Store);
#undef OP
} // namespace op
// A class that allows to easily compose SPIRV code.
// This class automatically keeps correct order of most of operations
// i.e. capabilities to the top,
class ShaderStream
{
public:
ShaderStream()
{
}
// composes shader string out of shader substreams.
std::string str() const
{
std::stringstream stream;
stream << capabilities.str() << "; ----------------- PREAMBLE -----------------\n"
<< preamble.str() << "; ----------------- DEBUG --------------------\n"
<< names.str() << "; ----------------- DECORATIONS --------------\n"
<< decorations.str() << "; ----------------- TYPES --------------------\n"
<< basictypes.str() << "; ----------------- CONSTANTS ----------------\n"
<< constants.str() << "; ----------------- ADVANCED TYPES -----------\n"
<< compositetypes.str()
<< ((compositeconstants.str().length() > 0) ? "; ----------------- CONSTANTS ----------------\n" : "")
<< compositeconstants.str() << "; ----------------- VARIABLES & FUNCTIONS ----\n"
<< shaderstream.str();
return stream.str();
}
// Functions below are used to push Operations, Variables and other strings, numbers and characters to the shader.
// Each function uses selectStream and map subroutines.
// selectStream is used to choose a proper substream of shader.
// E.g. if an operation is OpConstant it should be put into constants definitions stream - so selectStream will return that stream.
// map on the other hand is used to replace Variables and Operations to their in-SPIRV-code representations.
// for types like ints or floats map simply calls << operator to produce its string representation
// for Operations a proper operation string is returned
// for Variables there is a special mapping between in-C++ variable and in-SPIRV-code variable name.
// following sequence of functions could be squashed to just two using variadic templates once we move to C++11 or higher
// each method returns *this to allow chaining calls to these methods.
template <typename T>
ShaderStream &operator()(const T &a)
{
selectStream(a, 0) << map(a) << '\n';
return *this;
}
template <typename T1, typename T2>
ShaderStream &operator()(const T1 &a, const T2 &b)
{
selectStream(a, 0) << map(a) << '\t' << map(b) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e, const T6 &f)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\t'
<< map(f) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e, const T6 &f, const T7 &g)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\t'
<< map(f) << '\t' << map(g) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e, const T6 &f, const T7 &g,
const T8 &h)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\t'
<< map(f) << '\t' << map(g) << '\t' << map(h) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8,
typename T9>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e, const T6 &f, const T7 &g,
const T8 &h, const T9 &i)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\t'
<< map(f) << '\t' << map(g) << '\t' << map(h) << '\t' << map(i) << '\n';
return *this;
}
template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8,
typename T9, typename T10>
ShaderStream &operator()(const T1 &a, const T2 &b, const T3 &c, const T4 &d, const T5 &e, const T6 &f, const T7 &g,
const T8 &h, const T9 &i, const T10 &k)
{
selectStream(a, c) << map(a) << '\t' << map(b) << '\t' << map(c) << '\t' << map(d) << '\t' << map(e) << '\t'
<< map(f) << '\t' << map(g) << '\t' << map(h) << '\t' << map(i) << '\t' << map(k) << '\n';
return *this;
}
// returns true if two variables has the same in-SPIRV-code names
bool areSame(const Variable a, const Variable b)
{
VariableIt varA = vars.find(a);
VariableIt varB = vars.find(b);
return varA != vars.end() && varB != vars.end() && varA->second == varB->second;
}
// makes variable 'a' in-SPIRV-code name to be the same as variable 'b' in-SPIRV-code name
void makeSame(const Variable a, const Variable b)
{
VariableIt varB = vars.find(b);
if (varB != vars.end())
{
std::pair<VariableIt, bool> inserted = vars.insert(std::make_pair(a, varB->second));
if (!inserted.second)
inserted.first->second = varB->second;
}
}
private:
// generic version of map (tries to push whatever came to stringstream to get its string representation)
template <typename T>
std::string map(const T &a)
{
std::stringstream temp;
temp << a;
return temp.str();
}
// looks for mapping of c++ Variable object onto in-SPIRV-code name.
// if there was not yet such mapping generated a new mapping is created based on incremented local counter.
std::string map(const Variable &a)
{
VariableIt var = vars.find(a);
if (var != vars.end())
return var->second;
std::stringstream temp;
temp << '%';
temp.width(4);
temp.fill('0');
temp << std::hex << varCounter.incrementAndGetValue();
vars.insert(std::make_pair(a, temp.str()));
return temp.str();
}
// a simple specification for Operation
std::string map(const Operation &a)
{
return a.getValue();
}
// a specification for char* - faster than going through stringstream << operator
std::string map(const char *&a)
{
return std::string(a);
}
// a specification for char - faster than going through stringstream << operator
std::string map(const char &a)
{
return std::string(1, a);
}
// a generic version of selectStream - used when neither 1st nor 3rd SPIRV line token is Operation.
// In general should never happen.
// All SPIRV lines are constructed in a one of two forms:
// Variable = Operation operands...
// or
// Operation operands...
// So operation is either 1st or 3rd token.
template <typename T0, typename T1>
std::stringstream &selectStream(const T0 &op0, const T1 &op1)
{
DE_UNREF(op0);
DE_UNREF(op1);
return shaderstream;
}
// Specialisation for Operation being 1st parameter
// Certain operations make the SPIRV code line to be pushed to different substreams.
template <typename T1>
std::stringstream &selectStream(const Operation &op, const T1 &op1)
{
DE_UNREF(op1);
if (op == op::Decorate || op == op::MemberDecorate)
return decorations;
if (op == op::Name || op == op::MemberName)
return names;
if (op == op::Capability || op == op::Extension)
return capabilities;
if (op == op::MemoryModel || op == op::ExecutionMode || op == op::EntryPoint)
return preamble;
return shaderstream;
}
// Specialisation for Operation being 3rd parameter
// Certain operations make the SPIRV code line to be pushed to different substreams.
// If we would like to use this way of generating SPIRV we could use this method as SPIRV line validation point
// e.g. here instead of heving partial specialisation I could specialise for T0 being Variable since this has to match Variable = Operation operands...
template <typename T0>
std::stringstream &selectStream(const T0 &op0, const Operation &op)
{
DE_UNREF(op0);
if (op == op::ExtInstImport)
return preamble;
if (op == op::TypeVoid || op == op::TypeBool || op == op::TypeInt || op == op::TypeFloat ||
op == op::TypeVector || op == op::TypeMatrix)
return basictypes;
if (op == op::TypeArray || op == op::TypeStruct || op == op::TypeFunction || op == op::TypePointer ||
op == op::TypeImage || op == op::TypeSampledImage)
return compositetypes;
if (op == op::Constant)
return constants;
if (op == op::ConstantComposite)
return compositeconstants;
return shaderstream;
}
typedef std::map<Variable, std::string> VariablesPack;
typedef VariablesPack::iterator VariableIt;
// local mappings between c++ Variable objects and in-SPIRV-code names
VariablesPack vars;
// shader substreams
std::stringstream capabilities;
std::stringstream preamble;
std::stringstream names;
std::stringstream decorations;
std::stringstream basictypes;
std::stringstream constants;
std::stringstream compositetypes;
std::stringstream compositeconstants;
std::stringstream shaderstream;
// local incremented counter
Autocounter varCounter;
};
// A suppliementary class to group frequently used Variables together
class Variables
{
public:
Variables(Autocounter &autoincrement)
: version(autoincrement)
, mainFunc(autoincrement)
, mainFuncLabel(autoincrement)
, voidFuncVoid(autoincrement)
, copy_type(autoincrement)
, copy_type_vec(autoincrement)
, buffer_type_vec(autoincrement)
, copy_type_ptr(autoincrement)
, buffer_type(autoincrement)
, voidId(autoincrement)
, v4f32(autoincrement)
, v4s32(autoincrement)
, v4u32(autoincrement)
, v4s64(autoincrement)
, v4u64(autoincrement)
, s32(autoincrement)
, f32(autoincrement)
, u32(autoincrement)
, s64(autoincrement)
, u64(autoincrement)
, boolean(autoincrement)
, array_content_type(autoincrement)
, s32_type_ptr(autoincrement)
, dataSelectorStructPtrType(autoincrement)
, dataSelectorStructPtr(autoincrement)
, dataArrayType(autoincrement)
, dataInput(autoincrement)
, dataInputPtrType(autoincrement)
, dataInputType(autoincrement)
, dataInputSampledType(autoincrement)
, dataOutput(autoincrement)
, dataOutputPtrType(autoincrement)
, dataOutputType(autoincrement)
, dataSelectorStructType(autoincrement)
, input(autoincrement)
, inputPtr(autoincrement)
, output(autoincrement)
, outputPtr(autoincrement)
{
for (uint32_t i = 0; i < 32; ++i)
constants.push_back(Variable(autoincrement));
}
const Variable version;
const Variable mainFunc;
const Variable mainFuncLabel;
const Variable voidFuncVoid;
std::vector<Variable> constants;
const Variable copy_type;
const Variable copy_type_vec;
const Variable buffer_type_vec;
const Variable copy_type_ptr;
const Variable buffer_type;
const Variable voidId;
const Variable v4f32;
const Variable v4s32;
const Variable v4u32;
const Variable v4s64;
const Variable v4u64;
const Variable s32;
const Variable f32;
const Variable u32;
const Variable s64;
const Variable u64;
const Variable boolean;
const Variable array_content_type;
const Variable s32_type_ptr;
const Variable dataSelectorStructPtrType;
const Variable dataSelectorStructPtr;
const Variable dataArrayType;
const Variable dataInput;
const Variable dataInputPtrType;
const Variable dataInputType;
const Variable dataInputSampledType;
const Variable dataOutput;
const Variable dataOutputPtrType;
const Variable dataOutputType;
const Variable dataSelectorStructType;
const Variable input;
const Variable inputPtr;
const Variable output;
const Variable outputPtr;
};
// A routing generating SPIRV code for all test cases in this group
std::string MakeShader(VkShaderStageFlags shaderStage, ShaderType shaderType, VkFormat bufferFormat, bool reads,
bool unused)
{
const bool isR64 = (bufferFormat == VK_FORMAT_R64_UINT || bufferFormat == VK_FORMAT_R64_SINT);
// faster to write
const char is = '=';
// variables require such counter to generate their unique ids. Since there is possibility that in the future this code will
// run parallel this counter is made local to this function body to be safe.
Autocounter localcounter;
// A frequently used Variables (gathered into this single object for readability)
Variables var(localcounter);
// A SPIRV code builder
ShaderStream shaderSource;
// A basic preamble of SPIRV shader. Turns on required capabilities and extensions.
shaderSource(op::Capability, "Shader")(op::Capability, "VariablePointersStorageBuffer");
if (isR64)
{
shaderSource(op::Capability, "Int64");
}
shaderSource(op::Extension, "\"SPV_KHR_storage_buffer_storage_class\"")(
op::Extension, "\"SPV_KHR_variable_pointers\"")(var.version, is, op::ExtInstImport,
"\"GLSL.std.450\"")(op::MemoryModel, "Logical", "GLSL450");
// Use correct entry point definition depending on shader stage
if (shaderStage == VK_SHADER_STAGE_COMPUTE_BIT)
{
shaderSource(op::EntryPoint, "GLCompute", var.mainFunc, "\"main\"")(op::ExecutionMode, var.mainFunc,
"LocalSize", 1, 1, 1);
}
else if (shaderStage == VK_SHADER_STAGE_VERTEX_BIT)
{
shaderSource(op::EntryPoint, "Vertex", var.mainFunc, "\"main\"", var.input, var.output)(
op::Decorate, var.output, "BuiltIn", "Position")(op::Decorate, var.input, "Location", 0);
}
else if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
shaderSource(op::EntryPoint, "Fragment", var.mainFunc, "\"main\"", var.output)(
op::ExecutionMode, var.mainFunc, "OriginUpperLeft")(op::Decorate, var.output, "Location", 0);
}
// If we are testing vertex shader or fragment shader we need to provide the other one for the pipeline too.
// So the not tested one is 'unused'. It is then a minimal/simplest possible pass-through shader.
// If we are testing compute shader we dont need unused shader at all.
if (unused)
{
if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
shaderSource(var.voidId, is, op::TypeVoid)(var.voidFuncVoid, is, op::TypeFunction, var.voidId)(
var.f32, is, op::TypeFloat, 32)(var.v4f32, is, op::TypeVector, var.f32,
4)(var.outputPtr, is, op::TypePointer, "Output", var.v4f32)(
var.output, is, op::Variable, var.outputPtr, "Output")(var.constants[6], is, op::Constant, var.f32, 1)(
var.constants[7], is, op::ConstantComposite, var.v4f32, var.constants[6], var.constants[6],
var.constants[6], var.constants[6])(var.mainFunc, is, op::Function, var.voidId, "None",
var.voidFuncVoid)(var.mainFuncLabel, is, op::Label);
}
else if (shaderStage == VK_SHADER_STAGE_VERTEX_BIT)
{
shaderSource(var.voidId, is, op::TypeVoid)(var.voidFuncVoid, is, op::TypeFunction, var.voidId)(
var.f32, is, op::TypeFloat, 32)(var.v4f32, is, op::TypeVector, var.f32,
4)(var.outputPtr, is, op::TypePointer, "Output", var.v4f32)(
var.output, is, op::Variable, var.outputPtr, "Output")(var.inputPtr, is, op::TypePointer, "Input",
var.v4f32)(
var.input, is, op::Variable, var.inputPtr, "Input")(var.mainFunc, is, op::Function, var.voidId, "None",
var.voidFuncVoid)(var.mainFuncLabel, is, op::Label);
}
}
else // this is a start of actual shader that tests variable pointers
{
shaderSource(op::Decorate, var.dataInput, "DescriptorSet", 0)(op::Decorate, var.dataInput, "Binding", 0)
(op::Decorate, var.dataOutput, "DescriptorSet", 0)(op::Decorate, var.dataOutput, "Binding", 1);
// for scalar types and vector types we use 1024 element array of 4 elements arrays of 4-component vectors
// so the stride of internal array is size of 4-component vector
if (shaderType == SHADER_TYPE_SCALAR_COPY || shaderType == SHADER_TYPE_VECTOR_COPY)
{
if (isR64)
{
shaderSource(op::Decorate, var.array_content_type, "ArrayStride", 32);
}
else
{
shaderSource(op::Decorate, var.array_content_type, "ArrayStride", 16);
}
}
if (isR64)
{
shaderSource(op::Decorate, var.dataArrayType, "ArrayStride", 128);
}
else
{
// for matrices we use array of 4x4-component matrices
// stride of outer array is then 64 in every case
shaderSource(op::Decorate, var.dataArrayType, "ArrayStride", 64);
}
// an output block
shaderSource(op::MemberDecorate, var.dataOutputType, 0, "Offset", 0)(op::Decorate, var.dataOutputType, "Block")
// an input block. Marked readonly.
(op::MemberDecorate, var.dataInputType, 0, "NonWritable")(
op::MemberDecorate, var.dataInputType, 0, "Offset", 0)(op::Decorate, var.dataInputType, "Block")
//a special structure matching data in one of our buffers.
// member at 0 is an index to read position
// member at 1 is an index to write position
// member at 2 is always zero. It is used to perform OpSelect. I used value coming from buffer to avoid incidental optimisations that could prune OpSelect if the value was compile time known.
(op::MemberDecorate, var.dataSelectorStructType, 0, "Offset",
0)(op::MemberDecorate, var.dataSelectorStructType, 1, "Offset",
4)(op::MemberDecorate, var.dataSelectorStructType, 2, "Offset", 8)(op::Decorate,
var.dataSelectorStructType, "Block")
// binding to matching buffer
(op::Decorate, var.dataSelectorStructPtr, "DescriptorSet", 0)(op::Decorate, var.dataSelectorStructPtr,
"Binding", 2)
// making composite types used in shader
(var.voidId, is, op::TypeVoid)(var.voidFuncVoid, is, op::TypeFunction, var.voidId)
(var.boolean, is, op::TypeBool)
(var.f32, is, op::TypeFloat, 32)(var.s32, is, op::TypeInt, 32, 1)(var.u32, is, op::TypeInt, 32, 0);
if (isR64)
{
shaderSource(var.s64, is, op::TypeInt, 64, 1)(var.u64, is, op::TypeInt, 64, 0);
}
shaderSource(var.v4f32, is, op::TypeVector, var.f32, 4)(var.v4s32, is, op::TypeVector, var.s32,
4)(var.v4u32, is, op::TypeVector, var.u32, 4);
if (isR64)
{
shaderSource(var.v4s64, is, op::TypeVector, var.s64, 4)(var.v4u64, is, op::TypeVector, var.u64, 4);
}
// since the shared tests scalars, vectors, matrices of ints, uints and floats I am generating alternative names for some of the types so I can use those and not need to use "if" everywhere.
// A Variable mappings will make sure the proper variable name is used
// below is a first part of aliasing types based on int, uint, float
switch (bufferFormat)
{
case vk::VK_FORMAT_R32_SINT:
shaderSource.makeSame(var.buffer_type, var.s32);
shaderSource.makeSame(var.buffer_type_vec, var.v4s32);
break;
case vk::VK_FORMAT_R32_UINT:
shaderSource.makeSame(var.buffer_type, var.u32);
shaderSource.makeSame(var.buffer_type_vec, var.v4u32);
break;
case vk::VK_FORMAT_R32_SFLOAT:
shaderSource.makeSame(var.buffer_type, var.f32);
shaderSource.makeSame(var.buffer_type_vec, var.v4f32);
break;
case vk::VK_FORMAT_R64_SINT:
shaderSource.makeSame(var.buffer_type, var.s64);
shaderSource.makeSame(var.buffer_type_vec, var.v4s64);
break;
case vk::VK_FORMAT_R64_UINT:
shaderSource.makeSame(var.buffer_type, var.u64);
shaderSource.makeSame(var.buffer_type_vec, var.v4u64);
break;
default:
// to prevent compiler from complaining not all cases are handled (but we should not get here).
deAssertFail("This point should be not reachable with correct program flow.", __FILE__, __LINE__);
break;
}
// below is a second part that aliases based on scalar, vector, matrix
switch (shaderType)
{
case SHADER_TYPE_SCALAR_COPY:
shaderSource.makeSame(var.copy_type, var.buffer_type);
break;
case SHADER_TYPE_VECTOR_COPY:
shaderSource.makeSame(var.copy_type, var.buffer_type_vec);
break;
case SHADER_TYPE_MATRIX_COPY:
if (bufferFormat != VK_FORMAT_R32_SFLOAT)
TCU_THROW(NotSupportedError, "Matrices can be used only with floating point types.");
shaderSource(var.copy_type, is, op::TypeMatrix, var.buffer_type_vec, 4);
break;
default:
// to prevent compiler from complaining not all cases are handled (but we should not get here).
deAssertFail("This point should be not reachable with correct program flow.", __FILE__, __LINE__);
break;
}
// I will need some constants so lets add them to shader source
shaderSource(var.constants[0], is, op::Constant, var.s32, 0)(var.constants[1], is, op::Constant, var.s32, 1)(
var.constants[2], is, op::Constant, var.s32, 2)(var.constants[3], is, op::Constant, var.s32, 3)(
var.constants[4], is, op::Constant, var.u32, 4)(var.constants[5], is, op::Constant, var.u32, 1024);
// for fragment shaders I need additionally a constant vector (output "colour") so lets make it
if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
shaderSource(var.constants[6], is, op::Constant, var.f32, 1)(var.constants[7], is, op::ConstantComposite,
var.v4f32, var.constants[6], var.constants[6],
var.constants[6], var.constants[6]);
}
// additional alias for the type of content of this 1024-element outer array.
if (shaderType == SHADER_TYPE_SCALAR_COPY || shaderType == SHADER_TYPE_VECTOR_COPY)
{
shaderSource(var.array_content_type, is, op::TypeArray, var.buffer_type_vec, var.constants[4]);
}
else
{
shaderSource.makeSame(var.array_content_type, var.copy_type);
}
// Lets create pointer types to the input data type, output data type and a struct
// This must be distinct types due to different type decorations
// Lets make also actual poiters to the data
shaderSource(var.dataArrayType, is, op::TypeArray, var.array_content_type,
var.constants[5])(var.dataInputType, is, op::TypeStruct,
var.dataArrayType)(var.dataOutputType, is, op::TypeStruct, var.dataArrayType)(
var.dataInputPtrType, is, op::TypePointer, "StorageBuffer",
var.dataInputType)(var.dataOutputPtrType, is, op::TypePointer, "StorageBuffer", var.dataOutputType)(
var.dataInput, is, op::Variable, var.dataInputPtrType,
"StorageBuffer")(var.dataOutput, is, op::Variable, var.dataOutputPtrType, "StorageBuffer")(
var.dataSelectorStructType, is, op::TypeStruct, var.s32, var.s32,
var.s32)(var.dataSelectorStructPtrType, is, op::TypePointer, "Uniform", var.dataSelectorStructType)(
var.dataSelectorStructPtr, is, op::Variable, var.dataSelectorStructPtrType, "Uniform");
// we need also additional pointers to fullfil stage requirements on shaders inputs and outputs
if (shaderStage == VK_SHADER_STAGE_VERTEX_BIT)
{
shaderSource(var.inputPtr, is, op::TypePointer, "Input",
var.v4f32)(var.input, is, op::Variable, var.inputPtr,
"Input")(var.outputPtr, is, op::TypePointer, "Output",
var.v4f32)(var.output, is, op::Variable, var.outputPtr, "Output");
}
else if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
shaderSource(var.outputPtr, is, op::TypePointer, "Output", var.v4f32)(var.output, is, op::Variable,
var.outputPtr, "Output");
}
shaderSource(var.copy_type_ptr, is, op::TypePointer, "StorageBuffer",
var.copy_type)(var.s32_type_ptr, is, op::TypePointer, "Uniform", var.s32);
// Make a shader main function
shaderSource(var.mainFunc, is, op::Function, var.voidId, "None", var.voidFuncVoid)(var.mainFuncLabel, is,
op::Label);
Variable copyFromPtr(localcounter), copyToPtr(localcounter), zeroPtr(localcounter);
Variable copyFrom(localcounter), copyTo(localcounter), zero(localcounter);
// Lets load data from our auxiliary buffer with reading index, writing index and zero.
shaderSource(copyToPtr, is, op::AccessChain, var.s32_type_ptr, var.dataSelectorStructPtr,
var.constants[1])(copyTo, is, op::Load, var.s32, copyToPtr)(
copyFromPtr, is, op::AccessChain, var.s32_type_ptr, var.dataSelectorStructPtr,
var.constants[0])(copyFrom, is, op::Load, var.s32,
copyFromPtr)(zeroPtr, is, op::AccessChain, var.s32_type_ptr, var.dataSelectorStructPtr,
var.constants[2])(zero, is, op::Load, var.s32, zeroPtr);
// let start copying data using variable pointers
switch (shaderType)
{
case SHADER_TYPE_SCALAR_COPY:
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
Variable actualLoadChain(localcounter), actualStoreChain(localcounter), loadResult(localcounter);
Variable selection(localcounter);
Variable lcA(localcounter), lcB(localcounter), scA(localcounter), scB(localcounter);
shaderSource(selection, is, op::IEqual, var.boolean, zero, var.constants[0]);
if (reads)
{
// if we check reads we use variable pointers only for reading part
shaderSource(lcA, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0],
copyFrom, var.constants[i],
var.constants[j])(lcB, is, op::AccessChain, var.copy_type_ptr, var.dataInput,
var.constants[0], copyFrom, var.constants[i], var.constants[j])
// actualLoadChain will be a variable pointer as it was created through OpSelect
(actualLoadChain, is, op::Select, var.copy_type_ptr, selection, lcA, lcB)
// actualStoreChain will be a regular pointer
(actualStoreChain, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0],
copyTo, var.constants[i], var.constants[j]);
}
else
{
// if we check writes we use variable pointers only for writing part only
shaderSource
// actualLoadChain will be regular regualar pointer
(actualLoadChain, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0],
copyFrom, var.constants[i], var.constants[j])(scA, is, op::AccessChain, var.copy_type_ptr,
var.dataOutput, var.constants[0], copyTo,
var.constants[i], var.constants[j])(
scB, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0], copyTo,
var.constants[i], var.constants[j])
// actualStoreChain will be a variable pointer as it was created through OpSelect
(actualStoreChain, is, op::Select, var.copy_type_ptr, selection, scA, scB);
}
// do actual copying
shaderSource(loadResult, is, op::Load, var.copy_type, actualLoadChain)(op::Store, actualStoreChain,
loadResult);
}
}
break;
// cases below have the same logic as the one above - just we are copying bigger chunks of data with every load/store pair
case SHADER_TYPE_VECTOR_COPY:
for (int i = 0; i < 4; ++i)
{
Variable actualLoadChain(localcounter), actualStoreChain(localcounter), loadResult(localcounter);
Variable selection(localcounter);
Variable lcA(localcounter), lcB(localcounter), scA(localcounter), scB(localcounter);
shaderSource(selection, is, op::IEqual, var.boolean, zero, var.constants[0]);
if (reads)
{
shaderSource(lcA, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0], copyFrom,
var.constants[i])(lcB, is, op::AccessChain, var.copy_type_ptr, var.dataInput,
var.constants[0], copyFrom, var.constants[i])(
actualLoadChain, is, op::Select, var.copy_type_ptr, selection, lcA,
lcB)(actualStoreChain, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0],
copyTo, var.constants[i]);
}
else
{
shaderSource(actualLoadChain, is, op::AccessChain, var.copy_type_ptr, var.dataInput,
var.constants[0], copyFrom,
var.constants[i])(scA, is, op::AccessChain, var.copy_type_ptr, var.dataOutput,
var.constants[0], copyTo, var.constants[i])(
scB, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0], copyTo,
var.constants[i])(actualStoreChain, is, op::Select, var.copy_type_ptr, selection, scA, scB);
}
shaderSource(loadResult, is, op::Load, var.copy_type, actualLoadChain)(op::Store, actualStoreChain,
loadResult);
}
break;
case SHADER_TYPE_MATRIX_COPY:
{
Variable actualLoadChain(localcounter), actualStoreChain(localcounter), loadResult(localcounter);
Variable selection(localcounter);
Variable lcA(localcounter), lcB(localcounter), scA(localcounter), scB(localcounter);
shaderSource(selection, is, op::IEqual, var.boolean, zero, var.constants[0]);
if (reads)
{
shaderSource(lcA, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0], copyFrom)(
lcB, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0],
copyFrom)(actualLoadChain, is, op::Select, var.copy_type_ptr, selection, lcA, lcB)(
actualStoreChain, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0], copyTo);
}
else
{
shaderSource(actualLoadChain, is, op::AccessChain, var.copy_type_ptr, var.dataInput, var.constants[0],
copyFrom)(scA, is, op::AccessChain, var.copy_type_ptr, var.dataOutput, var.constants[0],
copyTo)(scB, is, op::AccessChain, var.copy_type_ptr, var.dataOutput,
var.constants[0], copyTo)(actualStoreChain, is, op::Select,
var.copy_type_ptr, selection, scA, scB);
}
shaderSource(loadResult, is, op::Load, var.copy_type, actualLoadChain)(op::Store, actualStoreChain,
loadResult);
}
break;
default:
// to prevent compiler from complaining not all cases are handled (but we should not get here).
deAssertFail("This point should be not reachable with correct program flow.", __FILE__, __LINE__);
break;
}
}
// This is common for test shaders and unused ones
// We need to fill stage ouput from shader properly
// output vertices positions in vertex shader
if (shaderStage == VK_SHADER_STAGE_VERTEX_BIT)
{
Variable inputValue(localcounter), outputLocation(localcounter);
shaderSource(inputValue, is, op::Load, var.v4f32, var.input)(outputLocation, is, op::AccessChain, var.outputPtr,
var.output)(op::Store, outputLocation, inputValue);
}
// output colour in fragment shader
else if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
shaderSource(op::Store, var.output, var.constants[7]);
}
// We are done. Lets close main function body
shaderSource(op::Return)(op::FunctionEnd);
return shaderSource.str();
}
RobustReadTest::RobustReadTest(tcu::TestContext &testContext, const std::string &name, VkShaderStageFlags shaderStage,
ShaderType shaderType, VkFormat bufferFormat, VkDeviceSize readAccessRange,
bool accessOutOfBackingMemory)
: RobustAccessWithPointersTest(testContext, name, shaderStage, shaderType, bufferFormat)
, m_readAccessRange(readAccessRange)
, m_accessOutOfBackingMemory(accessOutOfBackingMemory)
{
}
TestInstance *RobustReadTest::createInstance(Context &context) const
{
#ifndef CTS_USES_VULKANSC
auto device = createRobustBufferAccessVariablePointersDevice(context);
de::MovePtr<vk::DeviceDriver> deviceDriver = de::MovePtr<DeviceDriver>(
new DeviceDriver(context.getPlatformInterface(), context.getInstance(), *device, context.getUsedApiVersion(),
context.getTestContext().getCommandLine()));
#else
de::MovePtr<CustomInstance> customInstance =
de::MovePtr<CustomInstance>(new CustomInstance(createCustomInstanceFromContext(context)));
auto device = createRobustBufferAccessVariablePointersDevice(context, *customInstance);
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver =
de::MovePtr<DeviceDriverSC, DeinitDeviceDeleter>(
new DeviceDriverSC(context.getPlatformInterface(), *customInstance, *device,
context.getTestContext().getCommandLine(), context.getResourceInterface(),
context.getDeviceVulkanSC10Properties(), context.getDeviceProperties(),
context.getUsedApiVersion()),
vk::DeinitDeviceDeleter(context.getResourceInterface().get(), *device));
#endif // CTS_USES_VULKANSC
return new ReadInstance(context, device,
#ifdef CTS_USES_VULKANSC
customInstance,
#endif // CTS_USES_VULKANSC
deviceDriver, m_shaderType, m_shaderStage, m_bufferFormat, m_readAccessRange,
m_accessOutOfBackingMemory);
}
void RobustReadTest::initPrograms(SourceCollections &programCollection) const
{
if (m_shaderStage == VK_SHADER_STAGE_COMPUTE_BIT)
{
programCollection.spirvAsmSources.add("compute")
<< MakeShader(VK_SHADER_STAGE_COMPUTE_BIT, m_shaderType, m_bufferFormat, true, false);
}
else
{
programCollection.spirvAsmSources.add("vertex")
<< MakeShader(VK_SHADER_STAGE_VERTEX_BIT, m_shaderType, m_bufferFormat, true,
m_shaderStage != VK_SHADER_STAGE_VERTEX_BIT);
programCollection.spirvAsmSources.add("fragment")
<< MakeShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_shaderType, m_bufferFormat, true,
m_shaderStage != VK_SHADER_STAGE_FRAGMENT_BIT);
}
}
RobustWriteTest::RobustWriteTest(tcu::TestContext &testContext, const std::string &name, VkShaderStageFlags shaderStage,
ShaderType shaderType, VkFormat bufferFormat, VkDeviceSize writeAccessRange,
bool accessOutOfBackingMemory)
: RobustAccessWithPointersTest(testContext, name, shaderStage, shaderType, bufferFormat)
, m_writeAccessRange(writeAccessRange)
, m_accessOutOfBackingMemory(accessOutOfBackingMemory)
{
}
TestInstance *RobustWriteTest::createInstance(Context &context) const
{
#ifndef CTS_USES_VULKANSC
auto device = createRobustBufferAccessVariablePointersDevice(context);
de::MovePtr<vk::DeviceDriver> deviceDriver = de::MovePtr<DeviceDriver>(
new DeviceDriver(context.getPlatformInterface(), context.getInstance(), *device, context.getUsedApiVersion(),
context.getTestContext().getCommandLine()));
#else
de::MovePtr<CustomInstance> customInstance =
de::MovePtr<CustomInstance>(new CustomInstance(createCustomInstanceFromContext(context)));
auto device = createRobustBufferAccessVariablePointersDevice(context, *customInstance);
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver =
de::MovePtr<DeviceDriverSC, DeinitDeviceDeleter>(
new DeviceDriverSC(context.getPlatformInterface(), *customInstance, *device,
context.getTestContext().getCommandLine(), context.getResourceInterface(),
context.getDeviceVulkanSC10Properties(), context.getDeviceProperties(),
context.getUsedApiVersion()),
vk::DeinitDeviceDeleter(context.getResourceInterface().get(), *device));
#endif // CTS_USES_VULKANSC
return new WriteInstance(context, device,
#ifdef CTS_USES_VULKANSC
customInstance,
#endif
deviceDriver, m_shaderType, m_shaderStage, m_bufferFormat, m_writeAccessRange,
m_accessOutOfBackingMemory);
}
void RobustWriteTest::initPrograms(SourceCollections &programCollection) const
{
if (m_shaderStage == VK_SHADER_STAGE_COMPUTE_BIT)
{
programCollection.spirvAsmSources.add("compute")
<< MakeShader(VK_SHADER_STAGE_COMPUTE_BIT, m_shaderType, m_bufferFormat, false, false);
}
else
{
programCollection.spirvAsmSources.add("vertex")
<< MakeShader(VK_SHADER_STAGE_VERTEX_BIT, m_shaderType, m_bufferFormat, false,
m_shaderStage != VK_SHADER_STAGE_VERTEX_BIT);
programCollection.spirvAsmSources.add("fragment")
<< MakeShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_shaderType, m_bufferFormat, false,
m_shaderStage != VK_SHADER_STAGE_FRAGMENT_BIT);
}
}
AccessInstance::AccessInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
BufferAccessType bufferAccessType, VkDeviceSize inBufferAccessRange,
VkDeviceSize outBufferAccessRange, bool accessOutOfBackingMemory)
: vkt::TestInstance(context)
#ifdef CTS_USES_VULKANSC
, m_customInstance(customInstance)
#endif
, m_device(device)
, m_deviceDriver(deviceDriver)
, m_shaderType(shaderType)
, m_shaderStage(shaderStage)
, m_bufferFormat(bufferFormat)
, m_bufferAccessType(bufferAccessType)
, m_accessOutOfBackingMemory(accessOutOfBackingMemory)
{
tcu::TestLog &log = context.getTestContext().getLog();
const DeviceInterface &vk = *m_deviceDriver;
const auto &vki = context.getInstanceInterface();
const auto instance = context.getInstance();
const uint32_t queueFamilyIndex = context.getUniversalQueueFamilyIndex();
const VkPhysicalDevice physicalDevice = chooseDevice(vki, instance, context.getTestContext().getCommandLine());
SimpleAllocator memAlloc(vk, *m_device, getPhysicalDeviceMemoryProperties(vki, physicalDevice));
DE_ASSERT(RobustAccessWithPointersTest::s_numberOfBytesAccessed % sizeof(uint32_t) == 0);
DE_ASSERT(inBufferAccessRange <= RobustAccessWithPointersTest::s_numberOfBytesAccessed);
DE_ASSERT(outBufferAccessRange <= RobustAccessWithPointersTest::s_numberOfBytesAccessed);
if (m_bufferFormat == VK_FORMAT_R64_UINT || m_bufferFormat == VK_FORMAT_R64_SINT)
{
if (!context.getDeviceFeatures().shaderInt64)
{
TCU_THROW(NotSupportedError, "64-bit integers not supported");
}
}
// Check storage support
if (shaderStage == VK_SHADER_STAGE_VERTEX_BIT)
{
if (!context.getDeviceFeatures().vertexPipelineStoresAndAtomics)
{
TCU_THROW(NotSupportedError, "Stores not supported in vertex stage");
}
}
else if (shaderStage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
if (!context.getDeviceFeatures().fragmentStoresAndAtomics)
{
TCU_THROW(NotSupportedError, "Stores not supported in fragment stage");
}
}
createTestBuffer(context, vk, *m_device, inBufferAccessRange, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memAlloc,
m_inBuffer, m_inBufferAlloc, m_inBufferAccess, &populateBufferWithValues, &m_bufferFormat, false);
createTestBuffer(context, vk, *m_device, outBufferAccessRange, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memAlloc,
m_outBuffer, m_outBufferAlloc, m_outBufferAccess, &populateBufferWithFiller, nullptr, false);
int32_t indices[] = {(m_accessOutOfBackingMemory && (m_bufferAccessType == BUFFER_ACCESS_TYPE_READ_FROM_STORAGE)) ?
static_cast<int32_t>(RobustAccessWithPointersTest::s_testArraySize) - 1 :
0,
(m_accessOutOfBackingMemory && (m_bufferAccessType == BUFFER_ACCESS_TYPE_WRITE_TO_STORAGE)) ?
static_cast<int32_t>(RobustAccessWithPointersTest::s_testArraySize) - 1 :
0,
0};
AccessRangesData indicesAccess;
createTestBuffer(context, vk, *m_device, 3 * sizeof(int32_t), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, memAlloc,
m_indicesBuffer, m_indicesBufferAlloc, indicesAccess, &populateBufferWithCopy, &indices, true);
log << tcu::TestLog::Message << "input buffer - alloc size: " << m_inBufferAccess.allocSize
<< tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "input buffer - max access range: " << m_inBufferAccess.maxAccessRange
<< tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "output buffer - alloc size: " << m_outBufferAccess.allocSize
<< tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "output buffer - max access range: " << m_outBufferAccess.maxAccessRange
<< tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "indices - input offset: " << indices[0] << tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "indices - output offset: " << indices[1] << tcu::TestLog::EndMessage;
log << tcu::TestLog::Message << "indices - additional: " << indices[2] << tcu::TestLog::EndMessage;
// Create descriptor data
{
DescriptorPoolBuilder descriptorPoolBuilder;
descriptorPoolBuilder.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u);
descriptorPoolBuilder.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u);
descriptorPoolBuilder.addType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1u);
m_descriptorPool =
descriptorPoolBuilder.build(vk, *m_device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u);
DescriptorSetLayoutBuilder setLayoutBuilder;
setLayoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_ALL);
setLayoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_ALL);
setLayoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_ALL);
m_descriptorSetLayout = setLayoutBuilder.build(vk, *m_device);
const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, // VkStructureType sType;
nullptr, // const void* pNext;
*m_descriptorPool, // VkDescriptorPool descriptorPool;
1u, // uint32_t setLayoutCount;
&m_descriptorSetLayout.get() // const VkDescriptorSetLayout* pSetLayouts;
};
m_descriptorSet = allocateDescriptorSet(vk, *m_device, &descriptorSetAllocateInfo);
const VkDescriptorBufferInfo inBufferDescriptorInfo =
makeDescriptorBufferInfo(*m_inBuffer, 0ull, m_inBufferAccess.accessRange);
const VkDescriptorBufferInfo outBufferDescriptorInfo =
makeDescriptorBufferInfo(*m_outBuffer, 0ull, m_outBufferAccess.accessRange);
const VkDescriptorBufferInfo indicesBufferDescriptorInfo =
makeDescriptorBufferInfo(*m_indicesBuffer, 0ull, 12ull);
DescriptorSetUpdateBuilder setUpdateBuilder;
setUpdateBuilder.writeSingle(*m_descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0),
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &inBufferDescriptorInfo);
setUpdateBuilder.writeSingle(*m_descriptorSet, DescriptorSetUpdateBuilder::Location::binding(1),
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &outBufferDescriptorInfo);
setUpdateBuilder.writeSingle(*m_descriptorSet, DescriptorSetUpdateBuilder::Location::binding(2),
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, &indicesBufferDescriptorInfo);
setUpdateBuilder.update(vk, *m_device);
}
// Create fence
{
const VkFenceCreateInfo fenceParams = {
VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, // VkStructureType sType;
nullptr, // const void* pNext;
0u // VkFenceCreateFlags flags;
};
m_fence = createFence(vk, *m_device, &fenceParams);
}
// Get queue
vk.getDeviceQueue(*m_device, queueFamilyIndex, 0, &m_queue);
if (m_shaderStage == VK_SHADER_STAGE_COMPUTE_BIT)
{
m_testEnvironment = de::MovePtr<TestEnvironment>(
new ComputeEnvironment(m_context, *m_deviceDriver, *m_device, *m_descriptorSetLayout, *m_descriptorSet));
}
else
{
using tcu::Vec4;
const VkVertexInputBindingDescription vertexInputBindingDescription = {
0u, // uint32_t binding;
sizeof(tcu::Vec4), // uint32_t strideInBytes;
VK_VERTEX_INPUT_RATE_VERTEX // VkVertexInputStepRate inputRate;
};
const VkVertexInputAttributeDescription vertexInputAttributeDescription = {
0u, // uint32_t location;
0u, // uint32_t binding;
VK_FORMAT_R32G32B32A32_SFLOAT, // VkFormat format;
0u // uint32_t offset;
};
AccessRangesData vertexAccess;
const Vec4 vertices[] = {
Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
Vec4(-1.0f, 1.0f, 0.0f, 1.0f),
Vec4(1.0f, -1.0f, 0.0f, 1.0f),
};
const VkDeviceSize vertexBufferSize = static_cast<VkDeviceSize>(sizeof(vertices));
createTestBuffer(context, vk, *m_device, vertexBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, memAlloc,
m_vertexBuffer, m_vertexBufferAlloc, vertexAccess, &populateBufferWithCopy, &vertices, true);
const GraphicsEnvironment::DrawConfig drawWithOneVertexBuffer = {
std::vector<VkBuffer>(1, *m_vertexBuffer), // std::vector<VkBuffer> vertexBuffers;
DE_LENGTH_OF_ARRAY(vertices), // uint32_t vertexCount;
1, // uint32_t instanceCount;
VK_NULL_HANDLE, // VkBuffer indexBuffer;
0u, // uint32_t indexCount;
};
m_testEnvironment = de::MovePtr<TestEnvironment>(new GraphicsEnvironment(
m_context, *m_deviceDriver, *m_device, *m_descriptorSetLayout, *m_descriptorSet,
GraphicsEnvironment::VertexBindings(1, vertexInputBindingDescription),
GraphicsEnvironment::VertexAttributes(1, vertexInputAttributeDescription), drawWithOneVertexBuffer));
}
}
AccessInstance::~AccessInstance()
{
}
// Verifies if the buffer has the value initialized by BufferAccessInstance::populateReadBuffer at a given offset.
bool AccessInstance::isExpectedValueFromInBuffer(VkDeviceSize offsetInBytes, const void *valuePtr,
VkDeviceSize valueSize)
{
DE_ASSERT(offsetInBytes % 4 == 0);
DE_ASSERT(offsetInBytes < m_inBufferAccess.allocSize);
DE_ASSERT(valueSize == 4ull || valueSize == 8ull);
const uint32_t valueIndex = uint32_t(offsetInBytes / 4) + 2;
if (isUintFormat(m_bufferFormat))
{
const uint32_t expectedValues[2] = {valueIndex, valueIndex + 1u};
return !deMemCmp(valuePtr, &expectedValues, (size_t)valueSize);
}
else if (isIntFormat(m_bufferFormat))
{
const int32_t value = -int32_t(valueIndex);
const int32_t expectedValues[2] = {value, value - 1};
return !deMemCmp(valuePtr, &expectedValues, (size_t)valueSize);
}
else if (isFloatFormat(m_bufferFormat))
{
DE_ASSERT(valueSize == 4ull);
const float value = float(valueIndex);
return !deMemCmp(valuePtr, &value, (size_t)valueSize);
}
else
{
DE_ASSERT(false);
return false;
}
}
bool AccessInstance::isOutBufferValueUnchanged(VkDeviceSize offsetInBytes, VkDeviceSize valueSize)
{
DE_ASSERT(valueSize <= 8);
const uint8_t *const outValuePtr = (uint8_t *)m_outBufferAlloc->getHostPtr() + offsetInBytes;
const uint64_t defaultValue = 0xBABABABABABABABAull;
return !deMemCmp(outValuePtr, &defaultValue, (size_t)valueSize);
}
tcu::TestStatus AccessInstance::iterate(void)
{
const DeviceInterface &vk = *m_deviceDriver;
const vk::VkCommandBuffer cmdBuffer = m_testEnvironment->getCommandBuffer();
// Submit command buffer
{
const VkSubmitInfo submitInfo = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, // VkStructureType sType;
nullptr, // const void* pNext;
0u, // uint32_t waitSemaphoreCount;
nullptr, // const VkSemaphore* pWaitSemaphores;
nullptr, // const VkPIpelineStageFlags* pWaitDstStageMask;
1u, // uint32_t commandBufferCount;
&cmdBuffer, // const VkCommandBuffer* pCommandBuffers;
0u, // uint32_t signalSemaphoreCount;
nullptr // const VkSemaphore* pSignalSemaphores;
};
VK_CHECK(vk.resetFences(*m_device, 1, &m_fence.get()));
VK_CHECK(vk.queueSubmit(m_queue, 1, &submitInfo, *m_fence));
VK_CHECK(vk.waitForFences(*m_device, 1, &m_fence.get(), true, ~(0ull) /* infinity */));
}
// Prepare result buffer for read
{
const VkMappedMemoryRange outBufferRange = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, // VkStructureType sType;
nullptr, // const void* pNext;
m_outBufferAlloc->getMemory(), // VkDeviceMemory mem;
0ull, // VkDeviceSize offset;
m_outBufferAccess.allocSize, // VkDeviceSize size;
};
VK_CHECK(vk.invalidateMappedMemoryRanges(*m_device, 1u, &outBufferRange));
}
if (verifyResult())
return tcu::TestStatus::pass("All values OK");
else
return tcu::TestStatus::fail("Invalid value(s) found");
}
bool AccessInstance::verifyResult(bool splitAccess)
{
std::ostringstream logMsg;
tcu::TestLog &log = m_context.getTestContext().getLog();
const bool isReadAccess = (m_bufferAccessType == BUFFER_ACCESS_TYPE_READ_FROM_STORAGE);
const void *inDataPtr = m_inBufferAlloc->getHostPtr();
const void *outDataPtr = m_outBufferAlloc->getHostPtr();
bool allOk = true;
uint32_t valueNdx = 0;
const VkDeviceSize maxAccessRange =
isReadAccess ? m_inBufferAccess.maxAccessRange : m_outBufferAccess.maxAccessRange;
const bool isR64 = (m_bufferFormat == VK_FORMAT_R64_UINT || m_bufferFormat == VK_FORMAT_R64_SINT);
const uint32_t unsplitElementSize = (isR64 ? 8u : 4u);
const uint32_t elementSize = ((isR64 && !splitAccess) ? 8u : 4u);
for (VkDeviceSize offsetInBytes = 0; offsetInBytes < m_outBufferAccess.allocSize; offsetInBytes += elementSize)
{
const uint8_t *outValuePtr = static_cast<const uint8_t *>(outDataPtr) + offsetInBytes;
const size_t outValueSize =
static_cast<size_t>(deMinu64(elementSize, (m_outBufferAccess.allocSize - offsetInBytes)));
if (offsetInBytes >= RobustAccessWithPointersTest::s_numberOfBytesAccessed)
{
// The shader will only write 16 values into the result buffer. The rest of the values
// should remain unchanged or may be modified if we are writing out of bounds.
if (!isOutBufferValueUnchanged(offsetInBytes, outValueSize) &&
(isReadAccess || !isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize, outValuePtr, 4)))
{
logMsg << "\nValue " << valueNdx++ << " has been modified with an unknown value: "
<< *(static_cast<const uint32_t *>(static_cast<const void *>(outValuePtr)));
allOk = false;
}
}
else
{
const int32_t distanceToOutOfBounds =
static_cast<int32_t>(maxAccessRange) - static_cast<int32_t>(offsetInBytes);
bool isOutOfBoundsAccess = false;
logMsg << "\n" << valueNdx++ << ": ";
logValue(logMsg, outValuePtr, m_bufferFormat, outValueSize);
if (m_accessOutOfBackingMemory)
isOutOfBoundsAccess = true;
// Check if the shader operation accessed an operand located less than 16 bytes away
// from the out of bounds address. Less than 32 bytes away for 64 bit accesses.
if (!isOutOfBoundsAccess && distanceToOutOfBounds < (isR64 ? 32 : 16))
{
uint32_t operandSize = 0;
switch (m_shaderType)
{
case SHADER_TYPE_SCALAR_COPY:
operandSize = unsplitElementSize; // Size of scalar
break;
case SHADER_TYPE_VECTOR_COPY:
operandSize = unsplitElementSize * 4; // Size of vec4
break;
case SHADER_TYPE_MATRIX_COPY:
operandSize = unsplitElementSize * 16; // Size of mat4
break;
default:
DE_ASSERT(false);
}
isOutOfBoundsAccess = (((offsetInBytes / operandSize) + 1) * operandSize > maxAccessRange);
}
if (isOutOfBoundsAccess)
{
logMsg << " (out of bounds " << (isReadAccess ? "read" : "write") << ")";
const bool isValuePartiallyOutOfBounds =
((distanceToOutOfBounds > 0) && ((uint32_t)distanceToOutOfBounds < elementSize));
bool isValidValue = false;
if (isValuePartiallyOutOfBounds && !m_accessOutOfBackingMemory)
{
// The value is partially out of bounds
bool isOutOfBoundsPartOk = true;
bool isWithinBoundsPartOk = true;
uint32_t inBoundPartSize = distanceToOutOfBounds;
// For cases that partial element is out of bound, the part within the buffer allocated memory can be buffer content per spec.
// We need to check it as a whole part.
if (offsetInBytes + elementSize > m_inBufferAccess.allocSize)
{
inBoundPartSize =
static_cast<int32_t>(m_inBufferAccess.allocSize) - static_cast<int32_t>(offsetInBytes);
}
if (isReadAccess)
{
isWithinBoundsPartOk = isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize,
outValuePtr, inBoundPartSize);
isOutOfBoundsPartOk = isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize,
(uint8_t *)outValuePtr + inBoundPartSize,
outValueSize - inBoundPartSize);
}
else
{
isWithinBoundsPartOk = isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize,
outValuePtr, inBoundPartSize) ||
isOutBufferValueUnchanged(offsetInBytes, inBoundPartSize);
isOutOfBoundsPartOk =
isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize,
(uint8_t *)outValuePtr + inBoundPartSize,
outValueSize - inBoundPartSize) ||
isOutBufferValueUnchanged(offsetInBytes + inBoundPartSize, outValueSize - inBoundPartSize);
}
logMsg << ", first " << distanceToOutOfBounds << " byte(s) "
<< (isWithinBoundsPartOk ? "OK" : "wrong");
logMsg << ", last " << outValueSize - distanceToOutOfBounds << " byte(s) "
<< (isOutOfBoundsPartOk ? "OK" : "wrong");
isValidValue = isWithinBoundsPartOk && isOutOfBoundsPartOk;
}
else
{
if (isReadAccess)
{
isValidValue =
isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize, outValuePtr, outValueSize);
}
else
{
isValidValue = isOutBufferValueUnchanged(offsetInBytes, outValueSize);
if (!isValidValue)
{
// Out of bounds writes may modify values withing the memory ranges bound to the buffer
isValidValue = isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.allocSize, outValuePtr,
outValueSize);
if (isValidValue)
logMsg << ", OK, written within the memory range bound to the buffer";
}
}
}
if (!isValidValue && !splitAccess)
{
// Check if we are satisfying the [0, 0, 0, x] pattern, where x may be either 0 or 1,
// or the maximum representable positive integer value (if the format is integer-based).
const bool canMatchVec4Pattern =
(isReadAccess && !isValuePartiallyOutOfBounds && (m_shaderType == SHADER_TYPE_VECTOR_COPY) &&
(offsetInBytes / elementSize + 1) % 4 == 0);
bool matchesVec4Pattern = false;
if (canMatchVec4Pattern)
{
matchesVec4Pattern = verifyOutOfBoundsVec4(outValuePtr - 3u * elementSize, m_bufferFormat);
}
if (!canMatchVec4Pattern || !matchesVec4Pattern)
{
logMsg << ". Failed: ";
if (isReadAccess)
{
logMsg << "expected value within the buffer range or 0";
if (canMatchVec4Pattern)
logMsg << ", or the [0, 0, 0, x] pattern";
}
else
{
logMsg << "written out of the range";
}
allOk = false;
}
}
}
else // We are within bounds
{
if (isReadAccess)
{
if (!isExpectedValueFromInBuffer(offsetInBytes, outValuePtr, elementSize))
{
logMsg << ", Failed: unexpected value";
allOk = false;
}
}
else
{
// Out of bounds writes may change values within the bounds.
if (!isValueWithinBufferOrZero(inDataPtr, m_inBufferAccess.accessRange, outValuePtr, elementSize))
{
logMsg << ", Failed: unexpected value";
allOk = false;
}
}
}
}
}
log << tcu::TestLog::Message << logMsg.str() << tcu::TestLog::EndMessage;
if (!allOk && unsplitElementSize > 4u && !splitAccess)
{
// "Non-atomic accesses to storage buffers that are a multiple of 32 bits may be decomposed into 32-bit accesses that are individually bounds-checked."
return verifyResult(true /*splitAccess*/);
}
return allOk;
}
// BufferReadInstance
ReadInstance::ReadInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
//bool readFromStorage,
VkDeviceSize inBufferAccessRange, bool accessOutOfBackingMemory)
: AccessInstance(context, device,
#ifdef CTS_USES_VULKANSC
customInstance,
#endif // CTS_USES_VULKANSC
deviceDriver, shaderType, shaderStage, bufferFormat, BUFFER_ACCESS_TYPE_READ_FROM_STORAGE,
inBufferAccessRange, RobustAccessWithPointersTest::s_numberOfBytesAccessed,
accessOutOfBackingMemory)
{
}
// BufferWriteInstance
WriteInstance::WriteInstance(Context &context, Move<VkDevice> device,
#ifndef CTS_USES_VULKANSC
de::MovePtr<vk::DeviceDriver> deviceDriver,
#else
de::MovePtr<CustomInstance> customInstance,
de::MovePtr<vk::DeviceDriverSC, vk::DeinitDeviceDeleter> deviceDriver,
#endif // CTS_USES_VULKANSC
ShaderType shaderType, VkShaderStageFlags shaderStage, VkFormat bufferFormat,
VkDeviceSize writeBufferAccessRange, bool accessOutOfBackingMemory)
: AccessInstance(context, device,
#ifdef CTS_USES_VULKANSC
customInstance,
#endif // CTS_USES_VULKANSC
deviceDriver, shaderType, shaderStage, bufferFormat, BUFFER_ACCESS_TYPE_WRITE_TO_STORAGE,
RobustAccessWithPointersTest::s_numberOfBytesAccessed, writeBufferAccessRange,
accessOutOfBackingMemory)
{
}
} // unnamed namespace
tcu::TestCaseGroup *createBufferAccessWithVariablePointersTests(tcu::TestContext &testCtx)
{
// Lets make group for the tests
de::MovePtr<tcu::TestCaseGroup> bufferAccessWithVariablePointersTests(
new tcu::TestCaseGroup(testCtx, "through_pointers"));
// Lets add subgroups to better organise tests
de::MovePtr<tcu::TestCaseGroup> computeWithVariablePointersTests(new tcu::TestCaseGroup(testCtx, "compute"));
de::MovePtr<tcu::TestCaseGroup> computeReads(new tcu::TestCaseGroup(testCtx, "reads"));
de::MovePtr<tcu::TestCaseGroup> computeWrites(new tcu::TestCaseGroup(testCtx, "writes"));
de::MovePtr<tcu::TestCaseGroup> graphicsWithVariablePointersTests(new tcu::TestCaseGroup(testCtx, "graphics"));
de::MovePtr<tcu::TestCaseGroup> graphicsReads(new tcu::TestCaseGroup(testCtx, "reads"));
de::MovePtr<tcu::TestCaseGroup> graphicsReadsVertex(new tcu::TestCaseGroup(testCtx, "vertex"));
de::MovePtr<tcu::TestCaseGroup> graphicsReadsFragment(new tcu::TestCaseGroup(testCtx, "fragment"));
de::MovePtr<tcu::TestCaseGroup> graphicsWrites(new tcu::TestCaseGroup(testCtx, "writes"));
de::MovePtr<tcu::TestCaseGroup> graphicsWritesVertex(new tcu::TestCaseGroup(testCtx, "vertex"));
de::MovePtr<tcu::TestCaseGroup> graphicsWritesFragment(new tcu::TestCaseGroup(testCtx, "fragment"));
// A struct for describing formats
struct Formats
{
const VkFormat value;
const char *const name;
};
const Formats bufferFormats[] = {
{VK_FORMAT_R32_SINT, "s32"}, {VK_FORMAT_R32_UINT, "u32"}, {VK_FORMAT_R32_SFLOAT, "f32"},
{VK_FORMAT_R64_SINT, "s64"}, {VK_FORMAT_R64_UINT, "u64"},
};
const uint8_t bufferFormatsCount = static_cast<uint8_t>(DE_LENGTH_OF_ARRAY(bufferFormats));
// Amounts of data to copy
const VkDeviceSize rangeSizes[] = {1ull, 3ull, 4ull, 16ull, 32ull};
const uint8_t rangeSizesCount = static_cast<uint8_t>(DE_LENGTH_OF_ARRAY(rangeSizes));
// gather above data into one array
const struct ShaderTypes
{
const ShaderType value;
const char *const name;
const Formats *const formats;
const uint8_t formatsCount;
const VkDeviceSize *const sizes;
const uint8_t sizesCount;
} types[] = {{SHADER_TYPE_VECTOR_COPY, "vec4", bufferFormats, bufferFormatsCount, rangeSizes, rangeSizesCount},
{SHADER_TYPE_SCALAR_COPY, "scalar", bufferFormats, bufferFormatsCount, rangeSizes, rangeSizesCount}};
// Specify to which subgroups put various tests
const struct ShaderStages
{
VkShaderStageFlags stage;
de::MovePtr<tcu::TestCaseGroup> &reads;
de::MovePtr<tcu::TestCaseGroup> &writes;
} stages[] = {{VK_SHADER_STAGE_VERTEX_BIT, graphicsReadsVertex, graphicsWritesVertex},
{VK_SHADER_STAGE_FRAGMENT_BIT, graphicsReadsFragment, graphicsWritesFragment},
{VK_SHADER_STAGE_COMPUTE_BIT, computeReads, computeWrites}};
// Eventually specify if memory used should be in the "inaccesible" portion of buffer or entirely outside of buffer
const char *const backingMemory[] = {"in_memory", "out_of_memory"};
for (int32_t stageId = 0; stageId < DE_LENGTH_OF_ARRAY(stages); ++stageId)
for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); ++i)
for (int j = 0; j < types[i].formatsCount; ++j)
for (int k = 0; k < types[i].sizesCount; ++k)
for (int s = 0; s < DE_LENGTH_OF_ARRAY(backingMemory); ++s)
{
std::ostringstream name;
name << types[i].sizes[k] << "B_" << backingMemory[s] << "_with_" << types[i].name << '_'
<< types[i].formats[j].name;
stages[stageId].reads->addChild(
new RobustReadTest(testCtx, name.str().c_str(), stages[stageId].stage, types[i].value,
types[i].formats[j].value, types[i].sizes[k], s != 0));
}
for (int32_t stageId = 0; stageId < DE_LENGTH_OF_ARRAY(stages); ++stageId)
for (int i = 0; i < DE_LENGTH_OF_ARRAY(types); ++i)
for (int j = 0; j < types[i].formatsCount; ++j)
for (int k = 0; k < types[i].sizesCount; ++k)
for (int s = 0; s < DE_LENGTH_OF_ARRAY(backingMemory); ++s)
{
std::ostringstream name;
name << types[i].sizes[k] << "B_" << backingMemory[s] << "_with_" << types[i].name << '_'
<< types[i].formats[j].name;
stages[stageId].writes->addChild(
new RobustWriteTest(testCtx, name.str().c_str(), stages[stageId].stage, types[i].value,
types[i].formats[j].value, types[i].sizes[k], s != 0));
}
graphicsReads->addChild(graphicsReadsVertex.release());
graphicsReads->addChild(graphicsReadsFragment.release());
graphicsWrites->addChild(graphicsWritesVertex.release());
graphicsWrites->addChild(graphicsWritesFragment.release());
graphicsWithVariablePointersTests->addChild(graphicsReads.release());
graphicsWithVariablePointersTests->addChild(graphicsWrites.release());
computeWithVariablePointersTests->addChild(computeReads.release());
computeWithVariablePointersTests->addChild(computeWrites.release());
bufferAccessWithVariablePointersTests->addChild(graphicsWithVariablePointersTests.release());
bufferAccessWithVariablePointersTests->addChild(computeWithVariablePointersTests.release());
return bufferAccessWithVariablePointersTests.release();
}
} // namespace robustness
} // namespace vkt