/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include extern "C" { #include "common/types.h" #include "interface/test_capture.h" } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr unsigned kWidth = 127; constexpr unsigned kHeight = 95; constexpr unsigned kFrameSerial = 4; struct FormatCase { const char * name; FrameType type; bool hdr; bool pq; }; struct DamageCase { const char * name; }; struct StrideCase { const char * name; unsigned stride; }; struct LaunchCase { const char * renderer; unsigned width; unsigned height; unsigned rotate; bool capture; }; struct GeometryCase { const char * name; unsigned width; unsigned height; unsigned rotate; unsigned destX; unsigned destY; unsigned destWidth; unsigned destHeight; }; constexpr LaunchCase kDefaultLaunch = { "EGL", kWidth, kHeight, 0, true }; struct Capture { LG_TestCaptureHeader header {}; std::vector data; std::filesystem::path directory; std::filesystem::path path; std::filesystem::path log; }; struct RGB { float r; float g; float b; }; std::string readText(const std::filesystem::path & path) { std::ifstream input(path); return std::string( std::istreambuf_iterator(input), std::istreambuf_iterator()); } void expectX11Log(const std::string & clientLog) { EXPECT_NE(clientLog.find("X11 XInput"), std::string::npos) << clientLog; } std::filesystem::path makeTempDirectory() { std::array value {}; std::snprintf(value.data(), value.size(), "/tmp/lg-render-case.XXXXXX"); char * result = mkdtemp(value.data()); if (!result) return {}; return result; } int runClient(const FormatCase & format, const char * damage, const std::filesystem::path & capture, const std::filesystem::path & log, unsigned stride, const LaunchCase & launch) { const std::string size = std::to_string(launch.width) + "x" + std::to_string(launch.height); const std::string width = "test:width=" + std::to_string(kWidth); const std::string height = "test:height=" + std::to_string(kHeight); const std::string strideArg = "test:stride=" + std::to_string(stride); const std::string formatArg = std::string("test:format=") + format.name; const std::string damageArg = std::string("test:damage=") + damage; const std::string rendererArg = std::string("app:renderer=") + launch.renderer; const std::string count = "test:frameCount=" + std::to_string( launch.capture ? kFrameSerial : 30); const std::string captureFile = "test:captureFile=" + capture.string(); const std::string captureFrame = "test:captureFrame=" + std::to_string(kFrameSerial); const std::string rotate = "win:rotate=" + std::to_string(launch.rotate); std::vector args = { LG_CLIENT_PATH, "app:transport=test", rendererArg, width, height, strideArg, formatArg, damageArg, "test:frameRate=60", count, launch.capture ? "test:holdLastFrame=yes" : "test:holdLastFrame=no", launch.capture ? "test:realtime=no" : "test:realtime=yes", "win:size=" + size, rotate, "win:autoResize=no", "win:allowResize=no", "win:quickSplash=yes", "win:alerts=no", "win:noScreensaver=no", "spice:enable=no", "egl:multisample=no", "egl:scale=1", "opengl:mipmap=no", "opengl:vsync=no", "opengl:amdPinnedMem=no", "opengl:preventBuffer=yes", }; if (launch.capture) { args.push_back(captureFile); args.push_back(captureFrame); args.emplace_back("test:captureDelay=1"); } std::vector argv; argv.reserve(args.size() + 1); for (std::string & arg : args) argv.push_back(arg.data()); argv.push_back(nullptr); const pid_t pid = fork(); if (pid < 0) return -1; if (pid == 0) { setenv("XDG_CONFIG_HOME", capture.parent_path().c_str(), 1); setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); const int logFd = open(log.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); if (logFd >= 0) { dup2(logFd, STDOUT_FILENO); dup2(logFd, STDERR_FILENO); close(logFd); } execv(LG_CLIENT_PATH, argv.data()); _exit(127); } int status = 0; const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(20); while (std::chrono::steady_clock::now() < deadline) { const pid_t result = waitpid(pid, &status, WNOHANG); if (result == pid) return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); if (result < 0) return -1; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } kill(pid, SIGTERM); if (waitpid(pid, &status, 0) < 0) return -1; return 124; } Capture produceCapture(const FormatCase & format, const char * damage, unsigned stride = 0, const LaunchCase & launch = kDefaultLaunch) { Capture result; result.directory = makeTempDirectory(); if (result.directory.empty()) return result; std::string artifactName = std::string(format.name) + "-" + damage; if (stride) artifactName += "-stride" + std::to_string(stride); result.path = result.directory / (artifactName + ".lgcapture"); result.log = result.directory / (artifactName + ".log"); const int status = runClient( format, damage, result.path, result.log, stride, launch); EXPECT_EQ(status, 0) << readText(result.log); if (status != 0) return result; std::ifstream input(result.path, std::ios::binary); EXPECT_TRUE(input.good()) << "capture missing; client log:\n" << readText(result.log); if (!input) return result; input.read(reinterpret_cast(&result.header), sizeof(result.header)); EXPECT_EQ(input.gcount(), static_cast(sizeof(result.header))); EXPECT_EQ(result.header.magic, LG_TEST_CAPTURE_MAGIC); EXPECT_EQ(result.header.version, LG_TEST_CAPTURE_VERSION); EXPECT_EQ(result.header.headerSize, sizeof(result.header)); EXPECT_EQ(result.header.frameSerial, kFrameSerial); EXPECT_EQ(result.header.sourceType, static_cast(format.type)); EXPECT_EQ(result.header.width, launch.width); EXPECT_EQ(result.header.height, launch.height); EXPECT_EQ(result.header.flags & LG_TEST_CAPTURE_HDR, format.hdr ? static_cast(LG_TEST_CAPTURE_HDR) : 0u); EXPECT_EQ(result.header.flags & LG_TEST_CAPTURE_HDR_PQ, format.pq ? static_cast(LG_TEST_CAPTURE_HDR_PQ) : 0u); if (result.header.flags & LG_TEST_CAPTURE_NATIVE_HDR) { const std::string clientLog = readText(result.log); if (format.pq) { EXPECT_NE(clientLog.find( "HDR image description requested (PQ, BT.2020, " "referenceWhite:203 cd/m² maxLum:1000 cd/m²"), std::string::npos); EXPECT_NE(clientLog.find("maxCLL:1000 maxFALL:400"), std::string::npos); } else EXPECT_NE(clientLog.find( "HDR image description requested (scRGB, Windows-scRGB)"), std::string::npos); } result.data.resize(result.header.dataSize); input.read(reinterpret_cast(result.data.data()), result.data.size()); EXPECT_EQ(input.gcount(), static_cast(result.data.size())); return result; } RGB generatedColor(unsigned x, unsigned y, unsigned serial) { uint8_t r = static_cast(x) * 255 / (kWidth - 1); uint8_t g = static_cast(y) * 255 / (kHeight - 1); uint8_t b = ((x / 32) ^ (y / 32)) & 1 ? 0x30 : 0x90; const unsigned boxSize = std::min({kWidth, kHeight, 64u}); const unsigned boxX = (serial * 7) % (kWidth - boxSize); const unsigned boxY = (serial * 5) % (kHeight - boxSize); if (x >= boxX && y >= boxY && x < boxX + boxSize && y < boxY + boxSize) { const uint32_t color = serial * UINT32_C(2654435761); r = color >> 16; g = color >> 8; b = color; } return { static_cast(r) / 255.0f, static_cast(g) / 255.0f, static_cast(b) / 255.0f, }; } float linearToPQ(float linear) { constexpr float m1 = 2610.0f / 16384.0f; constexpr float m2 = 2523.0f / 32.0f; constexpr float c1 = 3424.0f / 4096.0f; constexpr float c2 = 2413.0f / 128.0f; constexpr float c3 = 2392.0f / 128.0f; const float p = std::pow(std::max(linear, 0.0f), m1); return std::pow((c1 + c2 * p) / (1.0f + c3 * p), m2); } float pqToLinear(float pq) { constexpr float m1inv = 16384.0f / 2610.0f; constexpr float m2inv = 32.0f / 2523.0f; constexpr float c1 = 3424.0f / 4096.0f; constexpr float c2 = 2413.0f / 128.0f; constexpr float c3 = 2392.0f / 128.0f; const float p = std::pow(std::max(pq, 0.0f), m2inv); const float d = std::max(p - c1, 0.0f) / (c2 - c3 * p); return std::pow(d, m1inv); } float quantizePQ(float scRGB) { constexpr unsigned lutSize = 4096; const unsigned index = scRGB <= 0.0f ? 0 : scRGB >= 4.0f ? lutSize : static_cast(scRGB * (lutSize / 4.0f) + 0.5f); const float lutScRGB = static_cast(index) * 4.0f / lutSize; const float pq = linearToPQ(lutScRGB / 125.0f); const unsigned code = static_cast(pq * 1023.0f + 0.5f); return static_cast(code) / 1023.0f; } uint16_t floatToHalf(float value) { uint32_t bits; std::memcpy(&bits, &value, sizeof(bits)); const uint16_t sign = (bits >> 16) & 0x8000; int exponent = ((bits >> 23) & 0xff) - 127 + 15; uint32_t mantissa = bits & 0x7fffff; if (exponent <= 0) return sign; if (exponent >= 31) return sign | 0x7c00; mantissa += 0x1000; if (mantissa & 0x800000) { mantissa = 0; if (++exponent >= 31) return sign | 0x7c00; } return sign | (static_cast(exponent) << 10) | (mantissa >> 13); } float halfToFloat(uint16_t value) { const uint32_t sign = static_cast(value & 0x8000) << 16; uint32_t exponent = (value >> 10) & 0x1f; uint32_t mantissa = value & 0x3ff; uint32_t bits; if (exponent == 0) bits = sign; else if (exponent == 31) bits = sign | 0x7f800000 | (mantissa << 13); else { exponent = exponent - 15 + 127; bits = sign | (exponent << 23) | (mantissa << 13); } float result; std::memcpy(&result, &bits, sizeof(result)); return result; } RGB toBT2020(RGB value) { return { value.r * 0.6274039f + value.g * 0.3292830f + value.b * 0.0433131f, value.r * 0.0690973f + value.g * 0.9195404f + value.b * 0.0113623f, value.r * 0.0163914f + value.g * 0.0880133f + value.b * 0.8955953f, }; } RGB toBT709(RGB value) { return { value.r * 1.6604910f + value.g * -0.5876411f + value.b * -0.0728499f, value.r * -0.1245505f + value.g * 1.1328999f + value.b * -0.0083494f, value.r * -0.0181508f + value.g * -0.1005789f + value.b * 1.1187297f, }; } float linearToSRGB(float value) { return value >= 0.0031308f ? std::pow(std::max(value, 0.0f), 1.0f / 2.4f) * 1.055f - 0.055f : value * 12.92f; } RGB compressAndEncode(RGB value) { value.r = std::max(value.r, 0.0f); value.g = std::max(value.g, 0.0f); value.b = std::max(value.b, 0.0f); const float peak = std::max({value.r, value.g, value.b}); if (peak > 0.0f) { const float compressed = peak < 0.75f ? peak : 0.75f + (peak - 0.75f) * 0.25f; const float scale = compressed / peak; value.r *= scale; value.g *= scale; value.b *= scale; } value.r = linearToSRGB(std::clamp(value.r, 0.0f, 1.0f)); value.g = linearToSRGB(std::clamp(value.g, 0.0f, 1.0f)); value.b = linearToSRGB(std::clamp(value.b, 0.0f, 1.0f)); return value; } RGB expectedColor(const FormatCase & format, const Capture & capture, unsigned x, unsigned y) { const RGB generated = generatedColor(x, y, kFrameSerial); if (!format.hdr) return generated; if (format.type == FRAME_TYPE_RGBA16F) { RGB scRGB = { halfToFloat(floatToHalf(generated.r * 4.0f)), halfToFloat(floatToHalf(generated.g * 4.0f)), halfToFloat(floatToHalf(generated.b * 4.0f)), }; if (capture.header.flags & LG_TEST_CAPTURE_NATIVE_HDR) return scRGB; scRGB.r *= 80.0f / 250.0f; scRGB.g *= 80.0f / 250.0f; scRGB.b *= 80.0f / 250.0f; return compressAndEncode(scRGB); } RGB linear709 = { generated.r * 4.0f, generated.g * 4.0f, generated.b * 4.0f, }; RGB encoded2020 = toBT2020(linear709); encoded2020.r = quantizePQ(encoded2020.r); encoded2020.g = quantizePQ(encoded2020.g); encoded2020.b = quantizePQ(encoded2020.b); if (capture.header.flags & LG_TEST_CAPTURE_NATIVE_HDR) return encoded2020; RGB linear2020 = { pqToLinear(encoded2020.r) * (10000.0f / 250.0f), pqToLinear(encoded2020.g) * (10000.0f / 250.0f), pqToLinear(encoded2020.b) * (10000.0f / 250.0f), }; return compressAndEncode(toBT709(linear2020)); } RGB capturedColor(const Capture & capture, unsigned x, unsigned y) { const bool bottomUp = capture.header.flags & LG_TEST_CAPTURE_BOTTOM_UP; const unsigned row = bottomUp ? capture.header.height - 1 - y : y; const uint8_t * pixel = capture.data.data() + row * capture.header.stride; switch (capture.header.captureFormat) { case LG_TEST_CAPTURE_RGBA8: pixel += x * 4; return { pixel[0] / 255.0f, pixel[1] / 255.0f, pixel[2] / 255.0f, }; case LG_TEST_CAPTURE_RGB10_A2: { uint32_t packed; std::memcpy(&packed, pixel + x * sizeof(packed), sizeof(packed)); return { static_cast( packed & 0x3ff) / 1023.0f, static_cast((packed >> 10) & 0x3ff) / 1023.0f, static_cast((packed >> 20) & 0x3ff) / 1023.0f, }; } case LG_TEST_CAPTURE_RGBA32F: { std::array value; std::memcpy(value.data(), pixel + x * sizeof(value), sizeof(value)); return {value[0], value[1], value[2]}; } default: ADD_FAILURE() << "Unknown capture format: " << capture.header.captureFormat; return {}; } } void compareReference(const FormatCase & format, const Capture & capture) { float maxError = 0.0f; unsigned maxX = 0; unsigned maxY = 0; RGB maxActual {}; RGB maxExpected {}; for (unsigned y = 0; y < capture.header.height; ++y) for (unsigned x = 0; x < capture.header.width; ++x) { const RGB actual = capturedColor(capture, x, y); const RGB expected = expectedColor(format, capture, x, y); const float error = std::max({ std::abs(actual.r - expected.r), std::abs(actual.g - expected.g), std::abs(actual.b - expected.b), }); if (error > maxError) { maxError = error; maxX = x; maxY = y; maxActual = actual; maxExpected = expected; } } const float tolerance = format.hdr ? 0.012f : 0.006f; EXPECT_LE(maxError, tolerance) << "max error at (" << maxX << ", " << maxY << ")" << "\nactual: " << maxActual.r << ", " << maxActual.g << ", " << maxActual.b << "\nexpected: " << maxExpected.r << ", " << maxExpected.g << ", " << maxExpected.b << "\ncapture retained at: " << capture.path; } unsigned scaledCoordinate(unsigned coordinate, unsigned sourceSize, unsigned destinationSize) { return (static_cast(coordinate) * 2 + 1) * sourceSize / (static_cast(destinationSize) * 2); } bool geometrySource(const GeometryCase & geometry, unsigned x, unsigned y, unsigned & sourceX, unsigned & sourceY) { if (x < geometry.destX || x >= geometry.destX + geometry.destWidth || y < geometry.destY || y >= geometry.destY + geometry.destHeight) return false; const unsigned localX = x - geometry.destX; const unsigned localY = y - geometry.destY; switch (geometry.rotate) { case 0: sourceX = scaledCoordinate(localX, kWidth, geometry.destWidth); sourceY = scaledCoordinate(localY, kHeight, geometry.destHeight); return true; case 90: sourceX = scaledCoordinate(localY, kWidth, geometry.destHeight); sourceY = kHeight - 1 - scaledCoordinate(localX, kHeight, geometry.destWidth); return true; case 180: sourceX = kWidth - 1 - scaledCoordinate(localX, kWidth, geometry.destWidth); sourceY = kHeight - 1 - scaledCoordinate(localY, kHeight, geometry.destHeight); return true; case 270: sourceX = kWidth - 1 - scaledCoordinate(localY, kWidth, geometry.destHeight); sourceY = scaledCoordinate(localX, kHeight, geometry.destWidth); return true; } ADD_FAILURE() << "Unknown rotation: " << geometry.rotate; return false; } void compareGeometry(const FormatCase & format, const GeometryCase & geometry, const Capture & capture) { float maxError = 0.0f; unsigned maxX = 0; unsigned maxY = 0; RGB maxActual {}; RGB maxExpected {}; for (unsigned y = 0; y < capture.header.height; ++y) for (unsigned x = 0; x < capture.header.width; ++x) { unsigned sourceX; unsigned sourceY; const RGB actual = capturedColor(capture, x, y); const RGB expected = geometrySource( geometry, x, y, sourceX, sourceY) ? expectedColor(format, capture, sourceX, sourceY) : RGB {}; const float error = std::max({ std::abs(actual.r - expected.r), std::abs(actual.g - expected.g), std::abs(actual.b - expected.b), }); if (error > maxError) { maxError = error; maxX = x; maxY = y; maxActual = actual; maxExpected = expected; } } EXPECT_LE(maxError, 0.006f) << "max error at (" << maxX << ", " << maxY << ")" << "\nactual: " << maxActual.r << ", " << maxActual.g << ", " << maxActual.b << "\nexpected: " << maxExpected.r << ", " << maxExpected.g << ", " << maxExpected.b << "\ncapture retained at: " << capture.path; } using RenderCase = std::tuple; class RenderPipelineTest : public testing::TestWithParam { }; TEST_P(RenderPipelineTest, MatchesReference) { const FormatCase & format = std::get<0>(GetParam()); const DamageCase & damage = std::get<1>(GetParam()); Capture capture = produceCapture(format, damage.name); ASSERT_FALSE(capture.data.empty()) << "artifacts: " << capture.directory; compareReference(format, capture); if (!testing::Test::HasFailure()) std::filesystem::remove_all(capture.directory); } std::string renderCaseName( const testing::TestParamInfo & info) { return std::string(std::get<0>(info.param).name) + "_" + std::get<1>(info.param).name; } INSTANTIATE_TEST_SUITE_P(FormatDamageMatrix, RenderPipelineTest, testing::Combine( testing::Values( FormatCase {"bgra", FRAME_TYPE_BGRA, false, false}, FormatCase {"rgba", FRAME_TYPE_RGBA, false, false}, FormatCase {"bgr32", FRAME_TYPE_BGR_32, false, false}, FormatCase {"rgb24", FRAME_TYPE_RGB_24, false, false}, FormatCase {"rgba10", FRAME_TYPE_RGBA10, true, true }, FormatCase {"rgba16f", FRAME_TYPE_RGBA16F, true, false} ), testing::Values( DamageCase {"full"}, DamageCase {"moving"}, DamageCase {"overlap"}, DamageCase {"max"}, DamageCase {"invalid"}, DamageCase {"null"}, DamageCase {"zero"} ) ), renderCaseName); using StrideRenderCase = std::tuple; class StrideRenderPipelineTest : public testing::TestWithParam { }; TEST_P(StrideRenderPipelineTest, MatchesReference) { const StrideCase & stride = std::get<0>(GetParam()); const DamageCase & damage = std::get<1>(GetParam()); const FormatCase format = { "bgra", FRAME_TYPE_BGRA, false, false }; Capture capture = produceCapture(format, damage.name, stride.stride); ASSERT_FALSE(capture.data.empty()) << "artifacts: " << capture.directory; const std::string clientLog = readText(capture.log); const std::string expectedLayout = "stride:" + std::to_string(stride.stride) + " pitch:" + std::to_string(stride.stride * 4); EXPECT_NE(clientLog.find(expectedLayout), std::string::npos) << clientLog; compareReference(format, capture); if (!testing::Test::HasFailure()) std::filesystem::remove_all(capture.directory); } std::string strideRenderCaseName( const testing::TestParamInfo & info) { return std::string(std::get<0>(info.param).name) + "_" + std::get<1>(info.param).name; } INSTANTIATE_TEST_SUITE_P(BGRAPaddedStride, StrideRenderPipelineTest, testing::Combine( testing::Values( StrideCase {"stride128", 128}, StrideCase {"stride1280", 1280} ), testing::Values( DamageCase {"full"}, DamageCase {"moving"} ) ), strideRenderCaseName); class GeometryRenderPipelineTest : public testing::TestWithParam { }; void runGeometryCase(const GeometryCase & geometry, bool x11) { const FormatCase format = { "bgra", FRAME_TYPE_BGRA, false, false }; const LaunchCase launch = { "EGL", geometry.width, geometry.height, geometry.rotate, true }; Capture capture = produceCapture(format, "moving", 0, launch); ASSERT_FALSE(capture.data.empty()) << "artifacts: " << capture.directory; if (x11) expectX11Log(readText(capture.log)); compareGeometry(format, geometry, capture); if (!testing::Test::HasFailure()) std::filesystem::remove_all(capture.directory); } TEST_P(GeometryRenderPipelineTest, MatchesReference) { runGeometryCase(GetParam(), false); } std::string geometryRenderCaseName( const testing::TestParamInfo & info) { return info.param.name; } INSTANTIATE_TEST_SUITE_P(ScaledRotated, GeometryRenderPipelineTest, testing::Values( GeometryCase {"scale2", 254, 190, 0, 0, 0, 254, 190}, GeometryCase {"letterbox", 254, 254, 0, 0, 32, 254, 190}, GeometryCase {"rotate90Scale2", 190, 254, 90, 0, 0, 190, 254}, GeometryCase {"rotate270Letterbox", 254, 254, 270, 32, 0, 190, 254} ), geometryRenderCaseName); class OpenGLRenderPipelineTest : public testing::TestWithParam { }; void runOpenGLCase(const FormatCase & format, bool x11) { const LaunchCase launch = { "OpenGL", 254, 254, 90, false }; const std::filesystem::path directory = makeTempDirectory(); ASSERT_FALSE(directory.empty()); const std::filesystem::path log = directory / (std::string(x11 ? "x11-opengl-" : "opengl-") + format.name + ".log"); const int status = runClient( format, "moving", directory / "unused", log, 0, launch); const std::string clientLog = readText(log); EXPECT_EQ(status, 0) << clientLog; if (x11) expectX11Log(clientLog); EXPECT_NE(clientLog.find("Using Renderer: OpenGL"), std::string::npos) << clientLog; EXPECT_NE(clientLog.find("Vendor :"), std::string::npos) << clientLog; EXPECT_NE(clientLog.find("Format:"), std::string::npos) << clientLog; EXPECT_EQ(clientLog.find("configure failed"), std::string::npos) << clientLog; if (!testing::Test::HasFailure()) std::filesystem::remove_all(directory); } TEST_P(OpenGLRenderPipelineTest, Runs) { runOpenGLCase(GetParam(), false); } std::string openGLRenderCaseName( const testing::TestParamInfo & info) { return info.param.name; } INSTANTIATE_TEST_SUITE_P(Formats, OpenGLRenderPipelineTest, testing::Values( FormatCase {"bgra", FRAME_TYPE_BGRA, false, false}, FormatCase {"rgba", FRAME_TYPE_RGBA, false, false}, FormatCase {"bgr32", FRAME_TYPE_BGR_32, false, false}, FormatCase {"rgb24", FRAME_TYPE_RGB_24, false, false}, FormatCase {"rgba10", FRAME_TYPE_RGBA10, true, true }, FormatCase {"rgba16f", FRAME_TYPE_RGBA16F, true, false} ), openGLRenderCaseName); class X11EGLRenderPipelineTest : public testing::TestWithParam { }; TEST_P(X11EGLRenderPipelineTest, MatchesReference) { ASSERT_FALSE(std::getenv("WAYLAND_DISPLAY")); ASSERT_TRUE(std::getenv("DISPLAY")); runGeometryCase(GetParam(), true); } INSTANTIATE_TEST_SUITE_P(X11Geometry, X11EGLRenderPipelineTest, testing::Values( GeometryCase {"letterbox", 254, 254, 0, 0, 32, 254, 190}, GeometryCase {"rotate90Scale2", 190, 254, 90, 0, 0, 190, 254} ), geometryRenderCaseName); TEST(X11OpenGLRenderPipelineTest, Runs) { const FormatCase format = { "bgra", FRAME_TYPE_BGRA, false, false }; ASSERT_FALSE(std::getenv("WAYLAND_DISPLAY")); ASSERT_TRUE(std::getenv("DISPLAY")); runOpenGLCase(format, true); } } // namespace