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