mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Feat] (trace-replay): add Minecraft main menu trace
This commit is contained in:
@@ -443,6 +443,59 @@ int ChannelValue(const RgbaImage& image, int x, int y, int channel) {
|
|||||||
return image.pixels[(static_cast<std::size_t>(y) * image.width + x) * 4 + channel];
|
return image.pixels[(static_cast<std::size_t>(y) * image.width + x) * 4 + channel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WriteDifferenceImage(const Result& result,
|
||||||
|
const RgbaImage& actual,
|
||||||
|
const RgbaImage& golden,
|
||||||
|
int x0,
|
||||||
|
int y0,
|
||||||
|
int compareWidth,
|
||||||
|
int compareHeight,
|
||||||
|
int fuzz,
|
||||||
|
std::string& error) {
|
||||||
|
if (result.diffPath.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int kDiffScale = 8;
|
||||||
|
RgbaImage diff;
|
||||||
|
diff.width = actual.width;
|
||||||
|
diff.height = actual.height;
|
||||||
|
diff.pixels.assign(static_cast<std::size_t>(diff.width) * diff.height * 4, 0);
|
||||||
|
for (int y = 0; y < diff.height; ++y) {
|
||||||
|
for (int x = 0; x < diff.width; ++x) {
|
||||||
|
std::uint8_t* dst = diff.pixels.data() + (static_cast<std::size_t>(y) * diff.width + x) * 4;
|
||||||
|
dst[3] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < compareHeight; ++y) {
|
||||||
|
for (int x = 0; x < compareWidth; ++x) {
|
||||||
|
int imageX = x0 + x;
|
||||||
|
int imageY = y0 + y;
|
||||||
|
int dr = std::abs(ChannelValue(actual, imageX, imageY, 0) -
|
||||||
|
ChannelValue(golden, imageX, imageY, 0));
|
||||||
|
int dg = std::abs(ChannelValue(actual, imageX, imageY, 1) -
|
||||||
|
ChannelValue(golden, imageX, imageY, 1));
|
||||||
|
int db = std::abs(ChannelValue(actual, imageX, imageY, 2) -
|
||||||
|
ChannelValue(golden, imageX, imageY, 2));
|
||||||
|
bool different = dr > fuzz || dg > fuzz || db > fuzz;
|
||||||
|
std::uint8_t* dst = diff.pixels.data() +
|
||||||
|
(static_cast<std::size_t>(imageY) * diff.width + imageX) * 4;
|
||||||
|
if (different) {
|
||||||
|
dst[0] = 0xff;
|
||||||
|
dst[1] = static_cast<std::uint8_t>(std::min(255, dg * kDiffScale));
|
||||||
|
dst[2] = static_cast<std::uint8_t>(std::min(255, db * kDiffScale));
|
||||||
|
} else {
|
||||||
|
dst[0] = static_cast<std::uint8_t>(std::min(255, dr * kDiffScale));
|
||||||
|
dst[1] = static_cast<std::uint8_t>(std::min(255, dg * kDiffScale));
|
||||||
|
dst[2] = static_cast<std::uint8_t>(std::min(255, db * kDiffScale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return WritePngRgba(result.diffPath, diff, error);
|
||||||
|
}
|
||||||
|
|
||||||
bool CompareWithGolden(const Request& request, Result& result) {
|
bool CompareWithGolden(const Request& request, Result& result) {
|
||||||
if (request.goldenPath.empty()) {
|
if (request.goldenPath.empty()) {
|
||||||
result.passed = true;
|
result.passed = true;
|
||||||
@@ -511,6 +564,13 @@ bool CompareWithGolden(const Request& request, Result& result) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string diffError;
|
||||||
|
if (!WriteDifferenceImage(result, actual, golden, x0, y0, compareWidth, compareHeight, fuzz, diffError)) {
|
||||||
|
result.statusCode = STATUS_IO_ERROR;
|
||||||
|
result.message = diffError.empty() ? "failed to write diff PNG" : diffError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
result.mismatchPixels = mismatch;
|
result.mismatchPixels = mismatch;
|
||||||
result.passed = mismatch <= request.tolerance;
|
result.passed = mismatch <= request.tolerance;
|
||||||
result.statusCode = result.passed ? STATUS_OK : STATUS_COMPARE_FAILED;
|
result.statusCode = result.passed ? STATUS_OK : STATUS_COMPARE_FAILED;
|
||||||
@@ -540,6 +600,7 @@ bool WriteResultJson(const Request& request, const Result& result) {
|
|||||||
file << " \"tracePath\": \"" << JsonEscape(request.tracePath) << "\",\n";
|
file << " \"tracePath\": \"" << JsonEscape(request.tracePath) << "\",\n";
|
||||||
file << " \"goldenPath\": \"" << JsonEscape(request.goldenPath) << "\",\n";
|
file << " \"goldenPath\": \"" << JsonEscape(request.goldenPath) << "\",\n";
|
||||||
file << " \"actualPath\": \"" << JsonEscape(result.actualPath) << "\",\n";
|
file << " \"actualPath\": \"" << JsonEscape(result.actualPath) << "\",\n";
|
||||||
|
file << " \"diffPath\": \"" << JsonEscape(result.diffPath) << "\",\n";
|
||||||
file << " \"backend\": \"" << JsonEscape(request.backend) << "\",\n";
|
file << " \"backend\": \"" << JsonEscape(request.backend) << "\",\n";
|
||||||
file << " \"targetFrame\": " << request.targetFrame << ",\n";
|
file << " \"targetFrame\": " << request.targetFrame << ",\n";
|
||||||
file << " \"targetCall\": " << request.targetCall << ",\n";
|
file << " \"targetCall\": " << request.targetCall << ",\n";
|
||||||
@@ -560,6 +621,7 @@ Result RunTraceReplay(const Request& request) {
|
|||||||
Result result;
|
Result result;
|
||||||
result.resultPath = request.outputDir + "/result.json";
|
result.resultPath = request.outputDir + "/result.json";
|
||||||
result.actualPath = request.outputDir + "/actual.png";
|
result.actualPath = request.outputDir + "/actual.png";
|
||||||
|
result.diffPath = request.diffPath;
|
||||||
const std::string mobileGlLogPath = request.outputDir + "/mobilegl.log";
|
const std::string mobileGlLogPath = request.outputDir + "/mobilegl.log";
|
||||||
|
|
||||||
if (!EnsureDirectory(request.outputDir)) {
|
if (!EnsureDirectory(request.outputDir)) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct Request {
|
|||||||
std::string tracePath;
|
std::string tracePath;
|
||||||
std::string goldenPath;
|
std::string goldenPath;
|
||||||
std::string outputDir;
|
std::string outputDir;
|
||||||
|
std::string diffPath;
|
||||||
std::string backend;
|
std::string backend;
|
||||||
std::string mobileGlLibrary = "libMobileGL.so";
|
std::string mobileGlLibrary = "libMobileGL.so";
|
||||||
int targetFrame = -1;
|
int targetFrame = -1;
|
||||||
@@ -38,6 +39,7 @@ struct Result {
|
|||||||
std::string message;
|
std::string message;
|
||||||
std::string resultPath;
|
std::string resultPath;
|
||||||
std::string actualPath;
|
std::string actualPath;
|
||||||
|
std::string diffPath;
|
||||||
long long mismatchPixels = -1;
|
long long mismatchPixels = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -365,3 +365,13 @@ add_trace_replay_test_for_backends(minecraft-1.21.4-startup
|
|||||||
HEIGHT 480
|
HEIGHT 480
|
||||||
TOLERANCE 20
|
TOLERANCE 20
|
||||||
FUZZ_PERCENT 20)
|
FUZZ_PERCENT 20)
|
||||||
|
|
||||||
|
add_trace_replay_test_for_backends(minecraft-1.21.4-main-menu
|
||||||
|
TRACE_ARCHIVE ${MOBILEGL_TRACE_ROOT}/fixtures/minecraft-1.21.4-main-menu.tgz
|
||||||
|
TRACE_FILE trace.trace
|
||||||
|
GOLDEN ${MOBILEGL_TRACE_ROOT}/fixtures/minecraft-1.21.4-main-menu.0000481787.png
|
||||||
|
TARGET_CALL 481787
|
||||||
|
WIDTH 854
|
||||||
|
HEIGHT 480
|
||||||
|
TOLERANCE 500
|
||||||
|
FUZZ_PERCENT 20)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ The bundled fixtures cover:
|
|||||||
traces rejected because they rely on compatibility/fixed-function OpenGL.
|
traces rejected because they rely on compatibility/fixed-function OpenGL.
|
||||||
- `minecraft-1.21.4-startup`: captured from Minecraft 1.21.4's Mojang Studios startup screen. The fixture is trimmed
|
- `minecraft-1.21.4-startup`: captured from Minecraft 1.21.4's Mojang Studios startup screen. The fixture is trimmed
|
||||||
to call 92195, immediately after the startup screen draw into Minecraft's offscreen framebuffer.
|
to call 92195, immediately after the startup screen draw into Minecraft's offscreen framebuffer.
|
||||||
|
- `minecraft-1.21.4-main-menu`: captured from Minecraft 1.21.4's English (US) main menu. The fixture is trimmed to
|
||||||
|
call 481787, immediately after the main menu draw into Minecraft's offscreen framebuffer.
|
||||||
|
|
||||||
Build from the MobileGL repository root:
|
Build from the MobileGL repository root:
|
||||||
|
|
||||||
@@ -48,6 +50,10 @@ build-test/tools/trace_replay/mobilegl_trace_replay \
|
|||||||
|
|
||||||
The Minecraft startup fixture uses `tools/trace_replay/fixtures/minecraft-1.21.4-startup.tgz`, golden image
|
The Minecraft startup fixture uses `tools/trace_replay/fixtures/minecraft-1.21.4-startup.tgz`, golden image
|
||||||
`minecraft-1.21.4-startup.0000092195.png`, `--target-call 92195`, `--width 854`, and `--height 480`.
|
`minecraft-1.21.4-startup.0000092195.png`, `--target-call 92195`, `--width 854`, and `--height 480`.
|
||||||
|
The Minecraft main-menu fixture uses `tools/trace_replay/fixtures/minecraft-1.21.4-main-menu.tgz`, golden image
|
||||||
|
`minecraft-1.21.4-main-menu.0000481787.png`, `--target-call 481787`, `--width 854`, `--height 480`, and
|
||||||
|
`--tolerance 500`.
|
||||||
|
Each replay that has a golden image writes `${case}-diff.png` in the backend output directory.
|
||||||
|
|
||||||
The Linux runner loads MobileGL by path with `dlopen`. It uses an EGL pbuffer for `DirectGLES` and maps the pbuffer
|
The Linux runner loads MobileGL by path with `dlopen`. It uses an EGL pbuffer for `DirectGLES` and maps the pbuffer
|
||||||
path to `VK_EXT_headless_surface` for `DirectVulkan`, so CI does not need an X11 or Wayland window.
|
path to `VK_EXT_headless_surface` for `DirectVulkan`, so CI does not need an X11 or Wayland window.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 402 KiB |
Binary file not shown.
@@ -50,6 +50,7 @@ execute_process(
|
|||||||
COMMAND "${TRACE_REPLAY_EXE}"
|
COMMAND "${TRACE_REPLAY_EXE}"
|
||||||
--trace "${trace_path}"
|
--trace "${trace_path}"
|
||||||
--golden "${TRACE_GOLDEN}"
|
--golden "${TRACE_GOLDEN}"
|
||||||
|
--diff "${TRACE_OUTPUT_DIR}/output/${TRACE_CASE_NAME}-diff.png"
|
||||||
--output "${TRACE_OUTPUT_DIR}/output"
|
--output "${TRACE_OUTPUT_DIR}/output"
|
||||||
--backend "${TRACE_BACKEND}"
|
--backend "${TRACE_BACKEND}"
|
||||||
--mobilegl-library "${MOBILEGL_LIBRARY}"
|
--mobilegl-library "${MOBILEGL_LIBRARY}"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ void PrintUsage(const char *argv0) {
|
|||||||
<< "\n"
|
<< "\n"
|
||||||
<< "Options:\n"
|
<< "Options:\n"
|
||||||
<< " --golden PATH Golden PNG to compare against\n"
|
<< " --golden PATH Golden PNG to compare against\n"
|
||||||
|
<< " --diff PATH Difference PNG output path\n"
|
||||||
<< " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n"
|
<< " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n"
|
||||||
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
|
<< " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n"
|
||||||
<< " --width N Replay surface width override\n"
|
<< " --width N Replay surface width override\n"
|
||||||
@@ -63,6 +64,8 @@ bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) {
|
|||||||
if (!ReadValue(argc, argv, i, request.tracePath)) return false;
|
if (!ReadValue(argc, argv, i, request.tracePath)) return false;
|
||||||
} else if (arg == "--golden") {
|
} else if (arg == "--golden") {
|
||||||
if (!ReadValue(argc, argv, i, request.goldenPath)) return false;
|
if (!ReadValue(argc, argv, i, request.goldenPath)) return false;
|
||||||
|
} else if (arg == "--diff") {
|
||||||
|
if (!ReadValue(argc, argv, i, request.diffPath)) return false;
|
||||||
} else if (arg == "--output") {
|
} else if (arg == "--output") {
|
||||||
if (!ReadValue(argc, argv, i, request.outputDir)) return false;
|
if (!ReadValue(argc, argv, i, request.outputDir)) return false;
|
||||||
} else if (arg == "--backend") {
|
} else if (arg == "--backend") {
|
||||||
@@ -131,6 +134,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
std::cout << result.message << "\n"
|
std::cout << result.message << "\n"
|
||||||
<< "result: " << result.resultPath << "\n"
|
<< "result: " << result.resultPath << "\n"
|
||||||
<< "actual: " << result.actualPath << "\n";
|
<< "actual: " << result.actualPath << "\n"
|
||||||
|
<< "diff: " << result.diffPath << "\n";
|
||||||
return result.passed ? 0 : result.statusCode;
|
return result.passed ? 0 : result.statusCode;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user