Merge pull request #7800 from utmapp/dev/neptune

project: add DirectX support for Windows guests
This commit is contained in:
osy
2026-07-31 22:41:16 -07:00
committed by GitHub
32 changed files with 1137 additions and 5037 deletions
+3
View File
@@ -147,6 +147,9 @@ jobs:
if: steps.cache-sysroot-universal.outputs.cache-matched-key == ''
run: |
./scripts/pack_dependencies.sh . macos arm64 x86_64
- name: Install D3DMetal
if: steps.cache-sysroot-universal.outputs.cache-matched-key == '' || github.event_name == 'release' || github.event.inputs.test_release == 'true' || github.event.inputs.rebuild_sysroot == 'true'
run: ./scripts/install_d3dmetal.sh sysroot-macOS-arm64_x86_64 "${{ secrets.D3DM_DIST_DMG_URL || vars.D3DM_DIST_DMG_URL }}"
- name: Compress Sysroot
if: steps.cache-sysroot-universal.outputs.cache-matched-key == '' || github.event_name == 'release' || github.event.inputs.test_release == 'true' || github.event.inputs.rebuild_sysroot == 'true'
run: tar -acf sysroot.tgz sysroot-macOS-arm64_x86_64
+1 -1
View File
@@ -43,7 +43,7 @@ must apply unprompted — they override default behavior and are easy to violate
`Assisted-by: AGENT:MODEL` trailer (e.g. `Assisted-by: Claude:claude-opus-4-8`) —
add it explicitly **even if your agent doesn't normally inject attribution
trailers**; the absence of a default trailer is not an excuse to omit it. Never
add `Co-authored-by` (strip any a tool adds). Commit titles are
add `Co-authored-by` or session links (strip any a tool adds). Commit titles are
`component: short description` explaining *why*.
- **Scope:** one feature/fix per PR; don't touch, refactor, or reformat unrelated
files; route new logging through `UTMLogging`/`logging` at `debug` level.
+3
View File
@@ -39,3 +39,6 @@ LAUNCHER_CODE_SIGN_ENTITLEMENTS = $(LAUNCHER_CODE_SIGN_ENTITLEMENTS_$(DEVELOPER_
CLI_CODE_SIGN_ENTITLEMENTS_YES = utmctl/utmctl.entitlements
CLI_CODE_SIGN_ENTITLEMENTS_NO = utmctl/utmctl-unsigned.entitlements
CLI_CODE_SIGN_ENTITLEMENTS = $(CLI_CODE_SIGN_ENTITLEMENTS_$(DEVELOPER_ACCOUNT_VM_ACCESS:default=NO))
RENDERER_CODE_SIGN_ENTITLEMENTS_YES = QEMURenderServer/QEMURenderServer.entitlements
RENDERER_CODE_SIGN_ENTITLEMENTS_NO = QEMURenderServer/QEMURenderServer-unsigned.entitlements
RENDERER_CODE_SIGN_ENTITLEMENTS = $(RENDERER_CODE_SIGN_ENTITLEMENTS_$(DEVELOPER_ACCOUNT_VM_ACCESS:default=NO))
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
@@ -27,13 +27,21 @@ import Virtualization // for getting network interfaces
private func f(_ string: String = "") -> QEMUArgumentFragment {
QEMUArgumentFragment(final: string)
}
/// Shared group ID for main process and helper
var appGroupIdentifier: String? {
let appGroup = Bundle.main.infoDictionary?["AppGroupIdentifier"] as? String
if let appGroup, !appGroup.hasPrefix("invalid.") {
return appGroup
}
return nil
}
/// Shared between helper and main process to store Unix sockets
var socketURL: URL {
#if os(iOS) || os(visionOS)
return FileManager.default.temporaryDirectory
#else
let appGroup = Bundle.main.infoDictionary?["AppGroupIdentifier"] as? String
let helper = Bundle.main.infoDictionary?["HelperIdentifier"] as? String
// default to unsigned sandbox path
var parentURL: URL = FileManager.default.homeDirectoryForCurrentUser
@@ -42,7 +50,7 @@ import Virtualization // for getting network interfaces
parentURL.appendPathComponent(helper ?? "com.utmapp.QEMUHelper")
parentURL.appendPathComponent("Data")
parentURL.appendPathComponent("tmp")
if let appGroup = appGroup, !appGroup.hasPrefix("invalid.") {
if let appGroup = appGroupIdentifier {
if let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) {
return containerURL
}
@@ -276,10 +284,25 @@ import Virtualization // for getting network interfaces
"edid=on"
}
if isDisplayGLSupported(display) && isVulkanSupported {
#if os(macOS)
"hostmem=8G"
#else
"hostmem=256M"
#endif
"blob=true"
"venus=true"
}
if isDisplayGLSupported(display) && isNeptuneSupported {
if !isVulkanSupported {
#if os(macOS)
"hostmem=8G"
#else
"hostmem=256M"
#endif
"blob=true"
}
"neptune=true"
}
f()
}
}
@@ -322,12 +345,27 @@ import Virtualization // for getting network interfaces
return UTMQEMUVulkanDriver(rawValue: rawValue) ?? .qemuVulkanDriverDefault
}
private var directXDriver: UTMQEMUDirectXDriver {
let rawValue = UserDefaults.standard.integer(forKey: "QEMUDirectXDriver")
return UTMQEMUDirectXDriver(rawValue: rawValue) ?? .qemuDirectXDriverDefault
}
private var isVulkanSupported: Bool {
isGLSupported &&
(rendererBackend == .qemuRendererBackendAngleMetal || rendererBackend == .qemuRendererBackendDefault) &&
vulkanDriver != .qemuVulkanDriverDisabled
}
private var isNeptuneSupported: Bool {
#if os(macOS)
isGLSupported &&
(rendererBackend == .qemuRendererBackendAngleMetal || rendererBackend == .qemuRendererBackendDefault) &&
directXDriver != .qemuDirectXDriverDisabled
#else
false
#endif
}
private var isSparc: Bool {
system.architecture == .sparc || system.architecture == .sparc64
}
+2 -2
View File
@@ -20,8 +20,8 @@ import Foundation
class UTMDownloadSupportToolsTask: UTMDownloadTask {
private let vm: any UTMSpiceVirtualMachine
private static let supportToolsDownloadUrl = URL(string: "https://getutm.app/downloads/utm-guest-tools-latest.iso")!
private static let supportToolsDownloadUrl = URL(string: "https://getutm.app/downloads/utm-guest-tools-3d-latest.iso")!
private var toolsUrl: URL {
fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("GuestSupportTools")
}
+2
View File
@@ -144,5 +144,7 @@
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>AppGroupIdentifier</key>
<string>group.$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM</string>
</dict>
</plist>
+6 -2
View File
@@ -2,9 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM</string>
</array>
</dict>
</plist>
+28
View File
@@ -211,12 +211,26 @@ struct DisplaySettingsView: View {
@AppStorage("NoSaveScreenshot") var isNoSaveScreenshot = false
@AppStorage("QEMURendererBackend") var qemuRendererBackend: UTMQEMURendererBackend = .qemuRendererBackendDefault
@AppStorage("QEMUVulkanDriver") var qemuVulkanDriver: UTMQEMUVulkanDriver = .qemuVulkanDriverDefault
@AppStorage("QEMUDirectXDriver") var qemuDirectXDriver: UTMQEMUDirectXDriver = .qemuDirectXDriverDefault
@AppStorage("QEMURendererFPSLimit") var qemuRendererFpsLimit: Int = 0
private var isVulkanSupported: Bool {
qemuRendererBackend == .qemuRendererBackendDefault || qemuRendererBackend == .qemuRendererBackendAngleMetal
}
private static var hasD3DMetal: Bool = {
let d3dMetalFramework = Bundle.main.bundleURL.appendingPathComponent("Contents/Frameworks/D3DMetal.framework")
return FileManager.default.fileExists(atPath: d3dMetalFramework.path)
}()
private var isD3DMetalAvailable: Bool {
if qemuDirectXDriver == .qemuDirectXDriverD3DMetal {
return true // always show option if already selected
} else {
return Self.hasD3DMetal
}
}
var body: some View {
Form {
Section(header: Text("Display")) {
@@ -253,6 +267,20 @@ struct DisplaySettingsView: View {
qemuVulkanDriver = .qemuVulkanDriverDefault
}
}
Picker("DirectX Driver", selection: $qemuDirectXDriver) {
Text("Default").tag(UTMQEMUDirectXDriver.qemuDirectXDriverDefault)
Text("Disabled").tag(UTMQEMUDirectXDriver.qemuDirectXDriverDisabled)
Text("DXMT").tag(UTMQEMUDirectXDriver.qemuDirectXDriverDXMT)
if isD3DMetalAvailable {
Text("D3DMetal").tag(UTMQEMUDirectXDriver.qemuDirectXDriverD3DMetal)
}
}.help("Select the DirectX driver to use for host passthrough rendering. DirectX requires guest drivers to be installed.")
.disabled(!isVulkanSupported)
.onChange(of: qemuRendererBackend) { _ in
if !isVulkanSupported {
qemuDirectXDriver = .qemuDirectXDriverDefault
}
}
if !isVulkanSupported {
Text("The selected renderer backend does not support Vulkan.")
}
+2
View File
@@ -128,7 +128,9 @@ static const int64_t kProcessTerminateTimeoutSeconds = 1;
task.standardOutput = standardOutput;
task.standardError = standardError;
NSMutableDictionary<NSString *, NSString *> *environment = [NSMutableDictionary dictionary];
NSURL *renderServerURL = [[[NSBundle mainBundle] URLForAuxiliaryExecutable:@"QEMURenderServer.app"] URLByAppendingPathComponent:@"Contents/MacOS/QEMURenderServer"];
environment[@"TMPDIR"] = NSFileManager.defaultManager.temporaryDirectory.path;
environment[@"RENDER_SERVER_EXEC_PATH"] = renderServerURL.path;
if (self.environment) {
[environment addEntriesFromDictionary:self.environment];
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
+2
View File
@@ -26,7 +26,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) NSDictionary<NSURL *, NSData *> *remoteBookmarks;
@property (nonatomic) UTMQEMURendererBackend rendererBackend;
@property (nonatomic) UTMQEMUVulkanDriver vulkanDriver;
@property (nonatomic) UTMQEMUDirectXDriver directXDriver;
@property (nonatomic) NSURL *shmemDirectoryURL;
@property (nonatomic, nullable) NSString *appSandboxGroupId;
@property (nonatomic, weak) id<QEMULauncherDelegate> launcherDelegate;
@property (nonatomic, nullable) QEMULogging *logging;
@property (nonatomic) BOOL hasDebugLog;
+81 -2
View File
@@ -58,7 +58,6 @@ static int startQemu(UTMProcess *process, int argc, const char *argv[], const ch
}
- (void)setVulkanDriver:(UTMQEMUVulkanDriver)vulkanDriver {
_vulkanDriver = vulkanDriver;
NSURL *vulkanIcds = [[NSBundle.mainBundle URLForResource:@"vulkan" withExtension:nil] URLByAppendingPathComponent:@"icd.d" isDirectory:YES];
NSURL *driver;
switch (vulkanDriver) {
@@ -77,12 +76,86 @@ static int startQemu(UTMProcess *process, int argc, const char *argv[], const ch
if (driver) {
self.mutableEnvironment[@"VK_DRIVER_FILES"] = driver.path;
self.resources = [self.resources arrayByAddingObject:driver];
_vulkanDriver = vulkanDriver;
} else {
[self.mutableEnvironment removeObjectForKey:@"VK_DRIVER_FILES"];
_vulkanDriver = kQEMUVulkanDriverDisabled;
}
}
- (void)setDirectXDriver:(UTMQEMUDirectXDriver)directXDriver {
NSString *backend;
NSString *frameworkName;
NSURL *library;
switch (directXDriver) {
case kQEMUDirectXDriverDefault:
case kQEMUDirectXDriverDXMT:
backend = @"dxmt";
frameworkName = @"dxmt-native";
break;
case kQEMUDirectXDriverD3DMetal:
backend = @"d3dmetal";
frameworkName = @"d3dmetal-native";
break;
case kQEMUDirectXDriverDisabled:
default:
break;
}
if (frameworkName) {
NSURL *bundleURL = NSBundle.mainBundle.bundleURL;
#if TARGET_OS_OSX
NSURL *contentsURL = [bundleURL URLByAppendingPathComponent:@"Contents" isDirectory:YES];
NSString *versionPath = @"Versions/A/";
#else
NSURL *contentsURL = bundleURL;
NSString *versionPath = @"";
#endif
NSURL *frameworksURL = [contentsURL URLByAppendingPathComponent:@"Frameworks" isDirectory:YES];
library = [frameworksURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.framework/%@%@", frameworkName, versionPath, frameworkName] isDirectory:NO];
if (![NSFileManager.defaultManager fileExistsAtPath:library.path]) {
UTMLog(@"DirectX driver '%@' is not available in this build", backend);
backend = nil;
library = nil;
} else if (directXDriver == kQEMUDirectXDriverD3DMetal) {
NSString *d3dMetal = @"D3DMetal";
NSURL *d3dMetalURL = [frameworksURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.framework/%@%@", d3dMetal, versionPath, d3dMetal] isDirectory:NO];
self.mutableEnvironment[@"D3DMETAL_FRAMEWORK_PATH"] = d3dMetalURL.path;
self.resources = [self.resources arrayByAddingObject:d3dMetalURL];
}
}
if (backend && library) {
self.mutableEnvironment[@"NPT_D3D11_LIBRARY_PATH"] = library.path;
self.mutableEnvironment[@"NPT_D3D12_LIBRARY_PATH"] = library.path;
self.mutableEnvironment[@"NPT_DXGI_LIBRARY_PATH"] = library.path;
self.mutableEnvironment[@"NPT_BACKEND"] = backend;
self.resources = [self.resources arrayByAddingObject:library];
_directXDriver = directXDriver;
} else {
[self.mutableEnvironment removeObjectForKey:@"NPT_D3D11_LIBRARY_PATH"];
[self.mutableEnvironment removeObjectForKey:@"NPT_D3D12_LIBRARY_PATH"];
[self.mutableEnvironment removeObjectForKey:@"NPT_DXGI_LIBRARY_PATH"];
[self.mutableEnvironment removeObjectForKey:@"NPT_BACKEND"];
[self.mutableEnvironment removeObjectForKey:@"D3DMETAL_FRAMEWORK_PATH"];
_directXDriver = kQEMUDirectXDriverDisabled;
}
}
- (void)setShmemDirectoryURL:(NSURL *)shmemDirectoryURL {
_shmemDirectoryURL = shmemDirectoryURL;
self.mutableEnvironment[@"XDG_RUNTIME_DIR"] = shmemDirectoryURL.path;
if (shmemDirectoryURL) {
self.mutableEnvironment[@"XDG_RUNTIME_DIR"] = shmemDirectoryURL.path;
} else {
[self.mutableEnvironment removeObjectForKey:@"XDG_RUNTIME_DIR"];
}
}
- (void)setAppSandboxGroupId:(NSString *)appSandboxGroupId {
_appSandboxGroupId = appSandboxGroupId;
if (appSandboxGroupId) {
self.mutableEnvironment[@"APP_SANDBOX_GROUP_ID"] = appSandboxGroupId;
} else {
[self.mutableEnvironment removeObjectForKey:@"APP_SANDBOX_GROUP_ID"];
}
}
- (NSPipe *)standardOutput {
@@ -122,6 +195,9 @@ static int startQemu(UTMProcess *process, int argc, const char *argv[], const ch
self.mutableEnvironment[@"MVK_CONFIG_LOG_LEVEL"] = @"4";
self.mutableEnvironment[@"MVK_DEBUG"] = @"1";
self.mutableEnvironment[@"MTL_DEBUG_LAYER"] = @"1";
self.mutableEnvironment[@"MTL_DEBUG_LAYER_ERROR_MODE"] = @"nslog";
self.mutableEnvironment[@"ANGLE_ENABLE_DEBUG_TRACE"] = @"1";
self.mutableEnvironment[@"ANGLE_METAL_DEBUG_BINDINGS"] = @"1";
} else {
[self.mutableEnvironment removeObjectForKey:@"G_MESSAGES_DEBUG"];
[self.mutableEnvironment removeObjectForKey:@"VK_LOADER_DEBUG"];
@@ -130,6 +206,9 @@ static int startQemu(UTMProcess *process, int argc, const char *argv[], const ch
[self.mutableEnvironment removeObjectForKey:@"MVK_CONFIG_LOG_LEVEL"];
[self.mutableEnvironment removeObjectForKey:@"MVK_DEBUG"];
[self.mutableEnvironment removeObjectForKey:@"MTL_DEBUG_LAYER"];
[self.mutableEnvironment removeObjectForKey:@"MTL_DEBUG_LAYER_ERROR_MODE"];
[self.mutableEnvironment removeObjectForKey:@"ANGLE_ENABLE_DEBUG_TRACE"];
[self.mutableEnvironment removeObjectForKey:@"ANGLE_METAL_DEBUG_BINDINGS"];
}
}
+8
View File
@@ -42,4 +42,12 @@ typedef NS_ENUM(NSInteger, UTMQEMUVulkanDriver) {
kQEMUVulkanDriverKosmicKrisp = 3,
};
/// Specify the DirectX driver for this VM
typedef NS_ENUM(NSInteger, UTMQEMUDirectXDriver) {
kQEMUDirectXDriverDefault = 0,
kQEMUDirectXDriverDisabled = 1,
kQEMUDirectXDriverDXMT = 2,
kQEMUDirectXDriverD3DMetal = 3,
};
#endif /* UTMQemuSystemBackends_h */
+9
View File
@@ -254,6 +254,13 @@ extension UTMQemuVirtualMachine {
}
}
private var directXDriver: UTMQEMUDirectXDriver {
get {
let rawValue = UserDefaults.standard.integer(forKey: "QEMUDirectXDriver")
return UTMQEMUDirectXDriver(rawValue: rawValue) ?? .qemuDirectXDriverDefault
}
}
@MainActor private func qemuEnsureEfiVarsAvailable() async throws {
guard let efiVarsURL = config.qemu.efiVarsURL else {
return
@@ -346,7 +353,9 @@ extension UTMQemuVirtualMachine {
system.remoteBookmarks = remoteBookmarks
system.rendererBackend = rendererBackend
system.vulkanDriver = try vulkanDriver
system.directXDriver = directXDriver
system.shmemDirectoryURL = await config.shmemDirectoryURL
system.appSandboxGroupId = await config.appGroupIdentifier
system.hasDebugLog = hasDebugLog
try Task.checkCancellation()
+158 -11
View File
@@ -52,7 +52,7 @@
84018699288B71BF0050AC51 /* BusyIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84018696288B71BF0050AC51 /* BusyIndicator.swift */; };
8401FD71269BEB2B00265F0D /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = CE6B240A25F1F3CE0020D43E /* main.c */; };
8401FD72269BEB3000265F0D /* Bootstrap.c in Sources */ = {isa = PBXBuildFile; fileRef = CE0DF17125A80B6300A51894 /* Bootstrap.c */; };
8401FD7A269BECE200265F0D /* QEMULauncher.app in Embed Launcher */ = {isa = PBXBuildFile; fileRef = 8401FD62269BE9C500265F0D /* QEMULauncher.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
8401FD7A269BECE200265F0D /* QEMULauncher.app in Embed Executables */ = {isa = PBXBuildFile; fileRef = 8401FD62269BE9C500265F0D /* QEMULauncher.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
8401FDA0269D266E00265F0D /* VMConfigAppleBootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401FD9F269D266E00265F0D /* VMConfigAppleBootView.swift */; };
8401FDA2269D3E2500265F0D /* VMConfigAppleNetworkingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401FDA1269D3E2500265F0D /* VMConfigAppleNetworkingView.swift */; };
8401FDA4269D43CF00265F0D /* VMConfigAppleDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401FDA3269D43CF00265F0D /* VMConfigAppleDisplayView.swift */; };
@@ -735,6 +735,11 @@
CE9B15482B12A87E003A32DD /* GenerateKey.c in Sources */ = {isa = PBXBuildFile; fileRef = CE9B15462B12A87E003A32DD /* GenerateKey.c */; };
CE9B15492B12A87E003A32DD /* GenerateKey.c in Sources */ = {isa = PBXBuildFile; fileRef = CE9B15462B12A87E003A32DD /* GenerateKey.c */; };
CE9B154A2B12A87E003A32DD /* GenerateKey.c in Sources */ = {isa = PBXBuildFile; fileRef = CE9B15462B12A87E003A32DD /* GenerateKey.c */; };
CEA3EDEB3016810500AAA8E4 /* dxmt-native.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEA3EDE93016810500AAA8E4 /* dxmt-native.framework */; };
CEA3EDEC3016810500AAA8E4 /* dxmt-native.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CEA3EDE93016810500AAA8E4 /* dxmt-native.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEA3EDED3016810500AAA8E4 /* d3dmetal-native.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEA3EDEA3016810500AAA8E4 /* d3dmetal-native.framework */; };
CEA3EDEE3016810500AAA8E4 /* d3dmetal-native.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = CEA3EDEA3016810500AAA8E4 /* d3dmetal-native.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CEA3EE05301691C500AAA8E4 /* QEMURenderServer.app in Embed Executables */ = {isa = PBXBuildFile; fileRef = CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
CEA45E25263519B5002FA97D /* VMDisplayMetalViewController+Pointer.h in Sources */ = {isa = PBXBuildFile; fileRef = 83FBDD53242FA71900D2C5D7 /* VMDisplayMetalViewController+Pointer.h */; };
CEA45E27263519B5002FA97D /* VMRemovableDrivesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D954524AD4F980059923A /* VMRemovableDrivesView.swift */; };
CEA45E37263519B5002FA97D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE772AAB25C8B0F600E4E379 /* ContentView.swift */; };
@@ -1313,6 +1318,13 @@
remoteGlobalIDString = CE9A352C26533A51005077CF;
remoteInfo = JailbreakInterposer;
};
CEA3EE03301691C500AAA8E4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = CE550BC1225947990063E575 /* Project object */;
proxyType = 1;
remoteGlobalIDString = CEA3EDEF30168B9000AAA8E4;
remoteInfo = QEMURenderServer;
};
CEBDA1E324D8BDDB0010B5EC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = CE550BC1225947990063E575 /* Project object */;
@@ -1364,6 +1376,7 @@
CEF83F8F250094EE00557D15 /* gcrypt.20.framework in Embed Libraries */,
CE0B6F5424AD67FA00FE012D /* spice-client-glib-2.0.8.framework in Embed Libraries */,
CEF83F8B250094D700557D15 /* spice-server.1.framework in Embed Libraries */,
CEA3EDEC3016810500AAA8E4 /* dxmt-native.framework in Embed Libraries */,
CE0B6F1B24AD679700FE012D /* gstapp-1.0.0.framework in Embed Libraries */,
CE0B6F2924AD67AD00FE012D /* gsttag-1.0.0.framework in Embed Libraries */,
CE0B6F3124AD67C100FE012D /* phodav-3.0.0.framework in Embed Libraries */,
@@ -1388,6 +1401,7 @@
CE03D0C524D913AF00F76B84 /* ffi.8.framework in Embed Libraries */,
CEF83F8D250094E700557D15 /* gthread-2.0.0.framework in Embed Libraries */,
CE63B0DB2F0ED4FF00C59D13 /* vulkan_kosmickrisp.framework in Embed Libraries */,
CEA3EDEE3016810500AAA8E4 /* d3dmetal-native.framework in Embed Libraries */,
CE064C662A563F4B003C833D /* swtpm.0.framework in Embed Libraries */,
CE03D09024D9124800F76B84 /* intl.8.framework in Embed Libraries */,
CE03D0C924D9140A00F76B84 /* pixman-1.0.framework in Embed Libraries */,
@@ -1521,15 +1535,16 @@
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
};
CE6B241425F1F5630020D43E /* Embed Launcher */ = {
CE6B241425F1F5630020D43E /* Embed Executables */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 6;
files = (
8401FD7A269BECE200265F0D /* QEMULauncher.app in Embed Launcher */,
8401FD7A269BECE200265F0D /* QEMULauncher.app in Embed Executables */,
CEA3EE05301691C500AAA8E4 /* QEMURenderServer.app in Embed Executables */,
);
name = "Embed Launcher";
name = "Embed Executables";
runOnlyForDeploymentPostprocessing = 0;
};
CEA45F71263519B5002FA97D /* Embed Libraries */ = {
@@ -2075,6 +2090,11 @@
CE9D197B226542FE00355E14 /* UTMProcess.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UTMProcess.m; sourceTree = "<group>"; };
CEA02A972436C7A30087E45F /* UTMLegacyQemuConfiguration+Networking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UTMLegacyQemuConfiguration+Networking.h"; sourceTree = "<group>"; };
CEA02A982436C7A30087E45F /* UTMLegacyQemuConfiguration+Networking.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UTMLegacyQemuConfiguration+Networking.m"; sourceTree = "<group>"; };
CEA3EDE93016810500AAA8E4 /* dxmt-native.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "dxmt-native.framework"; path = "$(SYSROOT_DIR)/Frameworks/dxmt-native.framework"; sourceTree = "<group>"; };
CEA3EDEA3016810500AAA8E4 /* d3dmetal-native.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "d3dmetal-native.framework"; path = "$(SYSROOT_DIR)/Frameworks/d3dmetal-native.framework"; sourceTree = "<group>"; };
CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QEMURenderServer.app; sourceTree = BUILT_PRODUCTS_DIR; };
CEA3EDFD30168C9900AAA8E4 /* QEMURenderServer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QEMURenderServer.entitlements; sourceTree = "<group>"; };
CEA3EE13301C15DD00AAA8E4 /* QEMURenderServer-unsigned.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "QEMURenderServer-unsigned.entitlements"; sourceTree = "<group>"; };
CEA45FB9263519B5002FA97D /* UTM SE.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "UTM SE.app"; sourceTree = BUILT_PRODUCTS_DIR; };
CEA51F862A81EAB700DDD7FA /* VMToolbarOrnamentModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMToolbarOrnamentModifier.swift; sourceTree = "<group>"; };
CEA9053725F981E900801E7C /* usb-1.0.0.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "usb-1.0.0.framework"; path = "$(SYSROOT_DIR)/Frameworks/usb-1.0.0.framework"; sourceTree = "<group>"; };
@@ -2349,6 +2369,7 @@
CEF83F882500949D00557D15 /* gthread-2.0.0.framework in Frameworks */,
CE03D08724D90F0700F76B84 /* gobject-2.0.0.framework in Frameworks */,
CE9B15362B11A491003A32DD /* SwiftConnect in Frameworks */,
CEA3EDED3016810500AAA8E4 /* d3dmetal-native.framework in Frameworks */,
CE0B6F0A24AD677200FE012D /* spice-client-glib-2.0.8.framework in Frameworks */,
CE0B6ECF24AD677200FE012D /* gstrtp-1.0.0.framework in Frameworks */,
CE0B6ECC24AD677200FE012D /* gstriff-1.0.0.framework in Frameworks */,
@@ -2364,6 +2385,7 @@
CE0B6EBB24AD677200FE012D /* libgstgio.a in Frameworks */,
84937EFF28960789003148F4 /* zstd.1.framework in Frameworks */,
CE0B6EE224AD677200FE012D /* gstnet-1.0.0.framework in Frameworks */,
CEA3EDEB3016810500AAA8E4 /* dxmt-native.framework in Frameworks */,
CE03D08624D90F0700F76B84 /* gmodule-2.0.0.framework in Frameworks */,
CED297142CE425CB00F1E3EB /* soup-3.0.0.framework in Frameworks */,
CE03D0CA24D9142000F76B84 /* ssl.1.1.framework in Frameworks */,
@@ -2624,6 +2646,8 @@
CE2D63D622653C7300FC7E63 /* Frameworks */ = {
isa = PBXGroup;
children = (
CEA3EDEA3016810500AAA8E4 /* d3dmetal-native.framework */,
CEA3EDE93016810500AAA8E4 /* dxmt-native.framework */,
CE63B0D92F0ED4FF00C59D13 /* MoltenVK.framework */,
CE63B0D72F0ED4FE00C59D13 /* vulkan_kosmickrisp.framework */,
CE63B0D82F0ED4FE00C59D13 /* vulkan.1.framework */,
@@ -2894,6 +2918,7 @@
CE9D18F72265410E00355E14 /* qemu */,
CE63B0F02F0ED67700C59D13 /* vulkan */,
CE6B240925F1F3CE0020D43E /* QEMULauncher */,
CEA3EDFB30168C6300AAA8E4 /* QEMURenderServer */,
CE9A352E26533A51005077CF /* JailbreakInterposer */,
CE9B153D2B11A4ED003A32DD /* Remote */,
CE4698F824C8FBD9008C1BD6 /* Icons */,
@@ -2917,6 +2942,7 @@
8401FD62269BE9C500265F0D /* QEMULauncher.app */,
84E3A8F0293DB37E0024A740 /* utmctl */,
CEF7F6D32AEEDCC400E34952 /* UTM Remote.app */,
CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */,
);
name = Products;
sourceTree = "<group>";
@@ -3047,6 +3073,15 @@
path = Remote;
sourceTree = "<group>";
};
CEA3EDFB30168C6300AAA8E4 /* QEMURenderServer */ = {
isa = PBXGroup;
children = (
CEA3EDFD30168C9900AAA8E4 /* QEMURenderServer.entitlements */,
CEA3EE13301C15DD00AAA8E4 /* QEMURenderServer-unsigned.entitlements */,
);
path = QEMURenderServer;
sourceTree = "<group>";
};
CEB63A9624F47C1200CAF323 /* Shared */ = {
isa = PBXGroup;
children = (
@@ -3249,6 +3284,7 @@
CE2D951924AD48BE0059923A /* Frameworks */,
CE2D951A24AD48BE0059923A /* Resources */,
CE0B6E6D24AD66CE00FE012D /* Embed Libraries */,
CEA3EE0F301A5A4900AAA8E4 /* Embed D3DMetal */,
CEBDA1E924D8BDDB0010B5EC /* Embed XPC Services */,
84E3A8FA293DB70A0024A740 /* Embed CLI Tool */,
);
@@ -3293,6 +3329,21 @@
productReference = CE9A352D26533A51005077CF /* JailbreakInterposer.framework */;
productType = "com.apple.product-type.framework";
};
CEA3EDEF30168B9000AAA8E4 /* QEMURenderServer */ = {
isa = PBXNativeTarget;
buildConfigurationList = CEA3EDF730168B9000AAA8E4 /* Build configuration list for PBXNativeTarget "QEMURenderServer" */;
buildPhases = (
CEA3EE0130168DF000AAA8E4 /* Copy prebuilt binary */,
);
buildRules = (
);
dependencies = (
);
name = QEMURenderServer;
productName = QEMULauncher;
productReference = CEA3EDFA30168B9000AAA8E4 /* QEMURenderServer.app */;
productType = "com.apple.product-type.application";
};
CEA45E1F263519B5002FA97D /* iOS-SE */ = {
isa = PBXNativeTarget;
buildConfigurationList = CEA45FB6263519B5002FA97D /* Build configuration list for PBXNativeTarget "iOS-SE" */;
@@ -3333,12 +3384,13 @@
CEBDA1D624D8BDDA0010B5EC /* Sources */,
CEBDA1D724D8BDDA0010B5EC /* Frameworks */,
CEBDA1D824D8BDDA0010B5EC /* Resources */,
CE6B241425F1F5630020D43E /* Embed Launcher */,
CE6B241425F1F5630020D43E /* Embed Executables */,
);
buildRules = (
);
dependencies = (
8401FD7C269BECF200265F0D /* PBXTargetDependency */,
CEA3EE04301691C500AAA8E4 /* PBXTargetDependency */,
);
name = QEMUHelper;
productName = QEMUHelper;
@@ -3457,6 +3509,7 @@
CE2D951B24AD48BE0059923A /* macOS */,
CEBDA1D924D8BDDA0010B5EC /* QEMUHelper */,
8401FD61269BE9C500265F0D /* QEMULauncher */,
CEA3EDEF30168B9000AAA8E4 /* QEMURenderServer */,
CE9A352C26533A51005077CF /* JailbreakInterposer */,
84E3A8EF293DB37E0024A740 /* utmctl */,
);
@@ -3625,6 +3678,46 @@
shellScript = "#!/bin/sh\n\nPLISTBUDDY=/usr/libexec/PlistBuddy\nROOT_PLIST=\"$SCRIPT_OUTPUT_FILE_0\"\n\nCOUNT=$($PLISTBUDDY -c \"Print :PreferenceSpecifiers\" \"$ROOT_PLIST\" | grep -c '^ }$')\n\nfor ((i = 0; i < COUNT; i++)); do\n remove=0\n platform=$($PLISTBUDDY -c \"Print :PreferenceSpecifiers:$i:Platform\" \"$ROOT_PLIST\" 2> /dev/null)\n if [ ! -z \"$platform\" ]; then\n if [ \"$platform\" == \"$PLATFORM_FAMILY_NAME\" ]; then\n echo \"Found entry $i for $platform\"\n $PLISTBUDDY -c \"Delete :PreferenceSpecifiers:$i:Platform\" \"$ROOT_PLIST\"\n else\n echo \"Exclude $i due to Platform\"\n remove=1\n fi\n fi\n excludetargets=$($PLISTBUDDY -c \"Print :PreferenceSpecifiers:$i:ExcludeTargets\" \"$ROOT_PLIST\" 2> /dev/null | sed '1d;$d' | xargs)\n if [ ! -z \"$excludetargets\" ]; then\n found=0\n for target in $excludetargets; do\n if [ \"$target\" == \"$TARGET_NAME\" ]; then\n found=1\n fi\n done\n if [ $found -eq 1 ]; then\n echo \"Exclude $i due to ExcludeTargets\"\n remove=1\n else\n echo \"Found entry $i for ExcludeTargets\"\n $PLISTBUDDY -c \"Delete :PreferenceSpecifiers:$i:ExcludeTargets\" \"$ROOT_PLIST\"\n fi\n fi\n if [ $remove -eq 1 ]; then\n echo \"Removing entry $i\"\n $PLISTBUDDY -c \"Delete :PreferenceSpecifiers:$i\" \"$ROOT_PLIST\"\n ((COUNT--))\n ((i--))\n fi\ndone\n";
showEnvVarsInLog = 0;
};
CEA3EE0130168DF000AAA8E4 /* Copy prebuilt binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(PROJECT_DIR)/$(SYSROOT_DIR)/libexec/virgl_render_server",
);
name = "Copy prebuilt binary";
outputFileListPaths = (
);
outputPaths = (
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nSRC=\"${PROJECT_DIR}/${SYSROOT_DIR}/libexec/virgl_render_server\"\nDST=\"${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}\"\nTMP=\"${TARGET_TEMP_DIR}/${EXECUTABLE_NAME}\"\n\nif [ ! -f \"${SRC}\" ]; then\n echo \"error: prebuilt binary not found at ${SRC}\" >&2\n exit 1\nfi\n\n# install_name_tool writes a temporary file next to the binary it patches,\n# which the script sandbox disallows inside the product, so patch it here.\nrm -f \"${TMP}\"\ncp -X \"${SRC}\" \"${TMP}\"\nxattr -c \"${TMP}\"\nchmod +x \"${TMP}\"\n\n# The prebuilt binary has no LC_RPATH but links against\n# @rpath/vulkan.1.framework, so add the load paths this target declares.\nfor RPATH in ${LD_RUNPATH_SEARCH_PATHS}; do\n install_name_tool -add_rpath \"${RPATH}\" \"${TMP}\"\ndone\n\nmkdir -p \"$(dirname \"${DST}\")\"\nrm -f \"${DST}\"\ncp -X \"${TMP}\" \"${DST}\"\n";
};
CEA3EE0F301A5A4900AAA8E4 /* Embed D3DMetal */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(PROJECT_DIR)/$(SYSROOT_DIR)/Frameworks/D3DMetal.framework",
);
name = "Embed D3DMetal";
outputFileListPaths = (
);
outputPaths = (
"$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/D3DMetal.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nSRC=\"${PROJECT_DIR}/${SYSROOT_DIR}/Frameworks/D3DMetal.framework\"\nDST=\"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/D3DMetal.framework\"\n\nif [ ! -d \"${SRC}\" ]; then\n echo \"note: D3DMetal.framework not found in ${SYSROOT_DIR}, skipping\"\n rm -rf \"${DST}\"\n exit 0\nfi\n\n# Extended attributes on the input would make codesign refuse the bundle.\nmkdir -p \"$(dirname \"${DST}\")\"\nrm -rf \"${DST}\"\nditto --norsrc --noextattr --noqtn \"${SRC}\" \"${DST}\"\n\nif [ \"${CODE_SIGNING_ALLOWED}\" != \"NO\" ] && [ -n \"${EXPANDED_CODE_SIGN_IDENTITY}\" ]; then\n if [ \"${ENABLE_HARDENED_RUNTIME}\" = \"YES\" ]; then\n RUNTIME_FLAGS=\"--options runtime\"\n fi\n codesign --force --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" ${RUNTIME_FLAGS} ${OTHER_CODE_SIGN_FLAGS} \"${DST}\"\nfi\n";
};
CEF7F6812AEEDCC400E34952 /* Patch Settings bundle */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -4426,6 +4519,11 @@
target = CE9A352C26533A51005077CF /* JailbreakInterposer */;
targetProxy = CE9A353226533A52005077CF /* PBXContainerItemProxy */;
};
CEA3EE04301691C500AAA8E4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = CEA3EDEF30168B9000AAA8E4 /* QEMURenderServer */;
targetProxy = CEA3EE03301691C500AAA8E4 /* PBXContainerItemProxy */;
};
CEBDA1E424D8BDDB0010B5EC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = CEBDA1D924D8BDDA0010B5EC /* QEMUHelper */;
@@ -4633,7 +4731,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "$(LAUNCHER_CODE_SIGN_ENTITLEMENTS)";
CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_MAC:default=-)";
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4655,7 +4752,6 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "$(LAUNCHER_CODE_SIGN_ENTITLEMENTS)";
CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_MAC:default=-)";
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -4798,6 +4894,7 @@
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"WITH_JIT=1",
"WITH_SERVER=1",
@@ -4832,6 +4929,7 @@
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"WITH_JIT=1",
"WITH_SERVER=1",
@@ -4925,13 +5023,13 @@
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/include/spice-1",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/include/libusb-1.0",
);
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LIBRARY_SEARCH_PATHS = (
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib/gstreamer-1.0",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib",
);
MACOSX_DEPLOYMENT_TARGET = 11.3;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -5008,13 +5106,13 @@
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/include/spice-1",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/include/libusb-1.0",
);
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LIBRARY_SEARCH_PATHS = (
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib/gstreamer-1.0",
"$(PROJECT_DIR)/$(QUOTED_SYSROOT_DIR)/lib",
);
MACOSX_DEPLOYMENT_TARGET = 11.3;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
QUOTED_SYSROOT_DIR = "\"$(SYSROOT_DIR)\"";
@@ -5080,6 +5178,46 @@
};
name = Release;
};
CEA3EDF830168B9000AAA8E4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "$(RENDERER_CODE_SIGN_ENTITLEMENTS)";
CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_MAC:default=-)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../../../../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).QEMURenderServer";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Debug;
};
CEA3EDF930168B9000AAA8E4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "$(RENDERER_CODE_SIGN_ENTITLEMENTS)";
CODE_SIGN_IDENTITY = "$(CODE_SIGN_IDENTITY_MAC:default=-)";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../../../../../../../Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).QEMURenderServer";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SWIFT_EMIT_LOC_STRINGS = YES;
};
name = Release;
};
CEA45FB7263519B5002FA97D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -5327,6 +5465,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CEA3EDF730168B9000AAA8E4 /* Build configuration list for PBXNativeTarget "QEMURenderServer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CEA3EDF830168B9000AAA8E4 /* Debug */,
CEA3EDF930168B9000AAA8E4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CEA45FB6263519B5002FA97D /* Build configuration list for PBXNativeTarget "iOS-SE" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -16,7 +16,7 @@
"location" : "https://github.com/utmapp/CocoaSpice.git",
"state" : {
"branch" : "main",
"revision" : "52b1535824657354fc3089eab24f1827280f9143"
"revision" : "9873c6cacf866c8f47964e777b53f5c8f47364d7"
}
},
{
Binary file not shown.
Binary file not shown.
+548
View File
@@ -0,0 +1,548 @@
From 263d9536fa0f652024b90103b1c9fffd57cc2542 Mon Sep 17 00:00:00 2001
From: osy <[email protected]>
Date: Tue, 28 Jul 2026 10:04:14 -0700
Subject: [PATCH 1/4] spice-display: avoid sending duplicate scanout
If the same surface + size is sent, ignore sending scanout to the client.
Guests using flip-model presentation issue a set_scanout for every frame
with a different backing buffer of the same dimensions. The IOSurface and
its ID are unchanged in that case, so re-announcing it also closes the
previous pipe and trips the client's stale-POLLHUP protection. Frame
content is delivered by gl_draw regardless.
Assisted-by: Claude:claude-fable-5
---
include/ui/spice-display.h | 4 ++++
ui/spice-display.c | 42 ++++++++++++++++++++++++++++----------
2 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index 8e752f67da..cc052f0a08 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -145,6 +145,10 @@ struct SimpleSpiceDisplay {
#if defined(CONFIG_IOSURFACE)
IOSurfaceRef iosurface;
int surface_send_fd;
+ /* last geometry sent via spice_qxl_gl_scanout/monitor_config so we can
+ * skip re-sending the scanout for pure back-buffer flips */
+ uint32_t scanout_last_x, scanout_last_y, scanout_last_w, scanout_last_h;
+ bool scanout_last_y0top;
#if defined(CONFIG_METAL)
SpiceDisplayMetalContext metal_context;
#endif
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 976f68710a..82d758ae08 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -995,18 +995,19 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
ssd->iosurface = NULL;
}
-static bool spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
+/* returns -1 on error, 0 if the surface is unchanged, 1 if (re)created */
+static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
{
if (ssd->iosurface) {
if (IOSurfaceGetHeight(ssd->iosurface) != height ||
IOSurfaceGetWidth(ssd->iosurface) != width) {
spice_iosurface_destroy(ssd);
- return spice_iosurface_create(ssd, width, height);
+ return spice_iosurface_create(ssd, width, height) ? 1 : -1;
} else {
- return true;
+ return 0;
}
} else {
- return spice_iosurface_create(ssd, width, height);
+ return spice_iosurface_create(ssd, width, height) ? 1 : -1;
}
}
@@ -1277,7 +1278,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
qemu_spice_display_metal_scanout_disable(ssd->metal_context);
}
#endif
- if (spice_iosurface_resize(ssd, width, height)) {
+ if (spice_iosurface_resize(ssd, width, height) >= 0) {
fd = spice_iosurface_create_fd(ssd, &fourcc);
if (fd < 0) {
error_report("spice_gl_switch: failed to create fd");
@@ -1390,13 +1391,12 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
#if defined(CONFIG_GBM)
fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL);
#elif defined(CONFIG_IOSURFACE)
- if (spice_iosurface_resize(ssd, backing_width, backing_height)) {
- ssd->tex_id = tex_id;
- ssd->y_0_top = y_0_top;
- fd = spice_iosurface_create_fd(ssd, &fourcc);
- } else {
- fd = -1;
+ int res = spice_iosurface_resize(ssd, backing_width, backing_height);
+ if (res < 0) {
+ fprintf(stderr, "%s: failed to create IOSurface\n", __func__);
+ return;
}
+ ssd->tex_id = tex_id;
#if defined(CONFIG_METAL)
if (ssd->metal_context && native.type == SCANOUT_TEXTURE_NATIVE_TYPE_METAL) {
qemu_spice_display_metal_scanout_texture(ssd->metal_context,
@@ -1406,6 +1406,26 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
qemu_spice_display_metal_scanout_disable(ssd->metal_context);
}
#endif
+ /*
+ * Guests using flip-model presentation issue a set_scanout for every
+ * frame with a different backing buffer of the same dimensions. The
+ * IOSurface (and its ID) is unchanged in that case, so avoid spamming
+ * the SPICE client with gl_scanout/monitors_config messages (which
+ * would also close the previous pipe and trigger the client's
+ * stale-POLLHUP protection). Frame content is delivered by gl_draw.
+ */
+ if (res == 0 && ssd->have_scanout &&
+ ssd->y_0_top == y_0_top &&
+ ssd->scanout_last_x == x && ssd->scanout_last_y == y &&
+ ssd->scanout_last_w == w && ssd->scanout_last_h == h) {
+ return;
+ }
+ ssd->y_0_top = y_0_top;
+ ssd->scanout_last_x = x;
+ ssd->scanout_last_y = y;
+ ssd->scanout_last_w = w;
+ ssd->scanout_last_h = h;
+ fd = spice_iosurface_create_fd(ssd, &fourcc);
#endif
if (fd < 0) {
fprintf(stderr, "%s: failed to get fd for texture\n", __func__);
--
2.41.0
From 7e67e276de77359eeccd7eeacdc644e735919280 Mon Sep 17 00:00:00 2001
From: osy <[email protected]>
Date: Tue, 28 Jul 2026 10:04:32 -0700
Subject: [PATCH 2/4] hvf: fix lost interrupt delivery during WFI
cpus_kick_thread() can race hvf_wait_for_ipi() and deadlock the VM: its
thread_kicked dedup can swallow a kick that was issued right after
hvf_wait_for_ipi() cleared the flag but before it entered pselect(). The
vCPU then sleeps in WFI with the interrupt or stop request still pending,
which stalls guest interrupts indefinitely and hangs pause_all_vcpus() on
guest reset.
Always send the signal instead of relying on the dedup, and bound the
otherwise open-ended pselect() so that a kick lost for any other reason
degrades to a short delay rather than an unbounded sleep.
Assisted-by: Claude:claude-fable-5
---
target/arm/hvf/hvf.c | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 1f35d9f4d4..b20d70458f 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -1242,7 +1242,21 @@ int hvf_arch_init_vcpu(CPUState *cpu)
void hvf_kick_vcpu_thread(CPUState *cpu)
{
- cpus_kick_thread(cpu);
+ /*
+ * Do not use cpus_kick_thread(): its thread_kicked dedup races with
+ * hvf_wait_for_ipi() clearing thread_kicked right before an unbounded
+ * pselect(). A kick that is skipped because thread_kicked was still set
+ * from an already-consumed kick is lost forever: the vCPU sleeps in WFI
+ * with the interrupt (or stop request) pending, which delays guest
+ * interrupts indefinitely and deadlocks pause_all_vcpus() on guest
+ * reset (zombie VM). Always send the signal.
+ */
+ qatomic_set(&cpu->thread_kicked, true);
+ int err = pthread_kill(cpu->thread->thread, SIG_IPI);
+ if (err && err != ESRCH) {
+ fprintf(stderr, "qemu:%s: %s", __func__, strerror(err));
+ exit(1);
+ }
hv_vcpus_exit(&cpu->accel->fd, 1);
}
@@ -1967,13 +1981,28 @@ static uint64_t hvf_vtimer_val(void)
static void hvf_wait_for_ipi(CPUState *cpu, struct timespec *ts)
{
+ /*
+ * Cap open-ended waits so that a lost kick degrades to a short delay
+ * instead of an unbounded sleep (defense in depth on top of the
+ * unconditional signal in hvf_kick_vcpu_thread()).
+ */
+ struct timespec bounded = { .tv_sec = 0, .tv_nsec = 10 * SCALE_MS };
+
/*
* Use pselect to sleep so that other threads can IPI us while we're
* sleeping.
*/
qatomic_set_mb(&cpu->thread_kicked, false);
+ /*
+ * A kick sent before thread_kicked was cleared may have been deduped;
+ * re-check the conditions it would have signalled before sleeping.
+ */
+ if (qatomic_read(&cpu->stop) ||
+ (cpu->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIQ))) {
+ return;
+ }
bql_unlock();
- pselect(0, 0, 0, 0, ts, &cpu->accel->unblock_ipi_mask);
+ pselect(0, 0, 0, 0, ts ? ts : &bounded, &cpu->accel->unblock_ipi_mask);
bql_lock();
}
--
2.41.0
From 6c0ee265362b5cd22786f38f21806465c064b502 Mon Sep 17 00:00:00 2001
From: osy <[email protected]>
Date: Wed, 29 Jul 2026 01:02:20 -0700
Subject: [PATCH 3/4] ui/spice: carry the guest scanout's pixel format to the
IOSurface
The shared IOSurface was created with a hardcoded 'BGRA' pixel format,
and that format was hardcoded again at every consumer, including the
fourcc reported to the client through spice_qxl_gl_scanout().
A scanout is in whatever format the guest allocated. That is normally
BGRA, but a fullscreen D3D swapchain becomes the scanout directly and may
be R8G8B8A8, so its bytes ended up in a surface every consumer read as
BGRA and red and blue came out transposed. Windowed output was unaffected
because the scanout is then the desktop primary, which is BGRA.
Take the format from the scanout MTLTexture, recreate the surface when it
changes just as for a size change, and report the real fourcc.
SpiceGlScanout.format is documented as the drm fourcc format; it was
being filled with a constant.
The EGL path takes the format too. The CGL path cannot: for 8-bit colour
CGLTexImageIOSurface2D only accepts GL_BGRA with
GL_UNSIGNED_INT_8_8_8_8_REV, and that combination reads the surface as
BGRA no matter what its fourcc says, so a non-BGRA surface there is
refused rather than silently transposed. Only the Metal path is ever
asked for a different order.
Assisted-by: Claude:claude-opus-5
---
include/ui/spice-display.h | 5 +++
ui/spice-display-metal.m | 23 +++++++++++++-
ui/spice-display.c | 65 +++++++++++++++++++++++++++++---------
3 files changed, 77 insertions(+), 16 deletions(-)
diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
index cc052f0a08..965e248511 100644
--- a/include/ui/spice-display.h
+++ b/include/ui/spice-display.h
@@ -144,6 +144,9 @@ struct SimpleSpiceDisplay {
#endif
#if defined(CONFIG_IOSURFACE)
IOSurfaceRef iosurface;
+ /* IOSurface pixel format ('BGRA'/'RGBA') the surface was created with;
+ * a guest scanout in a different channel order forces a recreate. */
+ uint32_t iosurface_fourcc;
int surface_send_fd;
/* last geometry sent via spice_qxl_gl_scanout/monitor_config so we can
* skip re-sending the scanout for pure back-buffer flips */
@@ -220,6 +223,8 @@ SpiceDisplayMetalContext qemu_spice_display_metal_create_context(IOSurfaceRef su
uint32_t width,
uint32_t height);
void qemu_spice_display_metal_destroy_context(SpiceDisplayMetalContext ctx);
+/* IOSurface pixel format matching a scanout MTLTexture, 0 if unsupported. */
+uint32_t qemu_spice_display_metal_texture_fourcc(void *texture);
void qemu_spice_display_metal_scanout_texture(SpiceDisplayMetalContext ctx,
MTLTexture_id tex, uint32_t x, uint32_t y,
uint32_t w, uint32_t h);
diff --git a/ui/spice-display-metal.m b/ui/spice-display-metal.m
index 29e58d9461..23d8408068 100644
--- a/ui/spice-display-metal.m
+++ b/ui/spice-display-metal.m
@@ -38,7 +38,10 @@ - (instancetype)initWithDevice:(id<MTLDevice>)device
{
if (self = [super init]) {
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
- textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
+ /* must match the surface, which follows the guest scanout's order */
+ textureDescriptor.pixelFormat =
+ IOSurfaceGetPixelFormat(surface) == 'RGBA' ? MTLPixelFormatRGBA8Unorm
+ : MTLPixelFormatBGRA8Unorm;
textureDescriptor.width = width;
textureDescriptor.height = height;
textureDescriptor.usage = MTLTextureUsageRenderTarget;
@@ -112,6 +115,24 @@ - (void)drawFrameAtRect:(CGRect)rect completion:(void (^)(void))completion
@end
+uint32_t qemu_spice_display_metal_texture_fourcc(void *texture)
+{
+ id<MTLTexture> tex = (id<MTLTexture>)texture;
+
+ if (!tex) {
+ return 0;
+ }
+ switch (tex.pixelFormat) {
+ case MTLPixelFormatBGRA8Unorm:
+ return 'BGRA';
+ case MTLPixelFormatRGBA8Unorm:
+ return 'RGBA';
+ default:
+ /* no IOSurface equivalent wired up; caller keeps its default */
+ return 0;
+ }
+}
+
SpiceDisplayMetalContext qemu_spice_display_metal_create_context(IOSurfaceRef surface,
uint32_t width,
uint32_t height)
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 82d758ae08..d73fbf5271 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -818,9 +818,10 @@ static void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef
CFRelease(number);
}
-static bool spice_iosurface_create_egl(SimpleSpiceDisplay *ssd, int width, int height,
- IOSurfaceRef surface)
+static bool spice_iosurface_create_egl_fmt(SimpleSpiceDisplay *ssd, int width, int height,
+ IOSurfaceRef surface, uint32_t fourcc)
{
+ const GLenum egl_internal_format = (fourcc == 'RGBA') ? GL_RGBA : GL_BGRA_EXT;
#if defined(CONFIG_EGL)
EGLint target = 0;
GLenum tex_target = 0;
@@ -845,7 +846,7 @@ static bool spice_iosurface_create_egl(SimpleSpiceDisplay *ssd, int width, int h
EGL_HEIGHT, height,
EGL_IOSURFACE_PLANE_ANGLE, 0,
EGL_TEXTURE_TARGET, target,
- EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+ EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, egl_internal_format,
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_TEXTURE_TYPE_ANGLE, GL_UNSIGNED_BYTE,
EGL_IOSURFACE_USAGE_HINT_ANGLE, EGL_IOSURFACE_WRITE_HINT_ANGLE,
@@ -872,11 +873,23 @@ static bool spice_iosurface_create_egl(SimpleSpiceDisplay *ssd, int width, int h
}
static bool spice_iosurface_create_cgl(SimpleSpiceDisplay *ssd, int width, int height,
- IOSurfaceRef surface)
+ IOSurfaceRef surface, uint32_t fourcc)
{
#if defined(HAVE_SPICE_MAC_CGL)
GLuint tex;
+ /*
+ * CGLTexImageIOSurface2D accepts only GL_BGRA/GL_UNSIGNED_INT_8_8_8_8_REV
+ * for 8-bit colour (see the table in <OpenGL/CGLIOSurface.h>; anything else
+ * returns kCGLBadValue), and that combination reads the surface as BGRA
+ * whatever its fourcc says. So this path cannot carry a differently-ordered
+ * scanout at all, and must refuse rather than silently transpose.
+ */
+ if (fourcc != 'BGRA') {
+ error_report("spice_iosurface_create: CGL supports BGRA surfaces only");
+ return false;
+ }
+
CGLSetCurrentContext(spice_gl_ctx);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_RECTANGLE, tex);
@@ -904,14 +917,15 @@ static bool spice_iosurface_create_cgl(SimpleSpiceDisplay *ssd, int width, int h
#endif
}
-static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height)
+static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height,
+ uint32_t fourcc)
{
IOSurfaceRef surface;
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
AddIntegerValue(dict, kIOSurfaceWidth, width);
AddIntegerValue(dict, kIOSurfaceHeight, height);
- AddIntegerValue(dict, kIOSurfacePixelFormat, 'BGRA');
+ AddIntegerValue(dict, kIOSurfacePixelFormat, (int)fourcc);
AddIntegerValue(dict, kIOSurfaceBytesPerElement, 4);
#if TARGET_OS_OSX
CFDictionaryAddValue(dict, kIOSurfaceIsGlobal, kCFBooleanTrue);
@@ -926,18 +940,19 @@ static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int heigh
}
if (spice_opengl == DISPLAY_GL_MODE_CORE) {
- if (!spice_iosurface_create_cgl(ssd, width, height, surface)) {
+ if (!spice_iosurface_create_cgl(ssd, width, height, surface, fourcc)) {
CFRelease(surface);
return false;
}
} else {
- if (!spice_iosurface_create_egl(ssd, width, height, surface)) {
+ if (!spice_iosurface_create_egl_fmt(ssd, width, height, surface, fourcc)) {
CFRelease(surface);
return false;
}
}
ssd->iosurface = surface;
+ ssd->iosurface_fourcc = fourcc;
#if defined(CONFIG_METAL)
ssd->metal_context = qemu_spice_display_metal_create_context(surface, width, height);
@@ -996,18 +1011,20 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
}
/* returns -1 on error, 0 if the surface is unchanged, 1 if (re)created */
-static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
+static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height,
+ uint32_t fourcc)
{
if (ssd->iosurface) {
if (IOSurfaceGetHeight(ssd->iosurface) != height ||
- IOSurfaceGetWidth(ssd->iosurface) != width) {
+ IOSurfaceGetWidth(ssd->iosurface) != width ||
+ ssd->iosurface_fourcc != fourcc) {
spice_iosurface_destroy(ssd);
- return spice_iosurface_create(ssd, width, height) ? 1 : -1;
+ return spice_iosurface_create(ssd, width, height, fourcc) ? 1 : -1;
} else {
return 0;
}
} else {
- return spice_iosurface_create(ssd, width, height) ? 1 : -1;
+ return spice_iosurface_create(ssd, width, height, fourcc) ? 1 : -1;
}
}
@@ -1031,7 +1048,7 @@ static int spice_iosurface_create_fd(SimpleSpiceDisplay *ssd, int *fourcc)
// when we close it, POLLHUP will be seen by the other side and know that
// the surface ID is stale and should not be used
ssd->surface_send_fd = fds[1];
- *fourcc = 'BGRA';
+ *fourcc = (int)ssd->iosurface_fourcc;
surfaceid = IOSurfaceGetID(ssd->iosurface);
write(ssd->surface_send_fd, &surfaceid, sizeof(surfaceid));
return fds[0];
@@ -1278,7 +1295,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
qemu_spice_display_metal_scanout_disable(ssd->metal_context);
}
#endif
- if (spice_iosurface_resize(ssd, width, height) >= 0) {
+ if (spice_iosurface_resize(ssd, width, height, 'BGRA') >= 0) {
fd = spice_iosurface_create_fd(ssd, &fourcc);
if (fd < 0) {
error_report("spice_gl_switch: failed to create fd");
@@ -1391,7 +1408,25 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
#if defined(CONFIG_GBM)
fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL);
#elif defined(CONFIG_IOSURFACE)
- int res = spice_iosurface_resize(ssd, backing_width, backing_height);
+ /*
+ * The surface is shared with the client verbatim, so it has to be in the
+ * guest scanout's channel order: a fullscreen D3D swapchain can be
+ * R8G8B8A8 while the desktop is B8G8R8A8. Only the Metal path can honour
+ * a non-BGRA order end to end, so everything else stays BGRA.
+ */
+ uint32_t want_fourcc = 'BGRA';
+#if defined(CONFIG_METAL)
+ if (native.type == SCANOUT_TEXTURE_NATIVE_TYPE_METAL &&
+ spice_opengl != DISPLAY_GL_MODE_CORE) {
+ uint32_t native_fourcc =
+ qemu_spice_display_metal_texture_fourcc(native.handle);
+ if (native_fourcc) {
+ want_fourcc = native_fourcc;
+ }
+ }
+#endif
+ int res = spice_iosurface_resize(ssd, backing_width, backing_height,
+ want_fourcc);
if (res < 0) {
fprintf(stderr, "%s: failed to create IOSurface\n", __func__);
return;
--
2.41.0
From 27d8515de35937969dec6144dc6a0b63362d5ecc Mon Sep 17 00:00:00 2001
From: osy <[email protected]>
Date: Wed, 29 Jul 2026 16:20:12 -0700
Subject: [PATCH 4/4] Revert "ui/console: force gl-unblock from the watchdog
timer"
This reverts commit 5cb1434ba33797b08f94c0f2168cd5ee7f70d8e6.
---
ui/console.c | 45 ++-------------------------------------------
1 file changed, 2 insertions(+), 43 deletions(-)
diff --git a/ui/console.c b/ui/console.c
index a2dddbd37a..f6d7879eff 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -166,32 +166,7 @@ void qemu_console_co_wait_update(QemuConsole *con)
static void graphic_hw_gl_unblock_timer(void *opaque)
{
- QemuConsole *con = opaque;
-
- /*
- * The display's GL render did not release the block within the timeout
- * (e.g. the gtk window is occluded, the host compositor throttled the
- * frame callback, or the host GPU stalled). The device's command queue is
- * gated on this block (virtio-gpu: renderer_blocked), so a stuck display
- * stalls the guest GPU scheduler into a TDR (dxgmms1 0x7E) ~2 s later.
- * Force the unblock so guest command/fence processing resumes well before
- * the guest's TdrDelay; the worst case is one stale/torn displayed frame,
- * never a guest crash. A late real release is absorbed in
- * graphic_hw_gl_block() (con->gl_block already 0 -> no-op).
- */
- /* Rate-limited: this can fire repeatedly when the display fence is
- * persistently slow; an occasional line is enough to know it's active. */
- static unsigned _ub;
- if ((_ub++ % 60u) == 0u) {
- warn_report("console: gl-unblock fallback firing (display GL fence slow); "
- "forcing unblock to keep the guest cmdq moving");
- }
- if (con->gl_block > 0) {
- con->gl_block = 0;
- if (con->hw_ops->gl_block) {
- con->hw_ops->gl_block(con->hw, false);
- }
- }
+ warn_report("console: no gl-unblock within one second");
}
void graphic_hw_gl_block(QemuConsole *con, bool block)
@@ -202,13 +177,6 @@ void graphic_hw_gl_block(QemuConsole *con, bool block)
if (block) {
con->gl_block++;
} else {
- /*
- * A late release after graphic_hw_gl_unblock_timer() force-cleared the
- * block (con->gl_block already 0) must be a safe no-op, not underflow.
- */
- if (con->gl_block == 0) {
- return;
- }
con->gl_block--;
}
assert(con->gl_block >= 0);
@@ -222,16 +190,7 @@ void graphic_hw_gl_block(QemuConsole *con, bool block)
if (block) {
timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
- /* Safety net only. The display samples (blits + fences) the guest
- * buffer directly at flush time, so the block lifetime is the GPU
- * fence latency -- not the host compositor's frame clock -- and
- * releasing the guest early TEARS: it re-renders into the buffer
- * while the queued blit still reads it (scaled/shifted frame
- * artifacts under present storms). 500ms is correct backpressure
- * for a slow (shared, possibly saturated) GPU while still
- * recovering a genuinely stuck fence well inside the guest's ~2s
- * TDR budget. */
- timeout += 500;
+ timeout += 1000; /* one sec */
timer_mod(con->gl_unblock_timer, timeout);
} else {
timer_del(con->gl_unblock_timer);
--
2.41.0
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -23,7 +23,7 @@ SPICE_SERVER_SRC="https://www.spice-space.org/download/releases/spice-server/spi
USB_SRC="https://github.com/libusb/libusb/releases/download/v1.0.25/libusb-1.0.25.tar.bz2"
USBREDIR_SRC="https://www.spice-space.org/download/usbredir/usbredir-0.14.0.tar.xz"
SLIRP_SRC="https://github.com/utmapp/libslirp/releases/download/v4.9.1-release-mirror/libslirp-v4.9.1.tar.gz"
QEMU_SRC="https://github.com/utmapp/qemu/releases/download/v10.0.2-utm/qemu-10.0.2-utm.tar.xz"
QEMU_SRC="https://github.com/utmapp/qemu/releases/download/v10.0.12-utm/qemu-10.0.12-utm.tar.xz"
# Source files for spice-client
JSON_GLIB_SRC="https://download.gnome.org/sources/json-glib/1.10/json-glib-1.10.0.tar.xz"
@@ -42,15 +42,20 @@ WEBKIT_REPO="https://github.com/utmapp/WebKit.git"
WEBKIT_COMMIT="ed78ab6e1a37f4f11583a0bd038f22ec91f3ff10"
WEBKIT_SUBDIRS="Source/ThirdParty/ANGLE Configurations Tools/ccache"
EPOXY_REPO="https://github.com/utmapp/libepoxy.git"
EPOXY_COMMIT="5014658f79e4d6872a1ad6754da9098ccd9d4fc5"
EPOXY_COMMIT="bf98587477fe68d07b93319ece7b40a7d0e2eabe"
VULKAN_LOADER_REPO="https://github.com/osy/Vulkan-Loader.git"
VULKAN_LOADER_COMMIT="6df86907be582927507637f3f6003ea58bc34517"
VIRGLRENDERER_REPO="https://github.com/utmapp/virglrenderer.git"
VIRGLRENDERER_COMMIT="d48a2d0d9a722fffd3f92c83e71d9426a4892a66"
VIRGLRENDERER_COMMIT="9499d3e53edbf356fb3e8f5ab68b27e208003022"
MESA_REPO="https://gitlab.freedesktop.org/osy/mesa.git"
MESA_COMMIT="79bc850d884a1307356ff61c017e58901b90c7e2"
MOLTENVK_REPO="https://github.com/utmapp/MoltenVK.git"
MOLTENVK_COMMIT="111c14f3abf5c00118fc7a5b00c92d7abbf40f62"
MOLTENVK_COMMIT="ef1c5461774f5fbd224ddcfd91fd2c0ea23f0384"
LLVM15_SRC="https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.7/llvm-project-15.0.7.src.tar.xz"
DXMT_REPO="https://github.com/utmapp/dxmt.git"
DXMT_COMMIT="be80e14c07613cd558036a576b82dc6d78acdfce"
D3DMETAL_REPO="https://github.com/utmapp/d3dmetal-native.git"
D3DMETAL_COMMIT="edd2e386c25a2039eca0f6ce8f794b3b165841eb"
# Decompiled Hypervisor for iOS
HYPERVISOR_REPO="https://github.com/utmapp/Hypervisor.git"
+118 -37
View File
@@ -21,9 +21,13 @@ RED='\033[0;31m'
NC='\033[0m'
# Knobs
IOS_SDKMINVER="14.0"
MAC_SDKMINVER="11.0"
IOS_SDKMINVER="16.0"
MAC_SDKMINVER="13.0"
VISIONOS_SDKMINVER="1.0"
# Network operations are retried because concurrent builds on the same CI runner
# get rate limited by GitHub and fail to clone/fetch.
RETRY_ATTEMPTS="${RETRY_ATTEMPTS:-5}"
RETRY_DELAY="${RETRY_DELAY:-15}"
# Build environment
PLATFORM=
@@ -90,6 +94,26 @@ check_env () {
version_check "2.4" "$(bison -V | head -1 | awk '{ print $NF }')" || { echo >&2 "${RED}'bison' >= 2.4 is required. Did you install from Homebrew and updated your \$PATH variable?"; exit 1; }
}
# Run a command, retrying with exponential backoff if it fails. Jitter is derived
# from the PID so that concurrent builds do not retry in lockstep.
retry () {
ATTEMPT=1
DELAY="$RETRY_DELAY"
while true; do
"$@" && return 0
STATUS=$?
if [ $ATTEMPT -ge $RETRY_ATTEMPTS ]; then
echo >&2 "${RED}'$*' failed after $ATTEMPT attempts.${NC}"
return $STATUS
fi
SLEEP=$(( DELAY + ($$ + ATTEMPT) % 10 ))
echo >&2 "${RED}'$*' failed (attempt $ATTEMPT of $RETRY_ATTEMPTS), retrying in ${SLEEP}s...${NC}"
sleep $SLEEP
ATTEMPT=$(( ATTEMPT + 1 ))
DELAY=$(( DELAY * 2 ))
done
}
download () {
URL=$1
FILE="$(basename $URL)"
@@ -102,7 +126,7 @@ download () {
echo "${GREEN}$TARGET already downloaded! Run with -d to force re-download.${NC}"
else
echo "${GREEN}Downloading ${URL}${NC}"
curl -L -O "$URL"
retry curl -f -L -O "$URL"
mv "$FILE" "$TARGET"
fi
if [ -d "$DIR" ]; then
@@ -121,6 +145,12 @@ download () {
fi
}
# Discard any partial clone from a previous attempt so that a retry starts clean.
clone_fresh () {
rm -rf "$2"
git clone --filter=tree:0 --no-checkout "$1" "$2"
}
clone () {
REPO="$1"
COMMIT="$2"
@@ -130,15 +160,26 @@ clone () {
if [ -d "$DIR" -a -z "$REDOWNLOAD" ]; then
echo "${GREEN}$DIR already downloaded! Run with -d to force re-download.${NC}"
else
rm -rf "$DIR"
echo "${GREEN}Cloning ${REPO}...${NC}"
git clone --filter=tree:0 --no-checkout "$REPO" "$DIR"
retry clone_fresh "$REPO" "$DIR"
if [ ! -z "$SUBDIRS" ]; then
git -C "$DIR" sparse-checkout init
git -C "$DIR" sparse-checkout set $SUBDIRS
fi
fi
git -C "$DIR" checkout "$COMMIT"
# tree:0 filtered clones fetch missing objects here, so these hit the network too
retry git -C "$DIR" checkout "$COMMIT"
retry git -C "$DIR" submodule update --init --recursive --filter=tree:0
}
clone_moltenvk_dependences () {
REPO="$1"
NAME="$(basename $REPO)"
DIR="$BUILD_DIR/$NAME"
pwd="$(pwd)"
cd "$DIR"
retry "./fetchDependencies" --none -v
cd "$pwd"
}
download_all () {
@@ -182,6 +223,10 @@ download_all () {
clone $LIBUCONTEXT_REPO $LIBUCONTEXT_COMMIT
clone $MESA_REPO $MESA_COMMIT
clone $MOLTENVK_REPO $MOLTENVK_COMMIT
clone_moltenvk_dependences $MOLTENVK_REPO
download $LLVM15_SRC
clone $DXMT_REPO $DXMT_COMMIT
clone $D3DMETAL_REPO $D3DMETAL_COMMIT
}
copy_private_headers() {
@@ -225,13 +270,16 @@ generate_meson_cross() {
echo "c_args = [${CFLAGS:+$(meson_quote $CFLAGS)}]" >> $cross
echo "cpp_args = [${CXXFLAGS:+$(meson_quote $CXXFLAGS)}]" >> $cross
echo "objc_args = [${OBJCFLAGS:+$(meson_quote $OBJCFLAGS)}]" >> $cross
echo "objcpp_args = [${OBJCFLAGS:+$(meson_quote $OBJCFLAGS)}]" >> $cross
echo "c_link_args = [${LDFLAGS:+$(meson_quote $LDFLAGS)}]" >> $cross
echo "cpp_link_args = [${LDFLAGS:+$(meson_quote $LDFLAGS)}]" >> $cross
echo "objc_link_args = [${LDFLAGS:+$(meson_quote $LDFLAGS)}]" >> $cross
echo "objcpp_link_args = [${LDFLAGS:+$(meson_quote $LDFLAGS)}]" >> $cross
echo "[binaries]" >> $cross
echo "c = [$(meson_quote $CC)]" >> $cross
echo "cpp = [$(meson_quote $CXX)]" >> $cross
echo "objc = [$(meson_quote $OBJCC)]" >> $cross
echo "objcpp = [$(meson_quote $OBJCC)]" >> $cross
echo "ar = [$(meson_quote $AR)]" >> $cross
echo "nm = [$(meson_quote $NM)]" >> $cross
echo "pkgconfig = ['$PREFIX/host/bin/pkg-config']" >> $cross
@@ -293,19 +341,19 @@ generate_cmake_toolchain() {
case $PLATFORM in
ios_simulator* )
echo "set(CMAKE_SYSTEM_NAME iOS)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT iphonesimulator)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT iphonesimulator CACHE STRING \"\" FORCE)" >> "$toolchain"
;;
ios* )
echo "set(CMAKE_SYSTEM_NAME iOS)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT iphoneos)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING \"\" FORCE)" >> "$toolchain"
;;
visionos_simulator* )
echo "set(CMAKE_SYSTEM_NAME visionOS)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT xrsimulator)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT xrsimulator CACHE STRING \"\" FORCE)" >> "$toolchain"
;;
visionos* )
echo "set(CMAKE_SYSTEM_NAME visionOS)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT xros)" >> "$toolchain"
echo "set(CMAKE_OSX_SYSROOT xros CACHE STRING \"\" FORCE)" >> "$toolchain"
;;
macos )
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> "$toolchain"
@@ -317,7 +365,7 @@ generate_cmake_toolchain() {
# Architecture
#
echo "set(CMAKE_SYSTEM_PROCESSOR \"$ARCH\")" >> "$toolchain"
echo "set(CMAKE_OSX_ARCHITECTURES \"$ARCH\")" >> "$toolchain"
echo "set(CMAKE_OSX_ARCHITECTURES \"$ARCH\" CACHE STRING \"\" FORCE)" >> "$toolchain"
echo "set(CMAKE_MACOSX_BUNDLE OFF CACHE BOOL \"\")" >> "$toolchain"
echo "" >> "$toolchain"
@@ -343,6 +391,7 @@ generate_cmake_toolchain() {
echo "set(CMAKE_C_COMPILER \"$CC_BIN\")" >> "$toolchain"
echo "set(CMAKE_CXX_COMPILER \"$CXX_BIN\")" >> "$toolchain"
echo "set(CMAKE_OBJC_COMPILER \"$OBJC_BIN\")" >> "$toolchain"
echo "set(CMAKE_ASM_COMPILER \"$CC_BIN\")" >> "$toolchain"
echo "" >> "$toolchain"
#
@@ -359,6 +408,7 @@ generate_cmake_toolchain() {
#
if [ -n "$CFLAGS" ]; then
echo "set(CMAKE_C_FLAGS \"$CFLAGS\" CACHE STRING \"\" FORCE)" >> "$toolchain"
echo "set(CMAKE_ASM_FLAGS \"$CFLAGS\" CACHE STRING \"\" FORCE)" >> "$toolchain"
fi
if [ -n "$OBJCFLAGS" ]; then
@@ -695,7 +745,12 @@ build_qemu_dependencies () {
# strip the minor versions
VULKAN_DYLIB="$PREFIX/lib/libvulkan.1.dylib"
mv "$(dirname $VULKAN_DYLIB)/$(readlink $VULKAN_DYLIB)" "$VULKAN_DYLIB"
meson_darwin_build $VIRGLRENDERER_REPO -Dtests=false -Dcheck-gl-errors=false -Dvenus=true -Dvulkan-dload=false -Drender-server-worker=thread
if [ "$PLATFORM" == "macos" ]; then
VIRGL_RENDER_SERVER_MODE="process"
else
VIRGL_RENDER_SERVER_MODE="thread"
fi
meson_darwin_build $VIRGLRENDERER_REPO -Dtests=false -Dcheck-gl-errors=false -Dvenus=true -Dneptune=true -Dvulkan-dload=false -Drender-server-mode="$VIRGL_RENDER_SERVER_MODE"
# Hypervisor for iOS
if [ "$PLATFORM" == "ios" ] || [ "$PLATFORM" == "ios_simulator" ]; then
build_hypervisor
@@ -785,7 +840,39 @@ build_vulkan_drivers () {
patch_vulkan_icd "$PREFIX/share/vulkan/icd.d/MoltenVK_icd.json"
}
fixup () {
build_d3d_drivers () {
LLVM15_NAME=$(basename "${LLVM15_SRC%.tar.*}")
cmake_build "$BUILD_DIR/$LLVM15_NAME/llvm" -DLLVM_ENABLE_ZSTD=Off -DLLVM_TARGETS_TO_BUILD="" -DLLVM_BUILD_TOOLS=Off -DLLVM_VERSION_PRINTER_SHOW_HOST_TARGET_INFO=Off
meson_build $DXMT_REPO -Dnative_llvm_path="$PREFIX"
if [ "$ARCH" == "x86_64" ]; then
meson_build $D3DMETAL_REPO -Dtests=disabled
else
# empty placeholder file
$CC -dynamiclib -x c /dev/null -o "$PREFIX/lib/libd3dmetal-native.dylib"
fi
}
fixup_imports() {
_file=$1
_list=$(otool -L "$_file" | tail -n +2 | cut -d ' ' -f 1 | awk '{$1=$1};1')
for g in $_list
do
base=$(basename "$g")
basefilename=${base%.*}
libname=${basefilename#lib*}
dir=$(dirname "$g")
if [ "$dir" == "$PREFIX/lib" ] || [ "$dir" == "@rpath" ]; then
if [ "$PLATFORM" == "macos" ]; then
newname="@rpath/$libname.framework/Versions/A/$libname"
else
newname="@rpath/$libname.framework/$libname"
fi
install_name_tool -change "$g" "$newname" "$_file"
fi
done
}
fixup_dylib () {
FILE=$1
BASE=$(basename "$FILE")
BASEFILENAME=${BASE%.*}
@@ -801,18 +888,16 @@ fixup () {
INFOPATH="$FRAMEWORKPATH"
fi
NEWFILE="$FRAMEWORKPATH/$LIBNAME"
LIST=$(otool -L "$FILE" | tail -n +2 | cut -d ' ' -f 1 | awk '{$1=$1};1')
OLDIFS=$IFS
IFS=$'\n'
echo "${GREEN}Fixing up $FILE...${NC}"
mkdir -p "$FRAMEWORKPATH"
mkdir -p "$INFOPATH"
cp -a "$FILE" "$NEWFILE"
/usr/libexec/PlistBuddy -c "Add :CFBundleExecutable string $LIBNAME" "$INFOPATH/Info.plist"
/usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $BUNDLE_ID" "$INFOPATH/Info.plist"
/usr/libexec/PlistBuddy -c "Add :MinimumOSVersion string $SDKMINVER" "$INFOPATH/Info.plist"
/usr/libexec/PlistBuddy -c "Add :CFBundleVersion string 1" "$INFOPATH/Info.plist"
/usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string 1.0" "$INFOPATH/Info.plist"
/usr/libexec/PlistBuddy -c "Add :CFBundleExecutable string $LIBNAME" "$INFOPATH/Info.plist" || true
/usr/libexec/PlistBuddy -c "Add :CFBundleIdentifier string $BUNDLE_ID" "$INFOPATH/Info.plist" || true
/usr/libexec/PlistBuddy -c "Add :MinimumOSVersion string $SDKMINVER" "$INFOPATH/Info.plist" || true
/usr/libexec/PlistBuddy -c "Add :CFBundleVersion string 1" "$INFOPATH/Info.plist" || true
/usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string 1.0" "$INFOPATH/Info.plist" || true
if [ "$PLATFORM" == "macos" ]; then
ln -sf "A" "$BASEFRAMEWORKPATH/Versions/Current"
ln -sf "Versions/Current/Resources" "$BASEFRAMEWORKPATH/Resources"
@@ -820,21 +905,7 @@ fixup () {
fi
newname="@rpath/$FRAMEWORKNAME/$LIBNAME"
install_name_tool -id "$newname" "$NEWFILE"
for g in $LIST
do
base=$(basename "$g")
basefilename=${base%.*}
libname=${basefilename#lib*}
dir=$(dirname "$g")
if [ "$dir" == "$PREFIX/lib" ] || [ "$dir" == "@rpath" ]; then
if [ "$PLATFORM" == "macos" ]; then
newname="@rpath/$libname.framework/Versions/A/$libname"
else
newname="@rpath/$libname.framework/$libname"
fi
install_name_tool -change "$g" "$newname" "$NEWFILE"
fi
done
fixup_imports "$NEWFILE"
IFS=$OLDIFS
}
@@ -844,7 +915,14 @@ fixup_all () {
FILES=$(find "$SYSROOT_DIR/lib" -type f -maxdepth 1 -name "*.dylib")
for f in $FILES
do
fixup $f
echo "${GREEN}Fixing up $f...${NC}"
fixup_dylib $f
done
FILES=$(find "$SYSROOT_DIR/libexec" -type f -maxdepth 1 -perm +111)
for f in $FILES
do
echo "${GREEN}Fixing up $f...${NC}"
fixup_imports $f
done
IFS=$OLDIFS
}
@@ -1089,7 +1167,7 @@ fi
echo "${GREEN}Deleting old sysroot!${NC}"
rm -rf "$PREFIX/"*
rm -f "$BUILD_DIR/BUILD_SUCCESS"
rm -f "$BUILD_DIR/meson*.cross"
rm -f "$BUILD_DIR"/meson*.cross
rm -f "$BUILD_DIR/cross.cmake"
mkdir -p "$PREFIX/Frameworks"
copy_private_headers
@@ -1098,6 +1176,9 @@ build_qemu_dependencies
build $QEMU_DIR --cross-prefix="" $QEMU_PLATFORM_BUILD_FLAGS $QEMU_DEBUG_FLAGS
build_spice_client
build_vulkan_drivers
if [ "$PLATFORM" == "macos" ]; then
build_d3d_drivers
fi
fixup_all
remove_shared_gst_plugins # another hack...
echo "${GREEN}All done!${NC}"
+5
View File
@@ -92,10 +92,12 @@ if [ "$SDK" == "macosx" ]; then
# this way we can import into Xcode and re-sign from there
UTM_ENTITLEMENTS="/tmp/utm.$$.entitlements"
LAUNCHER_ENTITLEMENTS="/tmp/launcher.$$.entitlements"
RENDERER_ENTITLEMENTS="/tmp/renderer.$$.entitlements"
HELPER_ENTITLEMENTS="/tmp/helper.$$.entitlements"
CLI_ENTITLEMENTS="/tmp/cli.$$.entitlements"
cp "$BASEDIR/../Platform/macOS/macOS.entitlements" "$UTM_ENTITLEMENTS"
cp "$BASEDIR/../QEMULauncher/QEMULauncher.entitlements" "$LAUNCHER_ENTITLEMENTS"
cp "$BASEDIR/../QEMURenderServer/QEMURenderServer.entitlements" "$RENDERER_ENTITLEMENTS"
cp "$BASEDIR/../QEMUHelper/QEMUHelper.entitlements" "$HELPER_ENTITLEMENTS"
cp "$BASEDIR/../utmctl/utmctl.entitlements" "$CLI_ENTITLEMENTS"
if [ ! -z "$TEAM_IDENTIFIER" ]; then
@@ -105,12 +107,15 @@ if [ "$SDK" == "macosx" ]; then
/usr/libexec/PlistBuddy -c "Set :com.apple.security.application-groups:0 ${TEAM_ID_PREFIX}${PRODUCT_BUNDLE_PREFIX}.UTM" "$UTM_ENTITLEMENTS"
/usr/libexec/PlistBuddy -c "Set :com.apple.security.application-groups:0 ${TEAM_ID_PREFIX}${PRODUCT_BUNDLE_PREFIX}.UTM" "$HELPER_ENTITLEMENTS"
/usr/libexec/PlistBuddy -c "Set :com.apple.security.application-groups:0 ${TEAM_ID_PREFIX}${PRODUCT_BUNDLE_PREFIX}.UTM" "$CLI_ENTITLEMENTS"
# nested bundles must be signed before the XPC service that contains them
codesign --force --sign - --entitlements "$LAUNCHER_ENTITLEMENTS" --timestamp=none --options runtime "$BUILT_PATH/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMULauncher.app/Contents/MacOS/QEMULauncher"
codesign --force --sign - --entitlements "$RENDERER_ENTITLEMENTS" --timestamp=none --options runtime "$BUILT_PATH/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMURenderServer.app/Contents/MacOS/QEMURenderServer"
codesign --force --sign - --entitlements "$HELPER_ENTITLEMENTS" --timestamp=none --options runtime "$BUILT_PATH/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMUHelper"
codesign --force --sign - --entitlements "$CLI_ENTITLEMENTS" --timestamp=none --options runtime "$BUILT_PATH/Contents/MacOS/utmctl"
codesign --force --sign - --entitlements "$UTM_ENTITLEMENTS" --timestamp=none --options runtime "$BUILT_PATH/Contents/MacOS/UTM"
rm "$UTM_ENTITLEMENTS"
rm "$LAUNCHER_ENTITLEMENTS"
rm "$RENDERER_ENTITLEMENTS"
rm "$HELPER_ENTITLEMENTS"
rm "$CLI_ENTITLEMENTS"
fi
+80
View File
@@ -0,0 +1,80 @@
#!/bin/sh
set -e
usage () {
echo "Usage: $(basename $0) sysroot-dir [dmg-url]"
echo ""
echo " sysroot-dir Sysroot to install into, e.g. sysroot-macOS-arm64_x86_64."
echo " dmg-url D3DMetal distribution DMG, either a URL or a local path."
echo ""
echo "Installs D3DMetal.framework into <sysroot-dir>/Frameworks, rebranded to"
echo "BUNDLE_ID below and ad-hoc signed. Does nothing if no URL is given."
echo ""
exit 1
}
# D3DMetal loads its own default.metallib through
# [NSBundle bundleWithIdentifier:@"com.apple.D3DMetal"]
# using that identifier as a literal compiled into the framework binary. We must
# not ship a com.apple.* bundle, so the identifier is rewritten in BOTH the
# Info.plist and the binary -- rewriting only one makes the lookup return nil and
# every D3DMetal process aborts in -[MTLDevice newLibraryWithFile:nil error:].
APPLE_BUNDLE_ID=com.apple.D3DMetal
BUNDLE_ID=com.elppa.D3DMetal
if [ $# -lt 1 ]; then
usage
fi
SYSROOT_DIR="$1"
DMG_URL="$2"
if [ -z "$DMG_URL" ]; then
echo "note: no D3DMetal DMG URL given, skipping"
exit 0
fi
if [ ${#APPLE_BUNDLE_ID} -ne ${#BUNDLE_ID} ]; then
echo "error: $BUNDLE_ID must be ${#APPLE_BUNDLE_ID} bytes to patch in place" >&2
exit 1
fi
FRAMEWORK="$SYSROOT_DIR/Frameworks/D3DMetal.framework"
MOUNT="$PWD/d3dmetal-mount"
DMG="$PWD/d3dmetal.dmg"
# Rewrite every occurrence of $1 with $2 in the binary $3, in place. Fails if the
# identifier is absent, so a future D3DMetal that drops it cannot ship silently.
patch_binary_identifier () {
LC_ALL=C perl -0777 -i -pe '
BEGIN { $old = shift @ARGV; $new = shift @ARGV }
$found += s/\Q$old\E/$new/g;
END { die "error: $old not found in $ARGV\n" unless $found }
' "$1" "$2" "$3"
}
if [ -f "$DMG_URL" ]; then
DMG="$DMG_URL"
else
curl -f -L -o "$DMG" "$DMG_URL"
fi
# The distribution DMG carries a license agreement, which hdiutil prompts for on
# stdin unless we answer it.
mkdir -p "$MOUNT"
yes | PAGER=cat hdiutil attach -nobrowse -readonly -noautoopen -mountpoint "$MOUNT" "$DMG"
trap 'hdiutil detach "$MOUNT" -quiet || true' EXIT
mkdir -p "$SYSROOT_DIR/Frameworks"
rm -rf "$FRAMEWORK"
ditto "$MOUNT/redist/lib/external/D3DMetal.framework" "$FRAMEWORK"
xattr -drs com.apple.quarantine "$FRAMEWORK"
patch_binary_identifier "$APPLE_BUNDLE_ID" "$BUNDLE_ID" "$FRAMEWORK/Versions/A/D3DMetal"
plutil -replace CFBundleIdentifier -string "$BUNDLE_ID" "$FRAMEWORK/Resources/Info.plist"
# Signs after the rewrite so the signing identifier is derived from the new
# Info.plist and the two agree.
codesign --force --sign - "$FRAMEWORK"
echo "installed D3DMetal $(plutil -extract CFBundleShortVersionString raw "$FRAMEWORK/Resources/version.plist") as $BUNDLE_ID into $FRAMEWORK"
+1
View File
@@ -72,6 +72,7 @@ pack_dir() {
cp -a "$SRC/$DIR" "$TGT/$DIR"
}
pack_all_objs "$BASEDIR" "*/libexec/virgl_render_server"
pack_all_objs "$BASEDIR" "*/bin/qemu-*"
pack_all_objs "$BASEDIR" "*/lib/*.dylib"
pack_all_objs "$BASEDIR" "*/lib/*.a"
+6
View File
@@ -31,6 +31,7 @@ OPTIONS="/tmp/options.$$.plist"
SIGNED="/tmp/signed.$$"
UTM_ENTITLEMENTS="/tmp/utm.$$.entitlements"
LAUNCHER_ENTITLEMENTS="/tmp/launcher.$$.entitlements"
RENDERER_ENTITLEMENTS="/tmp/renderer.$$.entitlements"
HELPER_ENTITLEMENTS="/tmp/helper.$$.entitlements"
CLI_ENTITLEMENTS="/tmp/cli.$$.entitlements"
INPUT_COPY="/tmp/UTM.$$.xcarchive"
@@ -71,11 +72,13 @@ EOL
if [ "$MODE" == "unsigned" ]; then
cp "$BASEDIR/../Platform/macOS/macOS-unsigned.entitlements" "$UTM_ENTITLEMENTS"
cp "$BASEDIR/../QEMULauncher/QEMULauncher-unsigned.entitlements" "$LAUNCHER_ENTITLEMENTS"
cp "$BASEDIR/../QEMURenderServer/QEMURenderServer-unsigned.entitlements" "$RENDERER_ENTITLEMENTS"
cp "$BASEDIR/../QEMUHelper/QEMUHelper-unsigned.entitlements" "$HELPER_ENTITLEMENTS"
cp "$BASEDIR/../utmctl/utmctl-unsigned.entitlements" "$CLI_ENTITLEMENTS"
else
cp "$BASEDIR/../Platform/macOS/macOS.entitlements" "$UTM_ENTITLEMENTS"
cp "$BASEDIR/../QEMULauncher/QEMULauncher.entitlements" "$LAUNCHER_ENTITLEMENTS"
cp "$BASEDIR/../QEMURenderServer/QEMURenderServer.entitlements" "$RENDERER_ENTITLEMENTS"
cp "$BASEDIR/../QEMUHelper/QEMUHelper.entitlements" "$HELPER_ENTITLEMENTS"
cp "$BASEDIR/../utmctl/utmctl.entitlements" "$CLI_ENTITLEMENTS"
@@ -92,7 +95,9 @@ fi
rm -rf "$INPUT_COPY"
cp -a "$INPUT" "$INPUT_COPY"
find "$INPUT_COPY/Products/Applications/UTM.app" -type d -path '*/Frameworks/*.framework' -exec codesign --force --sign - --timestamp=none \{\} \;
# nested bundles must be signed before the XPC service that contains them
codesign --force --sign - --entitlements "$LAUNCHER_ENTITLEMENTS" --timestamp=none --options runtime "$INPUT_COPY/Products/Applications/UTM.app/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMULauncher.app/Contents/MacOS/QEMULauncher"
codesign --force --sign - --entitlements "$RENDERER_ENTITLEMENTS" --timestamp=none --options runtime "$INPUT_COPY/Products/Applications/UTM.app/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMURenderServer.app/Contents/MacOS/QEMURenderServer"
codesign --force --sign - --entitlements "$HELPER_ENTITLEMENTS" --timestamp=none --options runtime "$INPUT_COPY/Products/Applications/UTM.app/Contents/XPCServices/QEMUHelper.xpc/Contents/MacOS/QEMUHelper"
codesign --force --sign - --entitlements "$CLI_ENTITLEMENTS" --timestamp=none --options runtime "$INPUT_COPY/Products/Applications/UTM.app/Contents/MacOS/utmctl"
codesign --force --sign - --entitlements "$UTM_ENTITLEMENTS" --timestamp=none --options runtime "$INPUT_COPY/Products/Applications/UTM.app/Contents/MacOS/UTM"
@@ -107,6 +112,7 @@ fi
rm "$OPTIONS"
rm "$UTM_ENTITLEMENTS"
rm "$LAUNCHER_ENTITLEMENTS"
rm "$RENDERER_ENTITLEMENTS"
rm "$HELPER_ENTITLEMENTS"
rm "$CLI_ENTITLEMENTS"
rm -rf "$INPUT_COPY"