blob: b618df9140f2ac1db5841ac5ae78a55f96a323d7 [file] [log] [blame]
// Copyright 2018 The Amber Authors.
//
// 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.
#include "src/verifier.h"
#include <cmath>
#include <string>
#include <vector>
#include "src/command.h"
namespace amber {
namespace {
const double kEpsilon = 0.000001;
const double kDefaultTexelTolerance = 0.002;
// It returns true if the difference is within the given error.
// If |is_tolerance_percent| is true, the actual tolerance will be
// relative value i.e., |tolerance| / 100 * fabs(expected).
// Otherwise, this method uses the absolute value i.e., |tolerance|.
bool IsEqualWithTolerance(const double real,
const double expected,
double tolerance,
const bool is_tolerance_percent = true) {
double difference = std::fabs(real - expected);
if (is_tolerance_percent) {
if (difference > ((tolerance / 100.0) * std::fabs(expected))) {
return false;
}
} else if (difference > tolerance) {
return false;
}
return true;
}
template <typename T>
Result CheckValue(const ProbeSSBOCommand* command,
const uint8_t* memory,
const std::vector<Value>& values) {
const auto comp = command->GetComparator();
const auto& tolerance = command->GetTolerances();
const T* ptr = reinterpret_cast<const T*>(memory);
for (size_t i = 0; i < values.size(); ++i) {
const T val = values[i].IsInteger() ? static_cast<T>(values[i].AsUint64())
: static_cast<T>(values[i].AsDouble());
switch (comp) {
case ProbeSSBOCommand::Comparator::kEqual:
if (values[i].IsInteger()) {
if (static_cast<uint64_t>(*ptr) != static_cast<uint64_t>(val)) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" == " + std::to_string(val) + ", at index " +
std::to_string(i));
}
} else {
if (!IsEqualWithTolerance(static_cast<const double>(*ptr),
static_cast<const double>(val), kEpsilon)) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" == " + std::to_string(val) + ", at index " +
std::to_string(i));
}
}
break;
case ProbeSSBOCommand::Comparator::kNotEqual:
if (values[i].IsInteger()) {
if (static_cast<uint64_t>(*ptr) == static_cast<uint64_t>(val)) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" != " + std::to_string(val) + ", at index " +
std::to_string(i));
}
} else {
if (IsEqualWithTolerance(static_cast<const double>(*ptr),
static_cast<const double>(val), kEpsilon)) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" != " + std::to_string(val) + ", at index " +
std::to_string(i));
}
}
break;
case ProbeSSBOCommand::Comparator::kFuzzyEqual:
if (!IsEqualWithTolerance(
static_cast<const double>(*ptr), static_cast<const double>(val),
command->HasTolerances() ? tolerance[0].value : kEpsilon,
command->HasTolerances() ? tolerance[0].is_percent : true)) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" ~= " + std::to_string(val) + ", at index " +
std::to_string(i));
}
break;
case ProbeSSBOCommand::Comparator::kLess:
if (*ptr >= val) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) + " < " +
std::to_string(val) + ", at index " +
std::to_string(i));
}
break;
case ProbeSSBOCommand::Comparator::kLessOrEqual:
if (*ptr > val) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" <= " + std::to_string(val) + ", at index " +
std::to_string(i));
}
break;
case ProbeSSBOCommand::Comparator::kGreater:
if (*ptr <= val) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) + " > " +
std::to_string(val) + ", at index " +
std::to_string(i));
}
break;
case ProbeSSBOCommand::Comparator::kGreaterOrEqual:
if (*ptr < val) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier failed: " + std::to_string(*ptr) +
" >= " + std::to_string(val) + ", at index " +
std::to_string(i));
}
break;
}
++ptr;
}
return {};
}
void SetupToleranceForTexels(const ProbeCommand* command,
double* tolerance,
bool* is_tolerance_percent) {
if (command->HasTolerances()) {
const auto& tol = command->GetTolerances();
if (tol.size() == 4) {
tolerance[0] = tol[0].value;
tolerance[1] = tol[1].value;
tolerance[2] = tol[2].value;
tolerance[3] = tol[3].value;
is_tolerance_percent[0] = tol[0].is_percent;
is_tolerance_percent[1] = tol[1].is_percent;
is_tolerance_percent[2] = tol[2].is_percent;
is_tolerance_percent[3] = tol[3].is_percent;
} else {
tolerance[0] = tol[0].value;
tolerance[1] = tol[0].value;
tolerance[2] = tol[0].value;
tolerance[3] = tol[0].value;
is_tolerance_percent[0] = tol[0].is_percent;
is_tolerance_percent[1] = tol[0].is_percent;
is_tolerance_percent[2] = tol[0].is_percent;
is_tolerance_percent[3] = tol[0].is_percent;
}
} else {
tolerance[0] = kDefaultTexelTolerance;
tolerance[1] = kDefaultTexelTolerance;
tolerance[2] = kDefaultTexelTolerance;
tolerance[3] = kDefaultTexelTolerance;
is_tolerance_percent[0] = false;
is_tolerance_percent[1] = false;
is_tolerance_percent[2] = false;
is_tolerance_percent[3] = false;
}
}
} // namespace
Verifier::Verifier() = default;
Verifier::~Verifier() = default;
Result Verifier::Probe(const ProbeCommand* command,
uint32_t texel_stride,
uint32_t row_stride,
uint32_t frame_width,
uint32_t frame_height,
const void* buf) {
uint32_t x = 0;
uint32_t y = 0;
uint32_t width = 0;
uint32_t height = 0;
if (command->IsWholeWindow()) {
width = frame_width;
height = frame_height;
} else if (command->IsRelative()) {
x = static_cast<uint32_t>(static_cast<float>(frame_width) *
command->GetX());
y = static_cast<uint32_t>(static_cast<float>(frame_height) *
command->GetY());
width = static_cast<uint32_t>(static_cast<float>(frame_width) *
command->GetWidth());
height = static_cast<uint32_t>(static_cast<float>(frame_height) *
command->GetHeight());
} else {
x = static_cast<uint32_t>(command->GetX());
y = static_cast<uint32_t>(command->GetY());
width = static_cast<uint32_t>(command->GetWidth());
height = static_cast<uint32_t>(command->GetHeight());
}
if (x + width > frame_width || y + height > frame_height) {
return Result(
"Line " + std::to_string(command->GetLine()) +
": Verifier::Probe Position(" + std::to_string(x + width - 1) + ", " +
std::to_string(y + height - 1) + ") is out of framebuffer scope (" +
std::to_string(frame_width) + "," + std::to_string(frame_height) + ")");
}
if (row_stride < frame_width * texel_stride) {
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier::Probe Row stride of " +
std::to_string(row_stride) + " is too small for " +
std::to_string(frame_width) + " texels of " +
std::to_string(texel_stride) + " bytes each");
}
double tolerance[4] = {0, 0, 0, 0};
bool is_tolerance_percent[4] = {0, 0, 0, 0};
SetupToleranceForTexels(command, tolerance, is_tolerance_percent);
// TODO(jaebaek): Support all VkFormat
const uint8_t* ptr = static_cast<const uint8_t*>(buf);
uint32_t count_of_invalid_pixels = 0;
uint32_t first_invalid_i = 0;
uint32_t first_invalid_j = 0;
for (uint32_t j = 0; j < height; ++j) {
const uint8_t* p = ptr + row_stride * (j + y) + texel_stride * x;
for (uint32_t i = 0; i < width; ++i) {
// TODO(jaebaek): Get actual pixel values based on frame buffer formats.
if (!IsEqualWithTolerance(
static_cast<double>(command->GetR()),
static_cast<double>(p[texel_stride * i]) / 255.0, tolerance[0],
is_tolerance_percent[0]) ||
!IsEqualWithTolerance(
static_cast<double>(command->GetG()),
static_cast<double>(p[texel_stride * i + 1]) / 255.0,
tolerance[1], is_tolerance_percent[1]) ||
!IsEqualWithTolerance(
static_cast<double>(command->GetB()),
static_cast<double>(p[texel_stride * i + 2]) / 255.0,
tolerance[2], is_tolerance_percent[2]) ||
(command->IsRGBA() &&
!IsEqualWithTolerance(
static_cast<double>(command->GetA()),
static_cast<double>(p[texel_stride * i + 3]) / 255.0,
tolerance[3], is_tolerance_percent[3]))) {
if (!count_of_invalid_pixels) {
first_invalid_i = i;
first_invalid_j = j;
}
++count_of_invalid_pixels;
}
}
}
if (count_of_invalid_pixels) {
const uint8_t* p = ptr + row_stride * (first_invalid_j + y) +
texel_stride * (x + first_invalid_i);
return Result(
"Line " + std::to_string(command->GetLine()) +
": Probe failed at: " + std::to_string(x + first_invalid_i) + ", " +
std::to_string(first_invalid_j + y) + "\n" +
" Expected RGBA: " + std::to_string(command->GetR() * 255) + ", " +
std::to_string(command->GetG() * 255) + ", " +
std::to_string(command->GetB() * 255) +
(command->IsRGBA() ? ", " + std::to_string(command->GetA() * 255) +
"\n Actual RGBA: "
: "\n Actual RGB: ") +
std::to_string(static_cast<int>(p[0])) + ", " +
std::to_string(static_cast<int>(p[1])) + ", " +
std::to_string(static_cast<int>(p[2])) +
(command->IsRGBA() ? ", " + std::to_string(static_cast<int>(p[3]))
: "") +
"\n" + "Probe failed in " + std::to_string(count_of_invalid_pixels) +
" pixels");
}
return {};
}
Result Verifier::ProbeSSBO(const ProbeSSBOCommand* command,
size_t size_in_bytes,
const void* cpu_memory) {
const auto& values = command->GetValues();
if (!cpu_memory) {
return values.empty() ? Result()
: Result(
"Verifier::ProbeSSBO actual data is empty "
"while expected data is not");
}
const auto& datum_type = command->GetDatumType();
size_t bytes_per_elem = datum_type.SizeInBytes() / datum_type.RowCount() /
datum_type.ColumnCount();
size_t offset = static_cast<size_t>(command->GetOffset());
if (values.size() * bytes_per_elem + offset > size_in_bytes) {
return Result(
"Line " + std::to_string(command->GetLine()) +
": Verifier::ProbeSSBO has more expected values than SSBO size");
}
if (offset % bytes_per_elem != 0) {
return Result(
"Line " + std::to_string(command->GetLine()) +
": Verifier::ProbeSSBO given offset is not multiple of bytes_per_elem");
}
const uint8_t* ptr = static_cast<const uint8_t*>(cpu_memory) + offset;
if (datum_type.IsInt8())
return CheckValue<int8_t>(command, ptr, values);
if (datum_type.IsUint8())
return CheckValue<uint8_t>(command, ptr, values);
if (datum_type.IsInt16())
return CheckValue<int16_t>(command, ptr, values);
if (datum_type.IsUint16())
return CheckValue<uint16_t>(command, ptr, values);
if (datum_type.IsInt32())
return CheckValue<int32_t>(command, ptr, values);
if (datum_type.IsUint32())
return CheckValue<uint32_t>(command, ptr, values);
if (datum_type.IsInt64())
return CheckValue<int64_t>(command, ptr, values);
if (datum_type.IsUint64())
return CheckValue<uint64_t>(command, ptr, values);
if (datum_type.IsFloat())
return CheckValue<float>(command, ptr, values);
if (datum_type.IsDouble())
return CheckValue<double>(command, ptr, values);
return Result("Line " + std::to_string(command->GetLine()) +
": Verifier::ProbeSSBO unknown datum type");
}
} // namespace amber