Skip to content

Commit 2f4f146

Browse files
Improve test image comparison diagnostics
Report tolerated and failing pixel differences separately. Include backend names in comparison failure images. Keep exact image comparisons silent.
1 parent 01addec commit 2f4f146

3 files changed

Lines changed: 189 additions & 37 deletions

File tree

Tests/DiligentCoreAPITest/src/TestingSwapChainBaseTest.cpp

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,9 @@ TEST(TestingSwapChainBaseTest, ToleratesConfiguredImageDifferences)
9696
class ComparisonFailureImageGuard
9797
{
9898
public:
99-
ComparisonFailureImageGuard()
100-
{
101-
const testing::TestInfo* const TestInfo = testing::UnitTest::GetInstance()->current_test_info();
102-
VERIFY_EXPR(TestInfo != nullptr);
103-
if (TestInfo != nullptr)
104-
m_FileName = std::string{TestInfo->test_suite_name()} + "." + TestInfo->name() + "_FAIL_.png";
105-
}
99+
ComparisonFailureImageGuard() :
100+
m_FileName{GetTestImageComparisonFailureFileName()}
101+
{}
106102

107103
~ComparisonFailureImageGuard()
108104
{
@@ -115,10 +111,116 @@ class ComparisonFailureImageGuard
115111
ComparisonFailureImageGuard& operator=(const ComparisonFailureImageGuard&) = delete;
116112
// clang-format on
117113

114+
const std::string& GetFileName() const
115+
{
116+
return m_FileName;
117+
}
118+
118119
private:
119120
std::string m_FileName;
120121
};
121122

123+
TEST(TestingSwapChainBaseTest, ReportsOnlyNonEmptyDifferenceCategories)
124+
{
125+
constexpr std::array<Uint8, 8> Reference{
126+
10,
127+
20,
128+
30,
129+
255,
130+
40,
131+
50,
132+
60,
133+
255,
134+
};
135+
constexpr std::array<Uint8, 8> Actual{
136+
12,
137+
20,
138+
30,
139+
255, // Maximum error 2: tolerated.
140+
40,
141+
50,
142+
60,
143+
255,
144+
};
145+
146+
TestImageComparisonAttribs ComparisonAttribs;
147+
ComparisonAttribs.MaxChannelError = 2;
148+
149+
testing::internal::CaptureStdout();
150+
std::unordered_map<std::string, int> FailureCounters;
151+
CompareTestImages(Reference.data(), 8, Actual.data(), 8, 2, 1,
152+
TEX_FORMAT_RGBA8_UNORM, FailureCounters, ComparisonAttribs);
153+
const std::string Output = testing::internal::GetCapturedStdout();
154+
155+
EXPECT_NE(Output.find("1 of 2 pixels differ but remain within the per-channel error threshold 2"), std::string::npos);
156+
EXPECT_EQ(Output.find("exceed the threshold"), std::string::npos);
157+
}
158+
159+
TEST(TestingSwapChainBaseTest, AddsRenderDeviceTypeToFailureImageName)
160+
{
161+
constexpr std::array<Uint8, 4> Reference{255, 255, 255, 255};
162+
constexpr std::array<Uint8, 4> Actual{0, 255, 255, 255};
163+
164+
std::unordered_map<std::string, int> FailureCounters;
165+
ComparisonFailureImageGuard FailureImageGuard;
166+
EXPECT_NONFATAL_FAILURE(
167+
CompareTestImages(Reference.data(), 4, Actual.data(), 4, 1, 1,
168+
TEX_FORMAT_RGBA8_UNORM, FailureCounters),
169+
"Image rendered by the test differs from the reference image");
170+
EXPECT_TRUE(FileSystem::FileExists(FailureImageGuard.GetFileName().c_str()));
171+
}
172+
173+
TEST(TestingSwapChainBaseTest, ReportsToleratedAndBadPixelStatistics)
174+
{
175+
constexpr std::array<Uint8, 16> Reference{
176+
10,
177+
20,
178+
30,
179+
255,
180+
40,
181+
50,
182+
60,
183+
255,
184+
70,
185+
80,
186+
90,
187+
255,
188+
100,
189+
110,
190+
120,
191+
255,
192+
};
193+
constexpr std::array<Uint8, 16> Actual{
194+
12,
195+
20,
196+
30,
197+
255, // Maximum error 2: tolerated.
198+
40,
199+
27,
200+
60,
201+
255, // Maximum error 23: bad.
202+
70,
203+
80,
204+
90,
205+
255,
206+
100,
207+
110,
208+
120,
209+
255,
210+
};
211+
212+
TestImageComparisonAttribs ComparisonAttribs;
213+
ComparisonAttribs.MaxChannelError = 2;
214+
215+
std::unordered_map<std::string, int> FailureCounters;
216+
ComparisonFailureImageGuard FailureImageGuard;
217+
EXPECT_NONFATAL_FAILURE(
218+
CompareTestImages(Reference.data(), 16, Actual.data(), 16, 4, 1,
219+
TEX_FORMAT_RGBA8_UNORM, FailureCounters, ComparisonAttribs),
220+
"1 of 4 pixels differ but remain within the per-channel error threshold 2; maximum channel error is 2\n"
221+
"1 of 4 pixels (25%) exceed the threshold; maximum channel error is 23");
222+
}
223+
122224
void TestSnapshotComparisonFailure(Uint32 Channel)
123225
{
124226
ASSERT_LT(Channel, 4u);

Tests/GPUTestFramework/include/TestingSwapChainBase.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#pragma once
2828

2929
#include <cstring>
30+
#include <string>
3031
#include <vector>
3132
#include <unordered_map>
3233

@@ -74,6 +75,9 @@ bool LoadTestImage(const char* FilePath,
7475
Uint32& Width,
7576
Uint32& Height);
7677

78+
/// Returns the backend-qualified file name for a comparison failure image.
79+
std::string GetTestImageComparisonFailureFileName(Uint32 FailureIndex = 0);
80+
7781
/// Writes the image to a PNG file. Alpha is omitted by default.
7882
void DumpTestImage(const Uint8* pPixels,
7983
Uint64 PixelsStride,

Tests/GPUTestFramework/src/TestingSwapChainBase.cpp

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <algorithm>
3131
#include <limits>
3232
#include <memory>
33+
#include <sstream>
3334

3435
#include "TestingSwapChainBase.hpp"
3536
#include "GraphicsAccessories.hpp"
@@ -105,6 +106,35 @@ bool LoadTestImage(const char* FilePath,
105106
return true;
106107
}
107108

109+
std::string GetTestImageComparisonFailureFileName(Uint32 FailureIndex)
110+
{
111+
const auto* const TestInfo = ::testing::UnitTest::GetInstance()->current_test_info();
112+
GPUTestingEnvironment* const pEnvironment = GPUTestingEnvironment::GetInstance();
113+
VERIFY_EXPR(TestInfo != nullptr);
114+
VERIFY_EXPR(pEnvironment != nullptr && pEnvironment->GetDevice() != nullptr);
115+
116+
const auto ValidateName = [](const std::string& src) {
117+
std::string dst = src;
118+
for (char& c : dst)
119+
{
120+
if (c == '.' || c == '\\' || c == '/')
121+
c = '_';
122+
}
123+
return dst;
124+
};
125+
126+
std::string FileName{ValidateName(TestInfo->test_suite_name())};
127+
FileName += '.';
128+
FileName += ValidateName(TestInfo->name());
129+
FileName += '_';
130+
FileName += GetRenderDeviceTypeShortString(pEnvironment->GetDevice()->GetDeviceInfo().Type);
131+
FileName += "_FAIL";
132+
if (FailureIndex > 0)
133+
FileName += std::to_string(FailureIndex);
134+
FileName += "_.png";
135+
return FileName;
136+
}
137+
108138
void CompareTestImages(const Uint8* pReferencePixels,
109139
Uint64 RefPixelsStride,
110140
const Uint8* pPixels,
@@ -161,29 +191,64 @@ void CompareTestImages(const Uint8* pReferencePixels,
161191
if (bIsIdentical)
162192
return;
163193

164-
Uint64 BadPixelCount = 0;
165-
Uint32 MaxObservedChannelErr = 0;
194+
Uint64 ToleratedPixelCount = 0;
195+
Uint64 BadPixelCount = 0;
196+
Uint32 MaxToleratedChannelError = 0;
197+
Uint32 MaxBadChannelError = 0;
166198
for (Uint32 Row = 0; Row < Height; ++Row)
167199
{
168200
for (Uint32 Col = 0; Col < Width; ++Col)
169201
{
170-
bool BadPixel = false;
202+
Uint32 PixelMaxChannelError = 0;
171203
for (Uint32 Component = 0; Component < ComponentCount; ++Component)
172204
{
173205
const Uint32 RefValue = pReferencePixels[Row * RefPixelsStride + Col * 4 + Component];
174206
const Uint32 Value = pPixels[Row * PixelsStride + Col * 4 + Component];
175207
const Uint32 Error = RefValue > Value ? RefValue - Value : Value - RefValue;
176-
MaxObservedChannelErr = std::max(MaxObservedChannelErr, Error);
177-
BadPixel |= Error > ComparisonAttribs.MaxChannelError;
208+
PixelMaxChannelError = std::max(PixelMaxChannelError, Error);
209+
}
210+
211+
if (PixelMaxChannelError > ComparisonAttribs.MaxChannelError)
212+
{
213+
++BadPixelCount;
214+
MaxBadChannelError = std::max(MaxBadChannelError, PixelMaxChannelError);
215+
}
216+
else if (PixelMaxChannelError > 0)
217+
{
218+
++ToleratedPixelCount;
219+
MaxToleratedChannelError = std::max(MaxToleratedChannelError, PixelMaxChannelError);
178220
}
179-
BadPixelCount += BadPixel ? 1 : 0;
180221
}
181222
}
182223

224+
if (ToleratedPixelCount == 0 && BadPixelCount == 0)
225+
return;
226+
183227
const Uint64 PixelCount = Uint64{Width} * Height;
228+
229+
std::ostringstream Statistics;
230+
if (ToleratedPixelCount > 0)
231+
{
232+
Statistics << ToleratedPixelCount << " of " << PixelCount
233+
<< " pixels differ but remain within the per-channel error threshold "
234+
<< Uint32{ComparisonAttribs.MaxChannelError} << "; maximum channel error is "
235+
<< MaxToleratedChannelError;
236+
}
237+
if (BadPixelCount > 0)
238+
{
239+
if (ToleratedPixelCount > 0)
240+
Statistics << '\n';
241+
242+
const double BadPixelPercentage = static_cast<double>(BadPixelCount) / static_cast<double>(PixelCount) * 100.0;
243+
Statistics << BadPixelCount << " of " << PixelCount << " pixels (" << BadPixelPercentage
244+
<< "%) exceed the threshold; maximum channel error is " << MaxBadChannelError;
245+
}
246+
184247
if (static_cast<double>(BadPixelCount) <=
185248
static_cast<double>(PixelCount) * ComparisonAttribs.MaxBadPixelRatio)
186249
{
250+
LOG_WARNING_MESSAGE("Image rendered by the test differs from the reference image, but is within the configured tolerance:\n",
251+
Statistics.str());
187252
return;
188253
}
189254

@@ -210,34 +275,15 @@ void CompareTestImages(const Uint8* pReferencePixels,
210275
}
211276
}
212277
}
213-
const auto* const TestInfo = ::testing::UnitTest::GetInstance()->current_test_info();
214-
215-
const auto ValidateName = [](const std::string& src) {
216-
std::string dst = src;
217-
for (char& c : dst)
218-
{
219-
if (c == '.' || c == '\\' || c == '/')
220-
c = '_';
221-
}
222-
return dst;
223-
};
224-
225-
std::string FileName{ValidateName(TestInfo->test_suite_name())};
226-
FileName += '.';
227-
FileName += ValidateName(TestInfo->name());
228-
auto& FailureCounter = FailureCounters[FileName];
229-
FileName += "_FAIL";
230-
if (FailureCounter > 0)
231-
FileName += std::to_string(FailureCounter);
232-
FileName += "_.png";
278+
const std::string CounterKey = GetTestImageComparisonFailureFileName();
279+
auto& FailureCounter = FailureCounters[CounterKey];
280+
const std::string FileName = GetTestImageComparisonFailureFileName(static_cast<Uint32>(FailureCounter));
233281
if (stbi_write_png(FileName.c_str(), Width * 2, Height * 2, 3, ReportImage.data(), (Width * 2) * 3) == 0)
234282
{
235283
LOG_ERROR_MESSAGE("Failed to write ", FileName);
236284
}
237-
ADD_FAILURE() << "Image rendered by the test differs from the reference image: "
238-
<< BadPixelCount << " of " << PixelCount << " pixels exceed the per-channel error threshold "
239-
<< Uint32{ComparisonAttribs.MaxChannelError} << "; maximum observed channel error is "
240-
<< MaxObservedChannelErr;
285+
ADD_FAILURE() << "Image rendered by the test differs from the reference image:\n"
286+
<< Statistics.str();
241287
++FailureCounter;
242288
}
243289
}

0 commit comments

Comments
 (0)