Headless: force opaque PNG screenshots by default, add --screenshot-keep-alpha

Games often use the framebuffer alpha channel for non-visual purposes,
so saved PNGs could look fully transparent in image viewers. Force alpha
to 255 when writing PNG screenshots unless --screenshot-keep-alpha is
passed. The MSE comparison ignores alpha either way.
This commit is contained in:
Henrik Rydgård committed 2026-08-09 13:41:39 +02:00
1 parent 032781e301
commit 8c9bf04add
7 files changed
+19 -4

No files matched your search

+1
View File
@@ -183,6 +183,7 @@ static const CommandLineParam g_autoParams[] = {
{POFF(screenshotFilename), CmdParamType::String, "screenshot", '\0', "Compare rendered output against a reference screenshot FILE", CmdLineMode::Headless},
{POFF(screenshotFilenameSave), CmdParamType::String, "screenshot-save", '\0', "Save rendered screenshot to specified path (PNG if the path ends in .png, BMP otherwise)", CmdLineMode::Headless},
{POFF(screenshotFilenameDiff), CmdParamType::String, "screenshot-diff", '\0', "Save a visual comparison image to FILE when comparing screenshots", CmdLineMode::Headless},
{POFF(screenshotSaveKeepAlpha), CmdParamType::Bool, "screenshot-keep-alpha", '\0', "Preserve the alpha channel when saving PNG screenshots (default: alpha is forced to 255)", CmdLineMode::Headless},
{POFF(timeout), CmdParamType::Double, "timeout", '\0', "Set the timeout value", CmdLineMode::Headless},
{POFF(maxScreenshotError), CmdParamType::Double, "max-mse", '\0', "Maximum allowed MSE error for screenshot comparison", CmdLineMode::Headless},
{POFF(mountIso), CmdParamType::String, "mount", 'm', "Mount ISO/CSO on umd1:", CmdLineMode::Headless},
+2
View File
@@ -96,6 +96,8 @@ struct CommandLineOptions {
std::optional<std::string> screenshotFilename;
std::optional<std::string> screenshotFilenameSave;
std::optional<std::string> screenshotFilenameDiff;
// Headless: preserve the alpha channel when saving PNG screenshots.
std::optional<bool> screenshotSaveKeepAlpha;
// Headless: mount an ISO/CSO on umd1:.
std::optional<std::string> mountIso;
+4
View File
@@ -108,6 +108,10 @@ test is kept in `<outputRoot>/logs/`.
- PNG, 512×272 (480×272 display in a 512-wide framebuffer), stored top-down
(row 0 = top of screen). The BMP output format is bottom-up per the BMP
spec; the flip is applied only when writing BMPs.
- The alpha channel is forced to 255 when writing PNGs (games often use alpha
for non-visual purposes, which would otherwise produce transparent-looking
images); pass `--screenshot-keep-alpha` to the headless binary to preserve
it (e.g. via a variant). The MSE comparison ignores alpha either way.
- Generated with `--graphics=software` they are fully deterministic: a
subsequent run produces byte-identical output, so `maxMse` can be 0.
- If rendering code changes the output, existing references may need
+5 -2
View File
@@ -459,7 +459,7 @@ bool ScreenshotComparer::SaveActualBitmap(const Path &resultFilename) {
return false;
}
bool ScreenshotComparer::SaveActualPNG(const Path &resultFilename) {
bool ScreenshotComparer::SaveActualPNG(const Path &resultFilename, bool keepAlpha) {
std::vector<u8> rgba((size_t)stride_ * h_ * 4);
const u32 *pixels = pixels_.data();
for (size_t i = 0; i < (size_t)stride_ * h_; ++i) {
@@ -467,7 +467,10 @@ bool ScreenshotComparer::SaveActualPNG(const Path &resultFilename) {
rgba[i * 4 + 0] = (p >> 16) & 0xFF;
rgba[i * 4 + 1] = (p >> 8) & 0xFF;
rgba[i * 4 + 2] = p & 0xFF;
rgba[i * 4 + 3] = (p >> 24) & 0xFF;
// Games often use the alpha channel for non-visual purposes, which
// results in fully transparent images in PNG viewers. By default,
// force alpha to 255 when writing the image.
rgba[i * 4 + 3] = keepAlpha ? ((p >> 24) & 0xFF) : 0xFF;
}
return pngSave(resultFilename, rgba.data(), stride_, h_, 4);
}
+1 -1
View File
@@ -47,7 +47,7 @@ public:
}
bool SaveActualBitmap(const Path &filename);
bool SaveActualPNG(const Path &filename);
bool SaveActualPNG(const Path &filename, bool keepAlpha = false);
bool SaveVisualComparisonPNG(const Path &filename);
protected:
+5 -1
View File
@@ -71,6 +71,7 @@
static Path g_comparisonScreenshot;
static Path g_screenshotSavePath;
static Path g_screenshotDiffPath;
static bool g_screenshotSaveKeepAlpha = false;
static double g_maxScreenshotError = 0.0;
static bool g_screenshotFailed = false;
static std::string g_debugOutputBuffer;
@@ -202,7 +203,7 @@ void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {
// If a screenshot save path is set, save unconditionally.
if (!g_screenshotSavePath.empty()) {
ScreenshotComparer saver(pixels, FRAME_STRIDE, FRAME_WIDTH, FRAME_HEIGHT);
bool saved = g_screenshotSavePath.GetFileExtension() == ".png" ? saver.SaveActualPNG(g_screenshotSavePath) : saver.SaveActualBitmap(g_screenshotSavePath);
bool saved = g_screenshotSavePath.GetFileExtension() == ".png" ? saver.SaveActualPNG(g_screenshotSavePath, g_screenshotSaveKeepAlpha) : saver.SaveActualBitmap(g_screenshotSavePath);
if (saved)
SendAndCollectOutput("Screenshot saved to: " + g_screenshotSavePath.ToVisualString() + "\n");
}
@@ -782,6 +783,9 @@ int main(int argc, const char* argv[]) {
if (cmdLineOptions.screenshotFilenameDiff.has_value()) {
g_screenshotDiffPath = Path(std::string(cmdLineOptions.screenshotFilenameDiff.value()));
}
if (cmdLineOptions.screenshotSaveKeepAlpha.has_value()) {
g_screenshotSaveKeepAlpha = cmdLineOptions.screenshotSaveKeepAlpha.value();
}
SetWriteFailureScreenshot(!getenv("GITHUB_ACTIONS") && !testOptions.bench);
SetWriteDebugOutput(!testOptions.compare && !testOptions.bench);
+1
View File
@@ -45,6 +45,7 @@ PPSSPPHeadless file.elf|file.prx|file.ppdmp [...] [options]
| `--screenshot=<file>` | Compare the rendered output against a reference screenshot. |
| `--screenshot-save=<file>` | Save the rendered output to a file (PNG if the path ends in `.png`, BMP otherwise). |
| `--screenshot-diff=<file>` | When comparing screenshots, save a visual comparison image to this file (always, regardless of pass/fail). |
| `--screenshot-keep-alpha` | Preserve the alpha channel when saving PNG screenshots (default: alpha is forced to 255, since games often use it for non-visual purposes). |
| `--max-mse=<number>` | Maximum allowed Mean Squared Error for screenshot comparison (default: 0 = exact). |
| `--compare` / `-c` | Compare test output with `.expected` text file and/or screenshot (see below). |
| `--timeout=<seconds>` | Abort test if it takes longer than this. |