Commit d08f1d8d by Jamie Madill Committed by Commit Bot

Perf tests: Add screenshot capture mode.

This adds a "--screenshot-dir" argument to capture screenshots. If we're running with screenshot capture then the test will early exit after the first capture. The screenshots use the same naming pattern as the test name: TracePerfTest.Run/vulkan_trex_200 -> angle_vulkan_trex_200.png Note the screenshot dir is relative to the test binary directory, not the CWD. Also adds a PNG saving utility function. Bug: angleproject:4615 Change-Id: I1de8ae6a6e6892586bb0b743e7b9a842f90f98e5 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2184834 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCody Northrop <cnorthrop@google.com>
parent 21d330b7
...@@ -238,7 +238,10 @@ template("angle_perftests_common") { ...@@ -238,7 +238,10 @@ template("angle_perftests_common") {
"perf_tests/third_party/perf/perf_test.cc", "perf_tests/third_party/perf/perf_test.cc",
"perf_tests/third_party/perf/perf_test.h", "perf_tests/third_party/perf/perf_test.h",
] ]
deps = [ "$angle_jsoncpp_dir:jsoncpp" ] deps = [
"$angle_jsoncpp_dir:jsoncpp",
"$angle_root/util:angle_png_utils",
]
public_deps = [ "${invoker.test_utils}" ] public_deps = [ "${invoker.test_utils}" ]
public_configs += [ "${angle_root}:libANGLE_config" ] public_configs += [ "${angle_root}:libANGLE_config" ]
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "common/utilities.h" #include "common/utilities.h"
#include "third_party/perf/perf_test.h" #include "third_party/perf/perf_test.h"
#include "third_party/trace_event/trace_event.h" #include "third_party/trace_event/trace_event.h"
#include "util/png_utils.h"
#include "util/shader_utils.h" #include "util/shader_utils.h"
#include "util/test_utils.h" #include "util/test_utils.h"
...@@ -239,6 +240,12 @@ void ANGLEPerfTest::run() ...@@ -239,6 +240,12 @@ void ANGLEPerfTest::run()
mStepsToRun = gStepsToRunOverride; mStepsToRun = gStepsToRunOverride;
} }
// Check again for early exit.
if (mSkipTest)
{
return;
}
// Do another warmup run. Seems to consistently improve results. // Do another warmup run. Seems to consistently improve results.
doRunLoop(kMaximumRunTimeSeconds); doRunLoop(kMaximumRunTimeSeconds);
...@@ -647,6 +654,10 @@ void ANGLERenderTest::step() ...@@ -647,6 +654,10 @@ void ANGLERenderTest::step()
else else
{ {
drawBenchmark(); drawBenchmark();
// Saves a screenshot. The test will also exit early if we're taking screenshots.
saveScreenshotIfEnabled();
// Swap is needed so that the GPU driver will occasionally flush its // Swap is needed so that the GPU driver will occasionally flush its
// internal command queue to the GPU. This is enabled for null back-end // internal command queue to the GPU. This is enabled for null back-end
// devices because some back-ends (e.g. Vulkan) also accumulate internal // devices because some back-ends (e.g. Vulkan) also accumulate internal
...@@ -747,6 +758,33 @@ std::vector<TraceEvent> &ANGLERenderTest::getTraceEventBuffer() ...@@ -747,6 +758,33 @@ std::vector<TraceEvent> &ANGLERenderTest::getTraceEventBuffer()
return mTraceEventBuffer; return mTraceEventBuffer;
} }
void ANGLERenderTest::saveScreenshotIfEnabled()
{
if (gScreenShotDir == nullptr)
{
return;
}
std::stringstream screenshotNameStr;
screenshotNameStr << gScreenShotDir << GetPathSeparator() << "angle" << mBackend << "_"
<< mStory << ".png";
std::string screenshotName = screenshotNameStr.str();
// RGBA 4-byte data.
std::vector<uint8_t> pixelData(mTestParams.windowWidth * mTestParams.windowHeight * 4);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glReadPixels(0, 0, mTestParams.windowWidth, mTestParams.windowHeight, GL_RGBA, GL_UNSIGNED_BYTE,
pixelData.data());
angle::SavePNG(screenshotName.c_str(), "ANGLE Screenshot", mTestParams.windowWidth,
mTestParams.windowHeight, pixelData);
// Early exit.
abortTest();
mSkipTest = true;
}
namespace angle namespace angle
{ {
double GetHostTimeSeconds() double GetHostTimeSeconds()
......
...@@ -69,6 +69,7 @@ class ANGLEPerfTest : public testing::Test, angle::NonCopyable ...@@ -69,6 +69,7 @@ class ANGLEPerfTest : public testing::Test, angle::NonCopyable
virtual void startTest() {} virtual void startTest() {}
// Called right before timer is stopped to let the test wait for asynchronous operations. // Called right before timer is stopped to let the test wait for asynchronous operations.
virtual void finishTest() {} virtual void finishTest() {}
virtual void flush() {}
protected: protected:
void run(); void run();
...@@ -153,6 +154,8 @@ class ANGLERenderTest : public ANGLEPerfTest ...@@ -153,6 +154,8 @@ class ANGLERenderTest : public ANGLEPerfTest
bool mIsTimestampQueryAvailable; bool mIsTimestampQueryAvailable;
void saveScreenshotIfEnabled();
private: private:
void SetUp() override; void SetUp() override;
void TearDown() override; void TearDown() override;
......
...@@ -17,6 +17,7 @@ bool gCalibration = false; ...@@ -17,6 +17,7 @@ bool gCalibration = false;
int gStepsToRunOverride = -1; int gStepsToRunOverride = -1;
bool gEnableTrace = false; bool gEnableTrace = false;
const char *gTraceFile = "ANGLETrace.json"; const char *gTraceFile = "ANGLETrace.json";
const char *gScreenShotDir = nullptr;
} // namespace angle } // namespace angle
using namespace angle; using namespace angle;
...@@ -37,7 +38,7 @@ void ANGLEProcessPerfTestArgs(int *argc, char **argv) ...@@ -37,7 +38,7 @@ void ANGLEProcessPerfTestArgs(int *argc, char **argv)
} }
else if (strcmp("--trace-file", argv[argIndex]) == 0 && argIndex < *argc - 1) else if (strcmp("--trace-file", argv[argIndex]) == 0 && argIndex < *argc - 1)
{ {
gTraceFile = argv[argIndex]; gTraceFile = argv[argIndex + 1];
// Skip an additional argument. // Skip an additional argument.
argIndex++; argIndex++;
} }
...@@ -55,6 +56,11 @@ void ANGLEProcessPerfTestArgs(int *argc, char **argv) ...@@ -55,6 +56,11 @@ void ANGLEProcessPerfTestArgs(int *argc, char **argv)
// Skip an additional argument. // Skip an additional argument.
argIndex++; argIndex++;
} }
else if (strcmp("--screenshot-dir", argv[argIndex]) == 0 && argIndex < *argc - 1)
{
gScreenShotDir = argv[argIndex + 1];
argIndex++;
}
else else
{ {
argv[argcOutCount++] = argv[argIndex]; argv[argcOutCount++] = argv[argIndex];
......
...@@ -18,6 +18,7 @@ extern bool gCalibration; ...@@ -18,6 +18,7 @@ extern bool gCalibration;
extern int gStepsToRunOverride; extern int gStepsToRunOverride;
extern bool gEnableTrace; extern bool gEnableTrace;
extern const char *gTraceFile; extern const char *gTraceFile;
extern const char *gScreenShotDir;
inline bool OneFrame() inline bool OneFrame()
{ {
......
...@@ -249,6 +249,19 @@ if (is_win && !angle_is_winuwp) { ...@@ -249,6 +249,19 @@ if (is_win && !angle_is_winuwp) {
} }
} }
config("angle_png_utils_config") {
include_dirs = [ ".." ]
}
angle_source_set("angle_png_utils") {
deps = [ "$angle_libpng_dir" ]
sources = [
"png_utils.cpp",
"png_utils.h",
]
public_configs = [ ":angle_png_utils_config" ]
}
config("angle_test_util_config") { config("angle_test_util_config") {
include_dirs = [ ".." ] include_dirs = [ ".." ]
} }
......
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// png_utils: Wrapper around libpng.
//
#include "util/png_utils.h"
#include <array>
#include <cstring>
#include <png.h>
namespace angle
{
namespace
{
class ScopedFILE
{
public:
ScopedFILE(FILE *fp) : mFP(fp) {}
~ScopedFILE() { close(); }
FILE *get() const { return mFP; }
void close()
{
if (mFP)
{
fclose(mFP);
mFP = nullptr;
}
}
private:
FILE *mFP;
};
} // namespace
bool SavePNG(const char *fileName,
const char *title,
uint32_t width,
uint32_t height,
const std::vector<uint8_t> &data)
{
ScopedFILE fp(fopen(fileName, "wb"));
if (!fp.get())
{
fprintf(stderr, "Error opening '%s'.\n", fileName);
return false;
}
png_struct *writeStruct =
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!writeStruct)
{
fprintf(stderr, "Error on png_create_write_struct.\n");
return false;
}
png_info *infoStruct = png_create_info_struct(writeStruct);
if (!infoStruct)
{
fprintf(stderr, "Error on png_create_info_struct.\n");
return false;
}
if (setjmp(png_jmpbuf(writeStruct)))
{
fp.close();
png_free_data(writeStruct, infoStruct, PNG_FREE_ALL, -1);
png_destroy_write_struct(&writeStruct, &infoStruct);
return false;
}
png_init_io(writeStruct, fp.get());
// Write header (8 bit colour depth)
png_set_IHDR(writeStruct, infoStruct, width, height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
// Set title
if (title != nullptr && strlen(title) > 0)
{
std::array<char, 50> mutableKey = {};
strcpy(mutableKey.data(), "Title");
std::array<char, 200> mutableText = {};
strncpy(mutableText.data(), title, 199);
png_text titleText;
titleText.compression = PNG_TEXT_COMPRESSION_NONE;
titleText.key = mutableKey.data();
titleText.text = mutableText.data();
png_set_text(writeStruct, infoStruct, &titleText, 1);
}
png_write_info(writeStruct, infoStruct);
// RGBA 4-byte stride.
const uint32_t rowStride = width * 4;
for (uint32_t row = 0; row < height; ++row)
{
uint32_t rowOffset = row * rowStride;
png_write_row(writeStruct, &data[rowOffset]);
}
png_write_end(writeStruct, infoStruct);
fp.close();
png_free_data(writeStruct, infoStruct, PNG_FREE_ALL, -1);
png_destroy_write_struct(&writeStruct, &infoStruct);
return true;
}
} // namespace angle
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// png_utils: Wrapper around libpng.
//
#ifndef UTIL_PNG_UTILS_H_
#define UTIL_PNG_UTILS_H_
#include <cstdint>
#include <vector>
namespace angle
{
bool SavePNG(const char *fileName,
const char *title,
uint32_t width,
uint32_t height,
const std::vector<uint8_t> &data);
} // namespace angle
#endif // UTIL_PNG_UTILS_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment