Refactor: Compatible compute and graphics VerifyIO
[platform/upstream/VK-GL-CTS.git] / external / vulkancts / modules / vulkan / spirv_assembly / vktSpvAsmComputeShaderTestUtil.hpp
1 #ifndef _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP
2 #define _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP
3 /*-------------------------------------------------------------------------
4  * Vulkan Conformance Tests
5  * ------------------------
6  *
7  * Copyright (c) 2015 Google Inc.
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  *//*!
22  * \file
23  * \brief Compute Shader Based Test Case Utility Structs/Functions
24  *//*--------------------------------------------------------------------*/
25
26 #include "deDefs.h"
27 #include "deFloat16.h"
28 #include "deRandom.hpp"
29 #include "tcuTestLog.hpp"
30 #include "tcuVector.hpp"
31 #include "tcuTestLog.hpp"
32 #include "vkMemUtil.hpp"
33 #include "vktSpvAsmUtils.hpp"
34
35 #include <string>
36 #include <vector>
37 #include <map>
38
39 using namespace vk;
40
41 namespace vkt
42 {
43 namespace SpirVAssembly
44 {
45
46 enum OpAtomicType
47 {
48         OPATOMIC_IADD = 0,
49         OPATOMIC_ISUB,
50         OPATOMIC_IINC,
51         OPATOMIC_IDEC,
52         OPATOMIC_LOAD,
53         OPATOMIC_STORE,
54         OPATOMIC_COMPEX,
55
56         OPATOMIC_LAST
57 };
58
59 enum BufferType
60 {
61         BUFFERTYPE_INPUT = 0,
62         BUFFERTYPE_EXPECTED,
63         BUFFERTYPE_ATOMIC_RET,
64
65         BUFFERTYPE_LAST
66 };
67
68 static void fillRandomScalars (de::Random& rnd, deInt32 minValue, deInt32 maxValue, deInt32* dst, deInt32 numValues)
69 {
70         for (int i = 0; i < numValues; i++)
71                 dst[i] = rnd.getInt(minValue, maxValue);
72 }
73
74 /*--------------------------------------------------------------------*//*!
75 * \brief Concrete class for an input/output storage buffer object used for OpAtomic tests
76 *//*--------------------------------------------------------------------*/
77 class OpAtomicBuffer : public BufferInterface
78 {
79 public:
80                                                 OpAtomicBuffer          (const deUint32 numInputElements, const deUint32 numOuptutElements, const OpAtomicType opAtomic, const BufferType type)
81                                                         : m_numInputElements    (numInputElements)
82                                                         , m_numOutputElements   (numOuptutElements)
83                                                         , m_opAtomic                    (opAtomic)
84                                                         , m_type                                (type)
85                                                 {}
86
87         void getBytes (std::vector<deUint8>& bytes) const
88         {
89                 std::vector<deInt32>    inputInts       (m_numInputElements, 0);
90                 de::Random                              rnd                     (m_opAtomic);
91
92                 fillRandomScalars(rnd, 1, 100, &inputInts.front(), m_numInputElements);
93
94                 // Return input values as is
95                 if (m_type == BUFFERTYPE_INPUT)
96                 {
97                         size_t                                  inputSize       = m_numInputElements * sizeof(deInt32);
98
99                         bytes.resize(inputSize);
100                         deMemcpy(&bytes.front(), &inputInts.front(), inputSize);
101                 }
102                 // Calculate expected output values
103                 else if (m_type == BUFFERTYPE_EXPECTED)
104                 {
105                         size_t                                  outputSize      = m_numOutputElements * sizeof(deInt32);
106                         bytes.resize(outputSize, 0xffu);
107
108                         for (size_t ndx = 0; ndx < m_numInputElements; ndx++)
109                         {
110                                 deInt32* const bytesAsInt = reinterpret_cast<deInt32*>(&bytes.front());
111
112                                 switch (m_opAtomic)
113                                 {
114                                         case OPATOMIC_IADD:             bytesAsInt[0] += inputInts[ndx];                                                break;
115                                         case OPATOMIC_ISUB:             bytesAsInt[0] -= inputInts[ndx];                                                break;
116                                         case OPATOMIC_IINC:             bytesAsInt[0]++;                                                                                break;
117                                         case OPATOMIC_IDEC:             bytesAsInt[0]--;                                                                                break;
118                                         case OPATOMIC_LOAD:             bytesAsInt[ndx] = inputInts[ndx];                                               break;
119                                         case OPATOMIC_STORE:    bytesAsInt[ndx] = inputInts[ndx];                                               break;
120                                         case OPATOMIC_COMPEX:   bytesAsInt[ndx] = (inputInts[ndx] % 2) == 0 ? -1 : 1;   break;
121                                         default:                                DE_FATAL("Unknown OpAtomic type");
122                                 }
123                         }
124                 }
125                 else if (m_type == BUFFERTYPE_ATOMIC_RET)
126                 {
127                         bytes.resize(m_numInputElements * sizeof(deInt32), 0xff);
128
129                         if (m_opAtomic == OPATOMIC_COMPEX)
130                         {
131                                 deInt32* const bytesAsInt = reinterpret_cast<deInt32*>(&bytes.front());
132                                 for (size_t ndx = 0; ndx < m_numInputElements; ndx++)
133                                         bytesAsInt[ndx] = inputInts[ndx] % 2;
134                         }
135                 }
136                 else
137                         DE_FATAL("Unknown buffer type");
138         }
139
140         size_t getByteSize (void) const
141         {
142                 switch (m_type)
143                 {
144                         case BUFFERTYPE_ATOMIC_RET:
145                         case BUFFERTYPE_INPUT:
146                                 return m_numInputElements * sizeof(deInt32);
147                         case BUFFERTYPE_EXPECTED:
148                                 return m_numOutputElements * sizeof(deInt32);
149                         default:
150                                 DE_FATAL("Unknown buffer type");
151                                 return 0;
152                 }
153         }
154
155         template <int OpAtomic>
156         static bool compareWithRetvals (const std::vector<Resource>& inputs, const std::vector<AllocationSp>& outputAllocs, const std::vector<Resource>& expectedOutputs, tcu::TestLog& log)
157         {
158                 if (outputAllocs.size() != 2 || inputs.size() != 1)
159                         DE_FATAL("Wrong number of buffers to compare");
160
161                 for (size_t i = 0; i < outputAllocs.size(); ++i)
162                 {
163                         const deUint32* values = reinterpret_cast<deUint32*>(outputAllocs[i]->getHostPtr());
164
165                         if (i == 1 && OpAtomic != OPATOMIC_COMPEX)
166                         {
167                                 // BUFFERTYPE_ATOMIC_RET for arithmetic operations must be verified manually by matching return values to inputs
168                                 std::vector<deUint8>    inputBytes;
169                                 inputs[0].getBytes(inputBytes);
170
171                                 const deUint32*                 inputValues                     = reinterpret_cast<deUint32*>(&inputBytes.front());
172                                 const size_t                    inputValuesCount        = inputBytes.size() / sizeof(deUint32);
173
174                                 // result of all atomic operations
175                                 const deUint32                  resultValue                     = *reinterpret_cast<deUint32*>(outputAllocs[0]->getHostPtr());
176
177                                 if (!compareRetVals<OpAtomic>(inputValues, inputValuesCount, resultValue, values))
178                                 {
179                                         log << tcu::TestLog::Message << "Wrong contents of buffer with return values after atomic operation." << tcu::TestLog::EndMessage;
180                                         return false;
181                                 }
182                         }
183                         else
184                         {
185                                 const BufferSp&                 expectedOutput = expectedOutputs[i].getBuffer();
186                                 std::vector<deUint8>    expectedBytes;
187
188                                 expectedOutput->getBytes(expectedBytes);
189
190                                 if (deMemCmp(&expectedBytes.front(), values, expectedBytes.size()))
191                                 {
192                                         log << tcu::TestLog::Message << "Wrong contents of buffer after atomic operation" << tcu::TestLog::EndMessage;
193                                         return false;
194                                 }
195                         }
196                 }
197                 return true;
198         }
199
200         template <int OpAtomic>
201         static bool compareRetVals (const deUint32* inputValues, const size_t inputValuesCount, const deUint32 resultValue, const deUint32* returnValues)
202         {
203                 // as the order of execution is undefined, validation of return values for atomic operations is tricky:
204                 // each inputValue stands for one atomic operation. Iterate through all of
205                 // done operations in time, each time finding one matching current result and un-doing it.
206
207                 std::vector<bool>               operationsUndone (inputValuesCount, false);
208                 deUint32                                currentResult    = resultValue;
209
210                 for (size_t operationUndone = 0; operationUndone < inputValuesCount; ++operationUndone)
211                 {
212                         // find which of operations was done at this moment
213                         size_t ndx;
214                         for (ndx = 0; ndx < inputValuesCount; ++ndx)
215                         {
216                                 if (operationsUndone[ndx]) continue;
217
218                                 deUint32 previousResult = currentResult;
219
220                                 switch (OpAtomic)
221                                 {
222                                         // operations are undone here, so the actual opeation is reversed
223                                         case OPATOMIC_IADD:             previousResult -= inputValues[ndx];                                             break;
224                                         case OPATOMIC_ISUB:             previousResult += inputValues[ndx];                                             break;
225                                         case OPATOMIC_IINC:             previousResult--;                                                                               break;
226                                         case OPATOMIC_IDEC:             previousResult++;                                                                               break;
227                                         default:                                DE_FATAL("Unsupported OpAtomic type for return value compare");
228                                 }
229
230                                 if (previousResult == returnValues[ndx])
231                                 {
232                                         // found matching operation
233                                         currentResult                   = returnValues[ndx];
234                                         operationsUndone[ndx]   = true;
235                                         break;
236                                 }
237                         }
238                         if (ndx == inputValuesCount)
239                         {
240                                 // no operation matches the current result value
241                                 return false;
242                         }
243                 }
244                 return true;
245         }
246
247 private:
248         const deUint32          m_numInputElements;
249         const deUint32          m_numOutputElements;
250         const OpAtomicType      m_opAtomic;
251         const BufferType        m_type;
252 };
253
254 /*--------------------------------------------------------------------*//*!
255  * \brief Concrete class for an input/output storage buffer object
256  *//*--------------------------------------------------------------------*/
257 template<typename E>
258 class Buffer : public BufferInterface
259 {
260 public:
261                                                 Buffer                          (const std::vector<E>& elements)
262                                                         : m_elements(elements)
263                                                 {}
264
265         void getBytes (std::vector<deUint8>& bytes) const
266         {
267                 const size_t size = m_elements.size() * sizeof(E);
268                 bytes.resize(size);
269                 deMemcpy(&bytes.front(), &m_elements.front(), size);
270         }
271
272         size_t getByteSize (void) const
273         {
274                 return m_elements.size() * sizeof(E);
275         }
276
277 private:
278         std::vector<E>          m_elements;
279 };
280
281 DE_STATIC_ASSERT(sizeof(tcu::Vec4) == 4 * sizeof(float));
282
283 typedef Buffer<float>           Float32Buffer;
284 typedef Buffer<deFloat16>       Float16Buffer;
285 typedef Buffer<deInt64>         Int64Buffer;
286 typedef Buffer<deInt32>         Int32Buffer;
287 typedef Buffer<deInt16>         Int16Buffer;
288 typedef Buffer<deUint16>        Uint16Buffer;
289 typedef Buffer<deInt8>          Int8Buffer;
290 typedef Buffer<deUint32>        Uint32Buffer;
291 typedef Buffer<deUint64>        Uint64Buffer;
292 typedef Buffer<tcu::Vec4>       Vec4Buffer;
293
294 typedef bool (*ComputeVerifyBinaryFunc) (const ProgramBinary&   binary);
295
296 /*--------------------------------------------------------------------*//*!
297  * \brief Specification for a compute shader.
298  *
299  * This struct bundles SPIR-V assembly code, input and expected output
300  * together.
301  *//*--------------------------------------------------------------------*/
302 struct ComputeShaderSpec
303 {
304         std::string                                                             assembly;
305         std::string                                                             entryPoint;
306         std::vector<Resource>                                   inputs;
307         std::vector<Resource>                                   outputs;
308         tcu::IVec3                                                              numWorkGroups;
309         SpecConstants                                                   specConstants;
310         BufferSp                                                                pushConstants;
311         std::vector<std::string>                                extensions;
312         VulkanFeatures                                                  requestedVulkanFeatures;
313         qpTestResult                                                    failResult;
314         std::string                                                             failMessage;
315         // If null, a default verification will be performed by comparing the memory pointed to by outputAllocations
316         // and the contents of expectedOutputs. Otherwise the function pointed to by verifyIO will be called.
317         // If true is returned, then the test case is assumed to have passed, if false is returned, then the test
318         // case is assumed to have failed. Exact meaning of failure can be customized with failResult.
319         VerifyIOFunc                                                    verifyIO;
320         ComputeVerifyBinaryFunc                                 verifyBinary;
321         SpirvVersion                                                    spirvVersion;
322         bool                                                                    coherentMemory;
323
324                                                                                         ComputeShaderSpec (void)
325                                                                                                 : entryPoint                                    ("main")
326                                                                                                 , pushConstants                                 (DE_NULL)
327                                                                                                 , requestedVulkanFeatures               ()
328                                                                                                 , failResult                                    (QP_TEST_RESULT_FAIL)
329                                                                                                 , failMessage                                   ("Output doesn't match with expected")
330                                                                                                 , verifyIO                                              (DE_NULL)
331                                                                                                 , verifyBinary                                  (DE_NULL)
332                                                                                                 , spirvVersion                                  (SPIRV_VERSION_1_0)
333                                                                                                 , coherentMemory                                (false)
334                                                                                         {}
335 };
336
337 /*--------------------------------------------------------------------*//*!
338  * \brief Helper functions for SPIR-V assembly shared by various tests
339  *//*--------------------------------------------------------------------*/
340
341 const char* getComputeAsmShaderPreamble                         (void);
342 const char* getComputeAsmShaderPreambleWithoutLocalSize         (void);
343 std::string getComputeAsmCommonTypes                            (std::string blockStorageClass = "Uniform");
344 const char*     getComputeAsmCommonInt64Types                   (void);
345
346 /*--------------------------------------------------------------------*//*!
347  * Declares two uniform variables (indata, outdata) of type
348  * "struct { float[] }". Depends on type "f32arr" (for "float[]").
349  *//*--------------------------------------------------------------------*/
350 const char* getComputeAsmInputOutputBuffer                      (void);
351 /*--------------------------------------------------------------------*//*!
352  * Declares buffer type and layout for uniform variables indata and
353  * outdata. Both of them are SSBO bounded to descriptor set 0.
354  * indata is at binding point 0, while outdata is at 1.
355  *//*--------------------------------------------------------------------*/
356 const char* getComputeAsmInputOutputBufferTraits        (void);
357
358 bool verifyOutput                                                                       (const std::vector<Resource>&,
359                                                                                                         const std::vector<AllocationSp>&        outputAllocs,
360                                                                                                         const std::vector<Resource>&            expectedOutputs,
361                                                                                                         tcu::TestLog&                                           log);
362
363                                                                                                         // Creates vertex-shader assembly by specializing a boilerplate StringTemplate
364
365 std::string makeComputeShaderAssembly(const std::map<std::string, std::string>& fragments);
366
367 } // SpirVAssembly
368 } // vkt
369
370 #endif // _VKTSPVASMCOMPUTESHADERTESTUTIL_HPP