mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 17:59:08 +02:00
iOS only runs app bundles, so with HEADLESS=ON the iOS build makes PPSSPPHeadless an app (org.ppsspp.headless) that starts UIKit and runs the headless main on a thread with the arguments devicectl launched it with. Vulkan only, and the IR interpreter since there's no JIT. frametests.py --ios installs it, copies over the dumps the device lacks, and compares each render with the host's Vulkan one like --adb does. Each devicectl call costs a second or two, so the new --screenshot-dir lets one launch render a batch of dumps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
3.5 KiB
Plaintext
100 lines
3.5 KiB
Plaintext
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// 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, version 2.0 or later versions.
|
|
|
|
// 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
// PPSSPPHeadless as an iOS app. iOS only runs app bundles, and kills one that doesn't bring up
|
|
// UIKit, so this starts UIKit and runs the headless main on a thread of its own, with the
|
|
// arguments devicectl launched it with:
|
|
//
|
|
// xcrun devicectl device process launch --console --device <udid> org.ppsspp.headless <args>
|
|
//
|
|
// Relative paths are resolved against the app's Documents directory, which
|
|
// `devicectl device copy to/from --domain-type appDataContainer` reaches. See frametests.py --ios.
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
int HeadlessMain(int argc, const char *argv[]);
|
|
|
|
// DarwinFileSystemServices presents its pickers on the app's view controller. Headless has none.
|
|
UIViewController *sharedViewController = nil;
|
|
|
|
// iOS 27 stops an app that doesn't adopt the scene lifecycle, even one that draws nothing.
|
|
@interface HeadlessSceneDelegate : UIResponder <UIWindowSceneDelegate>
|
|
@property (strong, nonatomic) UIWindow *window;
|
|
@end
|
|
|
|
@implementation HeadlessSceneDelegate
|
|
@end
|
|
|
|
@interface HeadlessAppDelegate : UIResponder <UIApplicationDelegate>
|
|
@end
|
|
|
|
@implementation HeadlessAppDelegate
|
|
|
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
|
|
chdir(documents.UTF8String);
|
|
|
|
NSArray<NSString *> *arguments = [NSProcessInfo processInfo].arguments;
|
|
std::vector<std::string> args;
|
|
for (NSString *arg in arguments) {
|
|
// --ios-clear=DIR deletes a folder under Documents first, since devicectl can't delete
|
|
// anything (its --remove-existing-content empties all of Documents).
|
|
if ([arg hasPrefix:@"--ios-clear="]) {
|
|
NSString *dir = [arg substringFromIndex:12];
|
|
if (dir.length == 0 || [dir hasPrefix:@"/"] || [dir containsString:@".."]) {
|
|
printf("--ios-clear needs a folder inside Documents: %s\n", dir.UTF8String);
|
|
exit(1);
|
|
}
|
|
[[NSFileManager defaultManager] removeItemAtPath:[documents stringByAppendingPathComponent:dir] error:nil];
|
|
continue;
|
|
}
|
|
args.push_back(arg.UTF8String);
|
|
}
|
|
|
|
std::thread([args]() {
|
|
std::vector<const char *> argv;
|
|
for (const std::string &arg : args) {
|
|
argv.push_back(arg.c_str());
|
|
}
|
|
argv.push_back(nullptr);
|
|
int ret = HeadlessMain((int)args.size(), argv.data());
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(ret);
|
|
}).detach();
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// devicectl --console only forwards stdout, from a pipe, which would otherwise be block buffered.
|
|
dup2(STDOUT_FILENO, STDERR_FILENO);
|
|
setvbuf(stdout, nullptr, _IOLBF, 0);
|
|
setvbuf(stderr, nullptr, _IONBF, 0);
|
|
@autoreleasepool {
|
|
return UIApplicationMain(argc, argv, nil, NSStringFromClass([HeadlessAppDelegate class]));
|
|
}
|
|
}
|