blob: 4d5acec323de44bc888ab985d18ed31c7c3393a7 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. All Rights Reserved.
//
// 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 "starboard/nplb/blitter_pixel_tests/fixture.h"
#include <string>
#include <vector>
#include "starboard/blitter.h"
#include "starboard/configuration_constants.h"
#include "starboard/directory.h"
#include "starboard/nplb/blitter_pixel_tests/command_line.h"
#include "starboard/nplb/blitter_pixel_tests/image.h"
#include "testing/gtest/include/gtest/gtest.h"
#if SB_HAS(BLITTER)
namespace starboard {
namespace nplb {
namespace blitter_pixel_tests {
namespace {
const int kTargetWidth = 128;
const int kTargetHeight = 128;
// The pixel tests accept a set of command line switches that can be used
// to gain more insight into the test results, as well as produce new
// expected test values.
// Runs the tests and outputs the results into the output folder with
// filenames matching those of the expected results PNG filenames. Thus, after
// this is called, one can copy the files into their source tree to serve as
// new expected results PNG files.
const char kRebaselineSwitch[] = "--rebaseline";
// Similar to "--rebaseline", but only rebaselines tests that fail.
const char kRebaselineFailedTestsSwitch[] = "--rebaseline-failed-tests";
// For each test that fails, 3 files will be output:
// 1. The actual results produced by the test.
// 2. The expected results for the test.
// 3. A diff image indicating where the tests differ, indicated by red pixels.
const char kOutputFailedTestDetailsSwitch[] = "--output-failed-test-details";
const size_t kPathSize = kSbFileMaxPath + 1;
// Returns the directory that all output will be placed in. There will never
// be any output unless command line switches are used.
std::string GetTestOutputDirectory() {
std::vector<char> test_output_path(kPathSize);
EXPECT_TRUE(SbSystemGetPath(kSbSystemPathTestOutputDirectory,
test_output_path.data(), kPathSize));
std::string output_dir = std::string(test_output_path.data());
output_dir += kSbFileSepChar;
output_dir += "starboard";
SB_CHECK(SbDirectoryCreate(output_dir.c_str()));
output_dir += kSbFileSepChar;
output_dir += "nplb";
SB_CHECK(SbDirectoryCreate(output_dir.c_str()));
output_dir += kSbFileSepChar;
output_dir += "blitter_pixel_tests";
SB_CHECK(SbDirectoryCreate(output_dir.c_str()));
output_dir += kSbFileSepChar;
output_dir += "data";
SB_CHECK(SbDirectoryCreate(output_dir.c_str()));
return output_dir;
}
// The input directory in which all the expected results PNG test files can
// be found.
std::string GetTestInputDirectory() {
std::vector<char> content_path(kPathSize);
EXPECT_TRUE(SbSystemGetPath(kSbSystemPathContentDirectory,
content_path.data(), kPathSize));
std::string directory_path =
std::string(content_path.data()) + kSbFileSepChar + "test" +
kSbFileSepChar + "starboard" + kSbFileSepChar + "nplb" +
kSbFileSepChar + "blitter_pixel_tests" + kSbFileSepChar + "data";
SB_CHECK(SbDirectoryCanOpen(directory_path.c_str()));
return directory_path;
}
// All file paths are based off of the name of the current test.
std::string GetCurrentTestName() {
return ::testing::UnitTest::GetInstance()->current_test_info()->name();
}
std::string GetRebaselinePath() {
return GetTestOutputDirectory() + kSbFileSepChar + GetCurrentTestName() +
"-expected.png";
}
std::string GetExpectedResultsPath() {
return GetTestInputDirectory() + kSbFileSepChar + GetCurrentTestName() +
"-expected.png";
}
std::string GetOutputDetailsActualResultsPath() {
return GetTestOutputDirectory() + kSbFileSepChar + GetCurrentTestName() +
"-actual.png";
}
std::string GetOutputDetailsExpectedResultsPath() {
return GetTestOutputDirectory() + kSbFileSepChar + GetCurrentTestName() +
"-expected.png";
}
std::string GetOutputDetailsDiffPath() {
return GetTestOutputDirectory() + kSbFileSepChar + GetCurrentTestName() +
"-diff.png";
}
} // namespace
SbBlitterPixelTest::SbBlitterPixelTest() {
// Setup convenience objects that will likely be used by almost every pixel
// tests.
device_ = SbBlitterCreateDefaultDevice();
EXPECT_TRUE(SbBlitterIsDeviceValid(device_));
surface_ = SbBlitterCreateRenderTargetSurface(
device_, GetWidth(), GetHeight(), kSbBlitterSurfaceFormatRGBA8);
if (!SbBlitterIsSurfaceValid(surface_)) {
SB_LOG(ERROR) << "A 32-bit RGBA format must be supported by the platform "
<< "in order to run pixel tests.";
SB_NOTREACHED();
}
render_target_ = SbBlitterGetRenderTargetFromSurface(surface_);
EXPECT_TRUE(SbBlitterIsRenderTargetValid(render_target_));
context_ = SbBlitterCreateContext(device_);
EXPECT_TRUE(SbBlitterIsContextValid(context_));
// Initialize |context_|'s render target to |render_target_|.
EXPECT_TRUE(SbBlitterSetRenderTarget(context_, render_target_));
// Initialize the surface by filling it to be completely transparent.
EXPECT_TRUE(SbBlitterSetBlending(context_, false));
EXPECT_TRUE(SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 0, 0, 0)));
EXPECT_TRUE(SbBlitterFillRect(
context_, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())));
}
SbBlitterPixelTest::~SbBlitterPixelTest() {
// Upon destruction, perform the pixel test on surface_.
CheckSurfacePixels();
EXPECT_TRUE(SbBlitterDestroyContext(context_));
EXPECT_TRUE(SbBlitterDestroySurface(surface_));
EXPECT_TRUE(SbBlitterDestroyDevice(device_));
}
int SbBlitterPixelTest::GetWidth() const {
return kTargetWidth;
}
int SbBlitterPixelTest::GetHeight() const {
return kTargetHeight;
}
void SbBlitterPixelTest::CheckSurfacePixels() {
// Here is where the actual pixel testing logic is. We start by flushing
// the context so that all draw commands are guaranteed to be submitted.
SbBlitterFlushContext(context_);
// Start by converting the resulting SbBlitterSurface into a CPU-accessible
// Image object.
Image actual_image(surface_);
if (CommandLineContains(kRebaselineSwitch)) {
// If we're rebaselining, we don't actually need to perform any tests, we
// just write out the resulting file.
actual_image.WriteToPNG(GetRebaselinePath());
}
// Flag indicating whether the actual image matches with the expected image.
// This is checked at the end of this function and determines whether the
// test passes or not.
bool is_match = false;
// Load the expected results image.
if (Image::CanOpenFile(GetExpectedResultsPath())) {
Image expected_image(GetExpectedResultsPath());
// Adjusting the following values affect how much of a fudge factor the
// tests are willing to accept before failing. The |kBlurSigma| parameter
// can be modified to affect how much the expected/actual images are blurred
// before being tested, which is good for reducing the severity of
// 1-pixel-off differences. The |kPixelTestValueFuzz| parameter can be
// adjusted to set how much each pixel color must differ by before being
// considered a difference.
const float kBlurSigma = 0.0f;
const int kPixelTestValueFuzz = 2;
// Blur the actual and expected images before comparing them so that we
// can be lenient on one-pixel-off differences.
Image actual_blurred_image = actual_image.GaussianBlur(kBlurSigma);
Image expected_blurred_image = expected_image.GaussianBlur(kBlurSigma);
// Perform the image diff on the blurred images.
Image diff_image = actual_blurred_image.Diff(
expected_blurred_image, kPixelTestValueFuzz, &is_match);
// If the images do not match, depending on what command line options were
// specified, we may write out resulting image PNG files.
if (!is_match) {
if (!CommandLineContains(kRebaselineSwitch) &&
!CommandLineContains(kRebaselineFailedTestsSwitch) &&
CommandLineContains(kOutputFailedTestDetailsSwitch)) {
actual_image.WriteToPNG(GetOutputDetailsActualResultsPath());
expected_image.WriteToPNG(GetOutputDetailsExpectedResultsPath());
diff_image.WriteToPNG(GetOutputDetailsDiffPath());
}
}
}
if (!is_match && !CommandLineContains(kRebaselineSwitch) &&
CommandLineContains(kRebaselineFailedTestsSwitch)) {
SB_LOG(INFO) << "Rebasing to " << GetRebaselinePath();
actual_image.WriteToPNG(GetRebaselinePath());
}
EXPECT_TRUE(is_match);
}
} // namespace blitter_pixel_tests
} // namespace nplb
} // namespace starboard
#endif // SB_HAS(BLITTER)