diff --git a/filament/backend/test/Arguments.cpp b/filament/backend/test/Arguments.cpp index b2d88dc6b5..35ba3f35c1 100644 --- a/filament/backend/test/Arguments.cpp +++ b/filament/backend/test/Arguments.cpp @@ -23,13 +23,17 @@ namespace test { -Backend parseArgumentsForBackend(int argc, char* argv[]) { - Backend backend = Backend::OPENGL; +TestArguments parseArguments(int argc, char* argv[]) { + TestArguments arguments = {}; + arguments.backend = Backend::OPENGL; + // The first colon in OPTSTR turns on silent error reporting. This is important, as the // arguments may also contain gtest parameters we don't know about. - static constexpr const char* OPTSTR = ":a:"; + static constexpr const char* OPTSTR = ":a:kc"; static const struct option OPTIONS[] = { { "api", required_argument, nullptr, 'a' }, + { "headless_only", no_argument, nullptr, 'k' }, + { "ci", no_argument, nullptr, 'c' }, { nullptr, 0, nullptr, 0 } // termination of the option list }; @@ -41,13 +45,13 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) { switch (opt) { case 'a': if (arg == "opengl") { - backend = Backend::OPENGL; + arguments.backend = Backend::OPENGL; } else if (arg == "vulkan") { - backend = Backend::VULKAN; + arguments.backend = Backend::VULKAN; } else if (arg == "metal") { - backend = Backend::METAL; + arguments.backend = Backend::METAL; } else if (arg == "webgpu") { - backend = Backend::WEBGPU; + arguments.backend = Backend::WEBGPU; } else { std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'." << std::endl @@ -55,10 +59,16 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) { << std::endl; } break; + case 'k': + arguments.headlessOnly = true; + break; + case 'c': + arguments.isContinuousIntegration = true; + break; } } - return backend; + return arguments; } } // namespace test diff --git a/filament/backend/test/PlatformRunner.h b/filament/backend/test/PlatformRunner.h index ea3c589570..a5a8b9f183 100644 --- a/filament/backend/test/PlatformRunner.h +++ b/filament/backend/test/PlatformRunner.h @@ -41,6 +41,7 @@ enum class OperatingSystem: uint8_t { LINUX = 2, // Also represents iOS phones. APPLE = 3, + CONTINUOUS_INTEGRATION = 4, // TODO: When tests support windows add it here. }; @@ -73,11 +74,17 @@ void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, */ int runTests(); +struct TestArguments { + Backend backend; + bool headlessOnly = false; + bool isContinuousIntegration = false; +}; + /** * A utility method that can be invoked by test runners to parse arguments. * Looks through the provided command-line arguments and finds any -a arguments. */ -Backend parseArgumentsForBackend(int argc, char* argv[]); +TestArguments parseArguments(int argc, char* argv[]); } // namespace test diff --git a/filament/backend/test/linux_runner.cpp b/filament/backend/test/linux_runner.cpp index 110ebd66a1..65ea6dc61d 100644 --- a/filament/backend/test/linux_runner.cpp +++ b/filament/backend/test/linux_runner.cpp @@ -43,7 +43,10 @@ std::array const VALID_BACKENDS{ }// namespace int main(int argc, char* argv[]) { - auto backend = test::parseArgumentsForBackend(argc, argv); + const auto arguments = test::parseArguments(argc, argv); + const auto backend = arguments.backend; + + // Note that Linux is headless-only. if (!std::any_of(VALID_BACKENDS.begin(), VALID_BACKENDS.end(), [backend](test::Backend validBackend) { return backend == validBackend; })) { @@ -51,6 +54,8 @@ int main(int argc, char* argv[]) { return 1; } - test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv); + const auto operatingSystem = arguments.isContinuousIntegration ? + test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::LINUX; + test::initTests(backend, operatingSystem, false, argc, argv); return test::runTests(); } diff --git a/filament/backend/test/mac_runner.mm b/filament/backend/test/mac_runner.mm index 6680629283..46bd836832 100644 --- a/filament/backend/test/mac_runner.mm +++ b/filament/backend/test/mac_runner.mm @@ -32,29 +32,34 @@ test::NativeView getNativeView() { @interface AppDelegate : NSObject @property test::Backend backend; +@property bool headlessOnly; @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - NSView* view = [self createView]; - - if (self.backend == test::Backend::OPENGL) { - nativeView.ptr = (void*) view; + if (self.headlessOnly) { + nativeView.ptr = nullptr; + nativeView.width = test::WINDOW_WIDTH; + nativeView.height = test::WINDOW_HEIGHT; + } else { + NSView* view = [self createView]; + switch (self.backend) { + case test::Backend::OPENGL: + nativeView.ptr = (void*) view; + break; + case test::Backend::METAL: + case test::Backend::VULKAN: + case test::Backend::WEBGPU: + case test::Backend::NOOP: + nativeView.ptr = (void*) view.layer; + break; + } + CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize; + nativeView.width = static_cast(drawableSize.width); + nativeView.height = static_cast(drawableSize.height); } - if (self.backend == test::Backend::METAL) { - nativeView.ptr = (void*) view.layer; - } - if (self.backend == test::Backend::VULKAN) { - nativeView.ptr = (void*) view.layer; - } - if (self.backend == test::Backend::WEBGPU) { - nativeView.ptr = (void*) view.layer; - } - CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize; - nativeView.width = static_cast(drawableSize.width); - nativeView.height = static_cast(drawableSize.height); exit(test::runTests()); } @@ -100,11 +105,25 @@ test::NativeView getNativeView() { @end int main(int argc, char* argv[]) { - auto backend = test::parseArgumentsForBackend(argc, argv); - test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv); - AppDelegate* delegate = [AppDelegate new]; - delegate.backend = backend; + const auto arguments = test::parseArguments(argc, argv); + const auto operatingSystem = arguments.isContinuousIntegration ? + test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::APPLE; + test::initTests(arguments.backend, operatingSystem, false, argc, argv); + NSApplication* app = [NSApplication sharedApplication]; + AppDelegate* delegate = [AppDelegate new]; + delegate.backend = arguments.backend; + delegate.headlessOnly = arguments.headlessOnly; [app setDelegate:delegate]; + + if (arguments.headlessOnly) { + // In headless mode, we don't want to start the NSApplication event loop. + // Instead, we can manually "finish" launching the app, which will trigger the tests to run. + [app finishLaunching]; + [delegate applicationDidFinishLaunching:nil]; + // The line above calls exit(), so we should not reach here. + return 0; + } + [app run]; } diff --git a/filament/backend/test/test_Blit.cpp b/filament/backend/test/test_Blit.cpp index 016ac86d83..3e0327028b 100644 --- a/filament/backend/test/test_Blit.cpp +++ b/filament/backend/test/test_Blit.cpp @@ -158,7 +158,7 @@ TEST_F(BlitTest, ColorMagnify) { constexpr int kNumLevels = 3; // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = mCleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); // Create a source texture. @@ -222,7 +222,7 @@ TEST_F(BlitTest, ColorMinify) { constexpr int kNumLevels = 3; // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = mCleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); // Create a source texture. @@ -371,7 +371,7 @@ TEST_F(BlitTest, Blit2DTextureArray) { constexpr int kDstTexLayer = 0; // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = mCleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); // Create a source texture. @@ -437,7 +437,7 @@ TEST_F(BlitTest, BlitRegion) { constexpr int kDstLevel = 0; // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = mCleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); // Create a source texture. diff --git a/filament/backend/test/test_Callbacks.cpp b/filament/backend/test/test_Callbacks.cpp index d44b2dfbda..7ffcf78f86 100644 --- a/filament/backend/test/test_Callbacks.cpp +++ b/filament/backend/test/test_Callbacks.cpp @@ -103,7 +103,7 @@ TEST_F(BackendTest, FrameCompletedCallback) { Cleanup cleanup(api); // Create a SwapChain. - auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = cleanup.add(createSwapChain()); int callbackCountA = 0; api.setFrameCompletedCallback(swapChain, nullptr, diff --git a/filament/backend/test/test_ReadPixels.cpp b/filament/backend/test/test_ReadPixels.cpp index 40eccf52c3..1825af3635 100644 --- a/filament/backend/test/test_ReadPixels.cpp +++ b/filament/backend/test/test_ReadPixels.cpp @@ -385,8 +385,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) { Cleanup cleanup(api); // Create a platform-specific SwapChain and make it current. - auto swapChain = cleanup.add( - api.createSwapChainHeadless(renderTargetSize, renderTargetSize, 0)); + auto swapChain = cleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{ diff --git a/filament/backend/test/test_Scissor.cpp b/filament/backend/test/test_Scissor.cpp index 1a3212020c..6597b2d396 100644 --- a/filament/backend/test/test_Scissor.cpp +++ b/filament/backend/test/test_Scissor.cpp @@ -70,7 +70,7 @@ TEST_F(BackendTest, ScissorViewportRegion) { // executeCommands(). { // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = cleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); Shader shader = SharedShaders::makeShader(api, cleanup, @@ -149,7 +149,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) { // executeCommands(). { // Create a SwapChain and make it current. We don't really use it so the res doesn't matter. - auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0)); + auto swapChain = cleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{ diff --git a/filament/backend/test/test_Template.cpp b/filament/backend/test/test_Template.cpp index 4787577295..47eeac211a 100644 --- a/filament/backend/test/test_Template.cpp +++ b/filament/backend/test/test_Template.cpp @@ -33,8 +33,7 @@ TEST_F(BackendTest, TestTemplate) { auto& api = getDriverApi(); Cleanup cleanup(api); - auto swapChain = - cleanup.add(api.createSwapChainHeadless(kRenderTargetSize, kRenderTargetSize, 0)); + auto swapChain = cleanup.add(createSwapChain()); api.makeCurrent(swapChain, swapChain); RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());