mirror of
https://github.com/utmapp/UTM.git
synced 2026-10-12 02:24:01 +00:00
preferences: support changing renderer backend
This commit is contained in:
@@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) BOOL hasRemoteProcess;
|
||||
@property (nonatomic, readonly) NSURL *libraryURL;
|
||||
@property (nonatomic) NSArray<NSString *> *argv;
|
||||
@property (nonatomic, nullable) NSDictionary<NSString *, NSString *> *environment;
|
||||
@property (nonatomic) dispatch_semaphore_t done;
|
||||
@property (nonatomic) NSInteger status;
|
||||
@property (nonatomic) NSInteger fatal;
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
}
|
||||
NSFileHandle *standardOutput = self.logging.standardOutput.fileHandleForWriting;
|
||||
NSFileHandle *standardError = self.logging.standardError.fileHandleForWriting;
|
||||
[_connection.remoteObjectProxy setEnvironment:self.environment];
|
||||
[[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
|
||||
if (error.domain == NSCocoaErrorDomain && error.code == NSXPCConnectionInvalid) {
|
||||
completion(YES, nil); // inhibit this error since we always see it on quit
|
||||
|
||||
@@ -16,11 +16,20 @@
|
||||
|
||||
#import "UTMQemu.h"
|
||||
|
||||
/// Specify the backend renderer for this VM
|
||||
typedef NS_ENUM(NSInteger, UTMQEMURendererBackend) {
|
||||
kQEMURendererBackendDefault = 0,
|
||||
kQEMURendererBackendAngleGL = 1,
|
||||
kQEMURendererBackendAngleMetal = 2,
|
||||
kQEMURendererBackendMax = 3,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UTMQemuSystem : UTMQemu
|
||||
|
||||
@property (nonatomic, nullable, copy) NSArray<NSURL *> *resources;
|
||||
@property (nonatomic) UTMQEMURendererBackend rendererBackend;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithArguments:(NSArray<NSString *> *)arguments NS_UNAVAILABLE;
|
||||
|
||||
@@ -32,19 +32,32 @@
|
||||
static void *start_qemu(void *args) {
|
||||
UTMQemuSystem *self = (__bridge_transfer UTMQemuSystem *)args;
|
||||
NSArray<NSString *> *qemuArgv = self.argv;
|
||||
NSMutableArray<NSString *> *environment = [NSMutableArray arrayWithCapacity:self.environment.count];
|
||||
|
||||
NSCAssert(self->_qemu_init != NULL, @"Started thread with invalid function.");
|
||||
NSCAssert(self->_qemu_main_loop != NULL, @"Started thread with invalid function.");
|
||||
NSCAssert(self->_qemu_cleanup != NULL, @"Started thread with invalid function.");
|
||||
NSCAssert(qemuArgv, @"Started thread with invalid argv.");
|
||||
|
||||
/* set up environment variables */
|
||||
[self.environment enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||
NSString *combined = [NSString stringWithFormat:@"%@=%@", key, value];
|
||||
[environment addObject:combined];
|
||||
setenv(key.UTF8String, value.UTF8String, 1);
|
||||
}];
|
||||
NSUInteger envc = environment.count;
|
||||
const char *envp[envc + 1];
|
||||
for (NSUInteger i = 0; i < envc; i++) {
|
||||
envp[i] = environment[i].UTF8String;
|
||||
}
|
||||
envp[envc] = NULL;
|
||||
|
||||
int argc = (int)qemuArgv.count + 1;
|
||||
const char *argv[argc];
|
||||
argv[0] = "qemu-system";
|
||||
for (int i = 0; i < qemuArgv.count; i++) {
|
||||
argv[i+1] = [qemuArgv[i] UTF8String];
|
||||
}
|
||||
const char *envp[] = { NULL };
|
||||
self->_qemu_init(argc, argv, envp);
|
||||
self->_qemu_main_loop();
|
||||
self->_qemu_cleanup();
|
||||
@@ -53,6 +66,26 @@ static void *start_qemu(void *args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- (void)setRendererBackend:(UTMQEMURendererBackend)rendererBackend {
|
||||
_rendererBackend = rendererBackend;
|
||||
switch (rendererBackend) {
|
||||
case kQEMURendererBackendAngleGL:
|
||||
self.environment = @{@"ANGLE_DEFAULT_PLATFORM": @"gl"};
|
||||
break;
|
||||
case kQEMURendererBackendAngleMetal:
|
||||
self.environment = @{@"ANGLE_DEFAULT_PLATFORM": @"metal"};
|
||||
break;
|
||||
case kQEMURendererBackendDefault:
|
||||
#if TARGET_OS_IPHONE
|
||||
// on iOS, use Metal as the default because it is more stable
|
||||
self.environment = @{@"ANGLE_DEFAULT_PLATFORM": @"metal"};
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithArguments:(NSArray<NSString *> *)arguments architecture:(nonnull NSString *)architecture {
|
||||
self = [super initWithArguments:arguments];
|
||||
if (self) {
|
||||
|
||||
@@ -45,6 +45,7 @@ NSString *const kSuspendSnapshotName = @"suspend";
|
||||
@property (nonatomic, nullable) dispatch_semaphore_t qemuDidConnectEvent;
|
||||
@property (nonatomic) BOOL changeCursorRequestInProgress;
|
||||
@property (nonatomic, nullable) NSString *lastErrorLine;
|
||||
@property (nonatomic, readonly) UTMQEMURendererBackend rendererBackend;
|
||||
|
||||
@end
|
||||
|
||||
@@ -68,6 +69,15 @@ NSString *const kSuspendSnapshotName = @"suspend";
|
||||
_isGuestToolsInstallRequested = isGuestToolsInstallRequested;
|
||||
}
|
||||
|
||||
- (UTMQEMURendererBackend)rendererBackend {
|
||||
NSInteger value = [NSUserDefaults.standardUserDefaults integerForKey:@"QEMURendererBackend"];
|
||||
if (value >= 0 && value < kQEMURendererBackendMax) {
|
||||
return (UTMQEMURendererBackend)value;
|
||||
} else {
|
||||
return kQEMURendererBackendDefault;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithConfiguration:(UTMConfigurationWrapper *)configuration packageURL:(NSURL *)packageURL {
|
||||
self = [super initWithConfiguration:configuration packageURL:packageURL];
|
||||
if (self) {
|
||||
@@ -219,6 +229,7 @@ NSString *const kSuspendSnapshotName = @"suspend";
|
||||
__weak typeof(self) weakSelf = self;
|
||||
__block NSError *qemuStartError = nil;
|
||||
dispatch_semaphore_t spiceConnectOrErrorEvent = dispatch_semaphore_create(0);
|
||||
self.system.rendererBackend = self.rendererBackend;
|
||||
[self.system startWithCompletion:^(BOOL success, NSString *msg){
|
||||
typeof(self) _self = weakSelf;
|
||||
if (!_self) {
|
||||
|
||||
@@ -94,6 +94,34 @@
|
||||
<key>DefaultValue</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Graphics</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSMultiValueSpecifier</string>
|
||||
<key>Title</key>
|
||||
<string>Renderer Backend</string>
|
||||
<key>Key</key>
|
||||
<string>QEMURendererBackend</string>
|
||||
<key>DefaultValue</key>
|
||||
<integer>0</integer>
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
<string>ANGLE (OpenGL)</string>
|
||||
<string>ANGLE (Metal)</string>
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
|
||||
@@ -28,6 +28,7 @@ struct SettingsView: View {
|
||||
@AppStorage("IsCapsLockKey") var isCapsLockKey = false
|
||||
@AppStorage("NoSaveScreenshot") var isNoSaveScreenshot = false
|
||||
@AppStorage("InvertScroll") var isInvertScroll = false
|
||||
@AppStorage("QEMURendererBackend") var qemuRendererBackend: UTMQEMURendererBackend = .qemuRendererBackendDefault
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@@ -56,6 +57,11 @@ struct SettingsView: View {
|
||||
Toggle(isOn: $isNoSaveScreenshot) {
|
||||
Text("Do not save VM screenshot to disk")
|
||||
}.help("If enabled, any existing screenshot will be deleted the next time the VM is started.")
|
||||
Picker("QEMU Renderer Backend", selection: $qemuRendererBackend) {
|
||||
Text("Default").tag(UTMQEMURendererBackend.qemuRendererBackendDefault)
|
||||
Text("ANGLE (OpenGL)").tag(UTMQEMURendererBackend.qemuRendererBackendAngleGL)
|
||||
Text("ANGLE (Metal)").tag(UTMQEMURendererBackend.qemuRendererBackendAngleMetal)
|
||||
}.help("By default, the best renderer for this device will be used. You can override this with to always use a specific renderer. This only applies to QEMU VMs with GPU accelerated graphics.")
|
||||
}
|
||||
Section(header: Text("Input")) {
|
||||
Toggle(isOn: $isCtrlRightClick, label: {
|
||||
@@ -93,6 +99,7 @@ extension UserDefaults {
|
||||
@objc dynamic var IsCapsLockKey: Bool { false }
|
||||
@objc dynamic var NoSaveScreenshot: Bool { false }
|
||||
@objc dynamic var InvertScroll: Bool { false }
|
||||
@objc dynamic var QEMURendererBackend: Int { 0 }
|
||||
}
|
||||
|
||||
@available(macOS 11, *)
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
@implementation QEMUHelper
|
||||
|
||||
@synthesize environment;
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.urls = [NSMutableArray array];
|
||||
@@ -112,7 +114,9 @@
|
||||
task.arguments = newArgv;
|
||||
task.standardOutput = standardOutput;
|
||||
task.standardError = standardError;
|
||||
//task.environment = @{@"DYLD_LIBRARY_PATH": libraryPath.path};
|
||||
if (self.environment) {
|
||||
task.environment = self.environment;
|
||||
}
|
||||
task.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
task.terminationHandler = ^(NSTask *task) {
|
||||
BOOL normalExit = task.terminationReason == NSTaskTerminationReasonExit && task.terminationStatus == 0;
|
||||
|
||||
@@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service.
|
||||
@protocol QEMUHelperProtocol
|
||||
|
||||
@property (nonatomic, nullable) NSDictionary<NSString *, NSString *> *environment;
|
||||
|
||||
- (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
|
||||
- (void)stopAccessingPath:(nullable NSString *)path;
|
||||
- (void)startQemu:(NSString *)binName standardOutput:(NSFileHandle *)standardOutput standardError:(NSFileHandle *)standardError libraryBookmark:(NSData *)libBookmark argv:(NSArray<NSString *> *)argv onExit:(void(^)(BOOL,NSString *))onExit;
|
||||
|
||||
@@ -68,8 +68,7 @@ static int loadQemu(const char *dylibPath, qemu_main_t *funcs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __attribute__((noreturn)) runQemu(qemu_main_t *funcs, int argc, const char **argv) {
|
||||
const char *envp[] = { NULL };
|
||||
static void __attribute__((noreturn)) runQemu(qemu_main_t *funcs, int argc, const char **argv, const char **envp) {
|
||||
if (funcs->qemu_init) {
|
||||
funcs->qemu_init(argc, argv, envp);
|
||||
}
|
||||
@@ -85,7 +84,7 @@ static void __attribute__((noreturn)) runQemu(qemu_main_t *funcs, int argc, cons
|
||||
exit(0);
|
||||
}
|
||||
|
||||
pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, int newStdout, int newStderr) {
|
||||
pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, const char **envp, int newStdout, int newStderr) {
|
||||
qemu_main_t funcs = {};
|
||||
int res = loadQemu(dylibPath, &funcs);
|
||||
if (res < 0) {
|
||||
@@ -103,16 +102,16 @@ pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, int newS
|
||||
// set thread QoS
|
||||
pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
|
||||
// launch qemu
|
||||
runQemu(&funcs, argc, argv);
|
||||
runQemu(&funcs, argc, argv, envp);
|
||||
}
|
||||
|
||||
int startQemuProcess(const char *dylibPath, int argc, const char **argv) {
|
||||
int startQemuProcess(const char *dylibPath, int argc, const char **argv, const char **envp) {
|
||||
qemu_main_t funcs = {};
|
||||
int res = loadQemu(dylibPath, &funcs);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
// launch qemu
|
||||
runQemu(&funcs, argc, argv);
|
||||
runQemu(&funcs, argc, argv, envp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, int newStdout, int newStderr);
|
||||
int startQemuProcess(const char *dylibPath, int argc, const char **argv);
|
||||
pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, const char **envp, int newStdout, int newStderr);
|
||||
int startQemuProcess(const char *dylibPath, int argc, const char **argv, const char **envp);
|
||||
|
||||
#endif /* Bootstrap_h */
|
||||
|
||||
+3
-1
@@ -17,10 +17,12 @@
|
||||
#include <stdio.h>
|
||||
#include "Bootstrap.h"
|
||||
|
||||
extern const char **environ;
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: QEMULauncher dylibPath qemuArguments...\n");
|
||||
return 1;
|
||||
}
|
||||
return startQemuProcess(argv[1], argc - 1, &argv[1]);
|
||||
return startQemuProcess(argv[1], argc - 1, &argv[1], environ);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user