• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/verifier.h"
16 
17 #include <cassert>
18 #include <cmath>
19 #include <cstring>
20 #include <string>
21 #include <vector>
22 
23 #include "src/command.h"
24 
25 namespace amber {
26 namespace {
27 
28 const uint32_t kBitsPerByte = 8;
29 const double kEpsilon = 0.000001;
30 const double kDefaultTexelTolerance = 0.002;
31 
32 // Copy [src_bit_offset, src_bit_offset + bits) bits of |src| to
33 // [0, bits) of |dst|.
CopyBitsOfMemoryToBuffer(uint8_t * dst,const uint8_t * src,uint32_t src_bit_offset,uint32_t bits)34 void CopyBitsOfMemoryToBuffer(uint8_t* dst,
35                               const uint8_t* src,
36                               uint32_t src_bit_offset,
37                               uint32_t bits) {
38   while (src_bit_offset > 7) {
39     ++src;
40     src_bit_offset = src_bit_offset - kBitsPerByte;
41   }
42 
43   // Number of bytes greater than or equal to |(src_bit_offset + bits) / 8|.
44   const uint32_t size_in_bytes = (src_bit_offset + bits + 7) / kBitsPerByte;
45   assert(size_in_bytes <= kBitsPerByte);
46 
47   uint64_t data = 0;
48   uint8_t* ptr = reinterpret_cast<uint8_t*>(&data);
49   for (uint32_t i = 0; i < size_in_bytes; ++i) {
50     ptr[i] = src[i];
51   }
52 
53   data >>= src_bit_offset;
54   if (bits != 64)
55     data &= (1ULL << bits) - 1ULL;
56 
57   std::memcpy(dst, &data, static_cast<size_t>((bits + 7) / kBitsPerByte));
58 }
59 
60 // Convert float |value| whose size is 16 bits to 32 bits float
61 // based on IEEE-754.
HexFloat16ToFloat(const uint8_t * value)62 float HexFloat16ToFloat(const uint8_t* value) {
63   uint32_t sign = (static_cast<uint32_t>(value[1]) & 0x80) << 24U;
64   uint32_t exponent = (((static_cast<uint32_t>(value[1]) & 0x7c) >> 2U) + 112U)
65                       << 23U;
66   uint32_t mantissa = ((static_cast<uint32_t>(value[1]) & 0x3) << 8U |
67                        static_cast<uint32_t>(value[0]))
68                       << 13U;
69 
70   uint32_t hex = sign | exponent | mantissa;
71   float* hex_float = reinterpret_cast<float*>(&hex);
72   return *hex_float;
73 }
74 
75 // Convert float |value| whose size is 11 bits to 32 bits float
76 // based on IEEE-754.
HexFloat11ToFloat(const uint8_t * value)77 float HexFloat11ToFloat(const uint8_t* value) {
78   uint32_t exponent = (((static_cast<uint32_t>(value[1]) << 2U) |
79                         ((static_cast<uint32_t>(value[0]) & 0xc0) >> 6U)) +
80                        112U)
81                       << 23U;
82   uint32_t mantissa = (static_cast<uint32_t>(value[0]) & 0x3f) << 17U;
83 
84   uint32_t hex = exponent | mantissa;
85   float* hex_float = reinterpret_cast<float*>(&hex);
86   return *hex_float;
87 }
88 
89 // Convert float |value| whose size is 10 bits to 32 bits float
90 // based on IEEE-754.
HexFloat10ToFloat(const uint8_t * value)91 float HexFloat10ToFloat(const uint8_t* value) {
92   uint32_t exponent = (((static_cast<uint32_t>(value[1]) << 3U) |
93                         ((static_cast<uint32_t>(value[0]) & 0xe0) >> 5U)) +
94                        112U)
95                       << 23U;
96   uint32_t mantissa = (static_cast<uint32_t>(value[0]) & 0x1f) << 18U;
97 
98   uint32_t hex = exponent | mantissa;
99   float* hex_float = reinterpret_cast<float*>(&hex);
100   return *hex_float;
101 }
102 
103 // Convert float |value| whose size is |bits| bits to 32 bits float
104 // based on IEEE-754.
105 // See https://www.khronos.org/opengl/wiki/Small_Float_Formats
106 // and https://en.wikipedia.org/wiki/IEEE_754.
107 //
108 //    Sign Exponent Mantissa Exponent-Bias
109 // 16    1        5       10            15
110 // 11    0        5        6            15
111 // 10    0        5        5            15
112 // 32    1        8       23           127
113 // 64    1       11       52          1023
114 //
115 // 11 and 10 bits floats are always positive.
116 // 14 bits float is used only RGB9_E5 format in OpenGL but it does not exist
117 // in Vulkan.
HexFloatToFloat(const uint8_t * value,uint8_t bits)118 float HexFloatToFloat(const uint8_t* value, uint8_t bits) {
119   switch (bits) {
120     case 10:
121       return HexFloat10ToFloat(value);
122     case 11:
123       return HexFloat11ToFloat(value);
124     case 16:
125       return HexFloat16ToFloat(value);
126   }
127 
128   assert(false && "Invalid bits");
129   return 0;
130 }
131 
132 // This is based on "18.3. sRGB transfer functions" of
133 // https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html
SRGBToLinearValue(double sRGB)134 double SRGBToLinearValue(double sRGB) {
135   if (sRGB <= 0.04045)
136     return sRGB / 12.92;
137 
138   return pow((sRGB + 0.055) / 1.055, 2.4);
139 }
140 
141 // It returns true if the difference is within the given error.
142 // If |is_tolerance_percent| is true, the actual tolerance will be
143 // relative value i.e., |tolerance| / 100 * fabs(expected).
144 // Otherwise, this method uses the absolute value i.e., |tolerance|.
IsEqualWithTolerance(const double actual,const double expected,double tolerance,const bool is_tolerance_percent=true)145 bool IsEqualWithTolerance(const double actual,
146                           const double expected,
147                           double tolerance,
148                           const bool is_tolerance_percent = true) {
149   double difference = std::fabs(actual - expected);
150   if (is_tolerance_percent) {
151     if (difference > ((tolerance / 100.0) * std::fabs(expected))) {
152       return false;
153     }
154   } else if (difference > tolerance) {
155     return false;
156   }
157   return true;
158 }
159 
160 template <typename T>
CheckValue(const ProbeSSBOCommand * command,const uint8_t * memory,const Value & value)161 Result CheckValue(const ProbeSSBOCommand* command,
162                   const uint8_t* memory,
163                   const Value& value) {
164   const auto comp = command->GetComparator();
165   const auto& tolerance = command->GetTolerances();
166   const T* ptr = reinterpret_cast<const T*>(memory);
167   const T val = value.IsInteger() ? static_cast<T>(value.AsUint64())
168                                   : static_cast<T>(value.AsDouble());
169   switch (comp) {
170     case ProbeSSBOCommand::Comparator::kEqual:
171       if (value.IsInteger()) {
172         if (static_cast<uint64_t>(*ptr) != static_cast<uint64_t>(val)) {
173           return Result(std::to_string(*ptr) + " == " + std::to_string(val));
174         }
175       } else {
176         if (!IsEqualWithTolerance(static_cast<const double>(*ptr),
177                                   static_cast<const double>(val), kEpsilon)) {
178           return Result(std::to_string(*ptr) + " == " + std::to_string(val));
179         }
180       }
181       break;
182     case ProbeSSBOCommand::Comparator::kNotEqual:
183       if (value.IsInteger()) {
184         if (static_cast<uint64_t>(*ptr) == static_cast<uint64_t>(val)) {
185           return Result(std::to_string(*ptr) + " != " + std::to_string(val));
186         }
187       } else {
188         if (IsEqualWithTolerance(static_cast<const double>(*ptr),
189                                  static_cast<const double>(val), kEpsilon)) {
190           return Result(std::to_string(*ptr) + " != " + std::to_string(val));
191         }
192       }
193       break;
194     case ProbeSSBOCommand::Comparator::kFuzzyEqual:
195       if (!IsEqualWithTolerance(
196               static_cast<const double>(*ptr), static_cast<const double>(val),
197               command->HasTolerances() ? tolerance[0].value : kEpsilon,
198               command->HasTolerances() ? tolerance[0].is_percent : true)) {
199         return Result(std::to_string(*ptr) + " ~= " + std::to_string(val));
200       }
201       break;
202     case ProbeSSBOCommand::Comparator::kLess:
203       if (*ptr >= val)
204         return Result(std::to_string(*ptr) + " < " + std::to_string(val));
205       break;
206     case ProbeSSBOCommand::Comparator::kLessOrEqual:
207       if (*ptr > val)
208         return Result(std::to_string(*ptr) + " <= " + std::to_string(val));
209       break;
210     case ProbeSSBOCommand::Comparator::kGreater:
211       if (*ptr <= val)
212         return Result(std::to_string(*ptr) + " > " + std::to_string(val));
213       break;
214     case ProbeSSBOCommand::Comparator::kGreaterOrEqual:
215       if (*ptr < val)
216         return Result(std::to_string(*ptr) + " >= " + std::to_string(val));
217       break;
218   }
219   return {};
220 }
221 
SetupToleranceForTexels(const ProbeCommand * command,double * tolerance,bool * is_tolerance_percent)222 void SetupToleranceForTexels(const ProbeCommand* command,
223                              double* tolerance,
224                              bool* is_tolerance_percent) {
225   if (command->HasTolerances()) {
226     const auto& tol = command->GetTolerances();
227     if (tol.size() == 4) {
228       tolerance[0] = tol[0].value;
229       tolerance[1] = tol[1].value;
230       tolerance[2] = tol[2].value;
231       tolerance[3] = tol[3].value;
232       is_tolerance_percent[0] = tol[0].is_percent;
233       is_tolerance_percent[1] = tol[1].is_percent;
234       is_tolerance_percent[2] = tol[2].is_percent;
235       is_tolerance_percent[3] = tol[3].is_percent;
236     } else {
237       tolerance[0] = tol[0].value;
238       tolerance[1] = tol[0].value;
239       tolerance[2] = tol[0].value;
240       tolerance[3] = tol[0].value;
241       is_tolerance_percent[0] = tol[0].is_percent;
242       is_tolerance_percent[1] = tol[0].is_percent;
243       is_tolerance_percent[2] = tol[0].is_percent;
244       is_tolerance_percent[3] = tol[0].is_percent;
245     }
246   } else {
247     tolerance[0] = kDefaultTexelTolerance;
248     tolerance[1] = kDefaultTexelTolerance;
249     tolerance[2] = kDefaultTexelTolerance;
250     tolerance[3] = kDefaultTexelTolerance;
251     is_tolerance_percent[0] = false;
252     is_tolerance_percent[1] = false;
253     is_tolerance_percent[2] = false;
254     is_tolerance_percent[3] = false;
255   }
256 }
257 
258 // Convert data of |texel| into double values based on the
259 // information given in |fmt|.
GetActualValuesFromTexel(const uint8_t * texel,const Format * fmt)260 std::vector<double> GetActualValuesFromTexel(const uint8_t* texel,
261                                              const Format* fmt) {
262   assert(fmt && !fmt->GetSegments().empty());
263 
264   std::vector<double> actual_values(fmt->GetSegments().size());
265   uint32_t bit_offset = 0;
266 
267   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
268     const auto& seg = fmt->GetSegments()[i];
269     if (seg.IsPadding()) {
270       bit_offset += seg.GetNumBits();
271       continue;
272     }
273 
274     uint8_t actual[8] = {0, 0, 0, 0, 0, 0, 0, 0};
275     uint32_t num_bits = seg.GetNumBits();
276     CopyBitsOfMemoryToBuffer(actual, texel, bit_offset, num_bits);
277 
278     FormatMode mode = seg.GetFormatMode();
279     if (type::Type::IsInt8(mode, num_bits)) {
280       int8_t* ptr8 = nullptr;
281       ptr8 = reinterpret_cast<int8_t*>(actual);
282       actual_values[i] = static_cast<double>(*ptr8);
283     } else if (type::Type::IsInt16(mode, num_bits)) {
284       int16_t* ptr16 = nullptr;
285       ptr16 = reinterpret_cast<int16_t*>(actual);
286       actual_values[i] = static_cast<double>(*ptr16);
287     } else if (type::Type::IsInt32(mode, num_bits)) {
288       int32_t* ptr32 = nullptr;
289       ptr32 = reinterpret_cast<int32_t*>(actual);
290       actual_values[i] = static_cast<double>(*ptr32);
291     } else if (type::Type::IsInt64(mode, num_bits)) {
292       int64_t* ptr64 = nullptr;
293       ptr64 = reinterpret_cast<int64_t*>(actual);
294       actual_values[i] = static_cast<double>(*ptr64);
295     } else if (type::Type::IsUint8(mode, num_bits)) {
296       actual_values[i] = static_cast<double>(*actual);
297     } else if (type::Type::IsUint16(mode, num_bits)) {
298       uint16_t* ptr16 = nullptr;
299       ptr16 = reinterpret_cast<uint16_t*>(actual);
300       actual_values[i] = static_cast<double>(*ptr16);
301     } else if (type::Type::IsUint32(mode, num_bits)) {
302       uint32_t* ptr32 = nullptr;
303       ptr32 = reinterpret_cast<uint32_t*>(actual);
304       actual_values[i] = static_cast<double>(*ptr32);
305     } else if (type::Type::IsUint64(mode, num_bits)) {
306       uint64_t* ptr64 = nullptr;
307       ptr64 = reinterpret_cast<uint64_t*>(actual);
308       actual_values[i] = static_cast<double>(*ptr64);
309     } else if (type::Type::IsFloat32(mode, num_bits)) {
310       float* ptr = reinterpret_cast<float*>(actual);
311       actual_values[i] = static_cast<double>(*ptr);
312     } else if (type::Type::IsFloat64(mode, num_bits)) {
313       double* ptr = reinterpret_cast<double*>(actual);
314       actual_values[i] = *ptr;
315     } else if (type::Type::IsFloat(mode) && num_bits < 32) {
316       actual_values[i] = static_cast<double>(
317           HexFloatToFloat(actual, static_cast<uint8_t>(num_bits)));
318     } else {
319       assert(false && "Incorrect number of bits for number.");
320     }
321 
322     bit_offset += num_bits;
323   }
324 
325   return actual_values;
326 }
327 
328 // If component mode of |fmt| is FormatMode::kUNorm or
329 // ::kSNorm or ::kSRGB, scale the corresponding value in |texel|.
330 // Note that we do not scale values with FormatMode::kUInt, ::kSInt,
331 // ::kUFloat, ::kSFloat.
ScaleTexelValuesIfNeeded(std::vector<double> * texel,const Format * fmt)332 void ScaleTexelValuesIfNeeded(std::vector<double>* texel, const Format* fmt) {
333   assert(fmt->GetSegments().size() == texel->size());
334 
335   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
336     const auto& seg = fmt->GetSegments()[i];
337     if (seg.IsPadding())
338       continue;
339 
340     double scaled_value = (*texel)[i];
341     if (seg.GetFormatMode() == FormatMode::kUNorm) {
342       scaled_value /= static_cast<double>((1 << seg.GetNumBits()) - 1);
343     } else if (seg.GetFormatMode() == FormatMode::kSNorm) {
344       scaled_value /= static_cast<double>((1 << (seg.GetNumBits() - 1)) - 1);
345     } else if (seg.GetFormatMode() == FormatMode::kSRGB) {
346       scaled_value /= static_cast<double>((1 << seg.GetNumBits()) - 1);
347       if (seg.GetName() != FormatComponentType::kA)
348         scaled_value = SRGBToLinearValue(scaled_value);
349     } else if (seg.GetFormatMode() == FormatMode::kSScaled ||
350                seg.GetFormatMode() == FormatMode::kUScaled) {
351       assert(false && "UScaled and SScaled are not implemented");
352     }
353 
354     (*texel)[i] = scaled_value;
355   }
356 }
357 
358 /// Check |texel| with |texel_format| is the same with the expected
359 /// RGB(A) values given via |command|. This method allow error
360 /// smaller than |tolerance|. If an element of
361 /// |is_tolerance_percent| is true, we assume that the corresponding
362 /// |tolerance| is relative i.e., percentage allowed error.
IsTexelEqualToExpected(const std::vector<double> & texel,const Format * fmt,const ProbeCommand * command,const double * tolerance,const bool * is_tolerance_percent)363 bool IsTexelEqualToExpected(const std::vector<double>& texel,
364                             const Format* fmt,
365                             const ProbeCommand* command,
366                             const double* tolerance,
367                             const bool* is_tolerance_percent) {
368   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
369     const auto& seg = fmt->GetSegments()[i];
370     if (seg.IsPadding())
371       continue;
372 
373     double texel_for_component = texel[i];
374     double expected = 0;
375     double current_tolerance = 0;
376     bool is_current_tolerance_percent = false;
377     switch (seg.GetName()) {
378       case FormatComponentType::kA:
379         if (!command->IsRGBA())
380           continue;
381 
382         expected = static_cast<double>(command->GetA());
383         current_tolerance = tolerance[3];
384         is_current_tolerance_percent = is_tolerance_percent[3];
385         break;
386       case FormatComponentType::kR:
387         expected = static_cast<double>(command->GetR());
388         current_tolerance = tolerance[0];
389         is_current_tolerance_percent = is_tolerance_percent[0];
390         break;
391       case FormatComponentType::kG:
392         expected = static_cast<double>(command->GetG());
393         current_tolerance = tolerance[1];
394         is_current_tolerance_percent = is_tolerance_percent[1];
395         break;
396       case FormatComponentType::kB:
397         expected = static_cast<double>(command->GetB());
398         current_tolerance = tolerance[2];
399         is_current_tolerance_percent = is_tolerance_percent[2];
400         break;
401       default:
402         continue;
403     }
404 
405     if (!IsEqualWithTolerance(expected, texel_for_component, current_tolerance,
406                               is_current_tolerance_percent)) {
407       return false;
408     }
409   }
410 
411   return true;
412 }
413 
GetTexelInRGBA(const std::vector<double> & texel,const Format * fmt)414 std::vector<double> GetTexelInRGBA(const std::vector<double>& texel,
415                                    const Format* fmt) {
416   std::vector<double> texel_in_rgba(texel.size());
417   for (size_t i = 0; i < fmt->GetSegments().size(); ++i) {
418     const auto& seg = fmt->GetSegments()[i];
419     if (seg.IsPadding())
420       continue;
421 
422     switch (seg.GetName()) {
423       case FormatComponentType::kR:
424         texel_in_rgba[0] = texel[i];
425         break;
426       case FormatComponentType::kG:
427         texel_in_rgba[1] = texel[i];
428         break;
429       case FormatComponentType::kB:
430         texel_in_rgba[2] = texel[i];
431         break;
432       case FormatComponentType::kA:
433         texel_in_rgba[3] = texel[i];
434         break;
435       default:
436         continue;
437     }
438   }
439   return texel_in_rgba;
440 }
441 
442 }  // namespace
443 
444 Verifier::Verifier() = default;
445 
446 Verifier::~Verifier() = default;
447 
Probe(const ProbeCommand * command,const Format * fmt,uint32_t texel_stride,uint32_t row_stride,uint32_t frame_width,uint32_t frame_height,const void * buf)448 Result Verifier::Probe(const ProbeCommand* command,
449                        const Format* fmt,
450                        uint32_t texel_stride,
451                        uint32_t row_stride,
452                        uint32_t frame_width,
453                        uint32_t frame_height,
454                        const void* buf) {
455   if (!command)
456     return Result("Verifier::Probe given ProbeCommand is nullptr");
457   if (!fmt)
458     return Result("Verifier::Probe given texel's Format is nullptr");
459   if (!buf)
460     return Result("Verifier::Probe given buffer to probe is nullptr");
461 
462   uint32_t x = 0;
463   uint32_t y = 0;
464   uint32_t width = 1;
465   uint32_t height = 1;
466 
467   if (command->IsWholeWindow()) {
468     width = frame_width;
469     height = frame_height;
470   } else if (command->IsRelative()) {
471     x = static_cast<uint32_t>(static_cast<float>(frame_width) *
472                               command->GetX());
473     y = static_cast<uint32_t>(static_cast<float>(frame_height) *
474                               command->GetY());
475     if (command->IsProbeRect()) {
476       width = static_cast<uint32_t>(static_cast<float>(frame_width) *
477                                     command->GetWidth());
478       height = static_cast<uint32_t>(static_cast<float>(frame_height) *
479                                      command->GetHeight());
480     }
481   } else {
482     x = static_cast<uint32_t>(command->GetX());
483     y = static_cast<uint32_t>(command->GetY());
484     width = static_cast<uint32_t>(command->GetWidth());
485     height = static_cast<uint32_t>(command->GetHeight());
486   }
487 
488   if (x + width > frame_width || y + height > frame_height) {
489     return Result(
490         "Line " + std::to_string(command->GetLine()) +
491         ": Verifier::Probe Position(" + std::to_string(x + width - 1) + ", " +
492         std::to_string(y + height - 1) + ") is out of framebuffer scope (" +
493         std::to_string(frame_width) + "," + std::to_string(frame_height) + ")");
494   }
495 
496   if (row_stride < frame_width * texel_stride) {
497     return Result("Line " + std::to_string(command->GetLine()) +
498                   ": Verifier::Probe Row stride of " +
499                   std::to_string(row_stride) + " is too small for " +
500                   std::to_string(frame_width) + " texels of " +
501                   std::to_string(texel_stride) + " bytes each");
502   }
503 
504   double tolerance[4] = {0, 0, 0, 0};
505   bool is_tolerance_percent[4] = {0, 0, 0, 0};
506   SetupToleranceForTexels(command, tolerance, is_tolerance_percent);
507 
508   const uint8_t* ptr = static_cast<const uint8_t*>(buf);
509   uint32_t count_of_invalid_pixels = 0;
510   uint32_t first_invalid_i = 0;
511   uint32_t first_invalid_j = 0;
512   std::vector<double> failure_values;
513   for (uint32_t j = 0; j < height; ++j) {
514     const uint8_t* p = ptr + row_stride * (j + y) + texel_stride * x;
515     for (uint32_t i = 0; i < width; ++i) {
516       auto actual_texel_values =
517           GetActualValuesFromTexel(p + texel_stride * i, fmt);
518       ScaleTexelValuesIfNeeded(&actual_texel_values, fmt);
519       if (!IsTexelEqualToExpected(actual_texel_values, fmt, command, tolerance,
520                                   is_tolerance_percent)) {
521         if (!count_of_invalid_pixels) {
522           failure_values = GetTexelInRGBA(actual_texel_values, fmt);
523           first_invalid_i = i;
524           first_invalid_j = j;
525         }
526         ++count_of_invalid_pixels;
527       }
528     }
529   }
530 
531   if (count_of_invalid_pixels) {
532     float scale = fmt->IsNormalized() ? 255.f : 1.f;
533     std::string reason =
534         "Line " + std::to_string(command->GetLine()) +
535         ": Probe failed at: " + std::to_string(x + first_invalid_i) + ", " +
536         std::to_string(first_invalid_j + y) + "\n" +
537         "  Expected: " + std::to_string(command->GetR() * scale) + ", " +
538         std::to_string(command->GetG() * scale) + ", " +
539         std::to_string(command->GetB() * scale);
540 
541     if (command->IsRGBA()) {
542       reason += ", " + std::to_string(command->GetA() * scale);
543     }
544 
545     reason +=
546         "\n    Actual: " +
547         std::to_string(static_cast<float>(failure_values[0]) * scale) + ", " +
548         std::to_string(static_cast<float>(failure_values[1]) * scale) + ", " +
549         std::to_string(static_cast<float>(failure_values[2]) * scale);
550 
551     if (command->IsRGBA()) {
552       reason +=
553           ", " + std::to_string(static_cast<float>(failure_values[3]) * scale);
554     }
555 
556     reason += "\nProbe failed in " + std::to_string(count_of_invalid_pixels) +
557               " pixels";
558 
559     return Result(reason);
560   }
561 
562   return {};
563 }
564 
ProbeSSBO(const ProbeSSBOCommand * command,uint32_t buffer_element_count,const void * buffer)565 Result Verifier::ProbeSSBO(const ProbeSSBOCommand* command,
566                            uint32_t buffer_element_count,
567                            const void* buffer) {
568   const auto& values = command->GetValues();
569   if (!buffer) {
570     if (values.empty())
571       return {};
572     return Result(
573         "Verifier::ProbeSSBO actual data is empty while expected "
574         "data is not");
575   }
576 
577   auto* fmt = command->GetFormat();
578   size_t elem_count = values.size() / fmt->InputNeededPerElement();
579   size_t offset = static_cast<size_t>(command->GetOffset());
580   size_t size_in_bytes = buffer_element_count * fmt->SizeInBytes();
581   if ((elem_count * fmt->SizeInBytes()) + offset > size_in_bytes) {
582     return Result("Line " + std::to_string(command->GetLine()) +
583                   ": Verifier::ProbeSSBO request to access to byte " +
584                   std::to_string((elem_count * fmt->SizeInBytes()) + offset) +
585                   " would read outside buffer of size " +
586                   std::to_string(size_in_bytes) + " bytes");
587   }
588 
589   if (offset % fmt->SizeInBytes() != 0) {
590     return Result("Line " + std::to_string(command->GetLine()) +
591                   ": Verifier::ProbeSSBO given offset (" +
592                   std::to_string(offset) + ") " +
593                   "is not multiple of element size (" +
594                   std::to_string(fmt->SizeInBytes()) + ")");
595   }
596 
597   auto& segments = fmt->GetSegments();
598 
599   const uint8_t* ptr = static_cast<const uint8_t*>(buffer) + offset;
600   for (size_t i = 0, k = 0; i < values.size(); ++i, ++k) {
601     if (k >= segments.size())
602       k = 0;
603 
604     const auto& value = values[i];
605     auto segment = segments[k];
606     // Skip over any padding bytes.
607     while (segment.IsPadding()) {
608       ptr += segment.PaddingBytes();
609       ++k;
610       if (k >= segments.size())
611         k = 0;
612 
613       segment = segments[k];
614     }
615 
616     Result r;
617     FormatMode mode = segment.GetFormatMode();
618     uint32_t num_bits = segment.GetNumBits();
619     if (type::Type::IsInt8(mode, num_bits))
620       r = CheckValue<int8_t>(command, ptr, value);
621     else if (type::Type::IsUint8(mode, num_bits))
622       r = CheckValue<uint8_t>(command, ptr, value);
623     else if (type::Type::IsInt16(mode, num_bits))
624       r = CheckValue<int16_t>(command, ptr, value);
625     else if (type::Type::IsUint16(mode, num_bits))
626       r = CheckValue<uint16_t>(command, ptr, value);
627     else if (type::Type::IsInt32(mode, num_bits))
628       r = CheckValue<int32_t>(command, ptr, value);
629     else if (type::Type::IsUint32(mode, num_bits))
630       r = CheckValue<uint32_t>(command, ptr, value);
631     else if (type::Type::IsInt64(mode, num_bits))
632       r = CheckValue<int64_t>(command, ptr, value);
633     else if (type::Type::IsUint64(mode, num_bits))
634       r = CheckValue<uint64_t>(command, ptr, value);
635     else if (type::Type::IsFloat32(mode, num_bits))
636       r = CheckValue<float>(command, ptr, value);
637     else if (type::Type::IsFloat64(mode, num_bits))
638       r = CheckValue<double>(command, ptr, value);
639     else
640       return Result("Unknown datum type");
641 
642     if (!r.IsSuccess()) {
643       return Result("Line " + std::to_string(command->GetLine()) +
644                     ": Verifier failed: " + r.Error() + ", at index " +
645                     std::to_string(i));
646     }
647 
648     ptr += segment.SizeInBytes();
649   }
650 
651   return {};
652 }
653 
654 }  // namespace amber
655