diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a3fa9e77..b71b2b6f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -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
diff --git a/AGENTS.md b/AGENTS.md
index 3ab5f3bb..baf829ce 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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.
diff --git a/Build.xcconfig b/Build.xcconfig
index 6329f71e..4f7dbb1f 100644
--- a/Build.xcconfig
+++ b/Build.xcconfig
@@ -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))
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 00000000..47dc3e3d
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/Configuration/UTMQemuConfiguration+Arguments.swift b/Configuration/UTMQemuConfiguration+Arguments.swift
index a42c51a7..36ccfdaf 100644
--- a/Configuration/UTMQemuConfiguration+Arguments.swift
+++ b/Configuration/UTMQemuConfiguration+Arguments.swift
@@ -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
}
diff --git a/Platform/UTMDownloadSupportToolsTask.swift b/Platform/UTMDownloadSupportToolsTask.swift
index c7dabfec..1f278283 100644
--- a/Platform/UTMDownloadSupportToolsTask.swift
+++ b/Platform/UTMDownloadSupportToolsTask.swift
@@ -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")
}
diff --git a/Platform/iOS/Info.plist b/Platform/iOS/Info.plist
index b2aa267b..60a5f1e8 100644
--- a/Platform/iOS/Info.plist
+++ b/Platform/iOS/Info.plist
@@ -144,5 +144,7 @@
CADisableMinimumFrameDurationOnPhone
+ AppGroupIdentifier
+ group.$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM
diff --git a/Platform/iOS/iOS.entitlements b/Platform/iOS/iOS.entitlements
index 510563fb..00ab2f17 100644
--- a/Platform/iOS/iOS.entitlements
+++ b/Platform/iOS/iOS.entitlements
@@ -2,9 +2,13 @@
- com.apple.developer.kernel.increased-memory-limit
-
com.apple.developer.kernel.extended-virtual-addressing
+ com.apple.developer.kernel.increased-memory-limit
+
+ com.apple.security.application-groups
+
+ group.$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM
+
diff --git a/Platform/macOS/SettingsView.swift b/Platform/macOS/SettingsView.swift
index f3846eef..cf84742c 100644
--- a/Platform/macOS/SettingsView.swift
+++ b/Platform/macOS/SettingsView.swift
@@ -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.")
}
diff --git a/QEMUHelper/QEMUHelper.m b/QEMUHelper/QEMUHelper.m
index 2ac2b5a2..8732be81 100644
--- a/QEMUHelper/QEMUHelper.m
+++ b/QEMUHelper/QEMUHelper.m
@@ -128,7 +128,9 @@ static const int64_t kProcessTerminateTimeoutSeconds = 1;
task.standardOutput = standardOutput;
task.standardError = standardError;
NSMutableDictionary *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];
}
diff --git a/QEMURenderServer/QEMURenderServer-unsigned.entitlements b/QEMURenderServer/QEMURenderServer-unsigned.entitlements
new file mode 100644
index 00000000..f1a60e82
--- /dev/null
+++ b/QEMURenderServer/QEMURenderServer-unsigned.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.inherit
+
+
+
diff --git a/QEMURenderServer/QEMURenderServer.entitlements b/QEMURenderServer/QEMURenderServer.entitlements
new file mode 100644
index 00000000..794eada1
--- /dev/null
+++ b/QEMURenderServer/QEMURenderServer.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.inherit
+
+
+
diff --git a/Services/UTMQemuSystem.h b/Services/UTMQemuSystem.h
index 4cbc6a45..061e9194 100644
--- a/Services/UTMQemuSystem.h
+++ b/Services/UTMQemuSystem.h
@@ -26,7 +26,9 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) NSDictionary *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 launcherDelegate;
@property (nonatomic, nullable) QEMULogging *logging;
@property (nonatomic) BOOL hasDebugLog;
diff --git a/Services/UTMQemuSystem.m b/Services/UTMQemuSystem.m
index 1f414b84..008f8649 100644
--- a/Services/UTMQemuSystem.m
+++ b/Services/UTMQemuSystem.m
@@ -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"];
}
}
diff --git a/Services/UTMQemuSystemBackends.h b/Services/UTMQemuSystemBackends.h
index 9958ee41..db6eb716 100644
--- a/Services/UTMQemuSystemBackends.h
+++ b/Services/UTMQemuSystemBackends.h
@@ -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 */
diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift
index e5a62566..40df926e 100644
--- a/Services/UTMQemuVirtualMachine.swift
+++ b/Services/UTMQemuVirtualMachine.swift
@@ -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()
diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj
index 1a37efbb..e160d35b 100644
--- a/UTM.xcodeproj/project.pbxproj
+++ b/UTM.xcodeproj/project.pbxproj
@@ -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 = ""; };
CEA02A972436C7A30087E45F /* UTMLegacyQemuConfiguration+Networking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UTMLegacyQemuConfiguration+Networking.h"; sourceTree = ""; };
CEA02A982436C7A30087E45F /* UTMLegacyQemuConfiguration+Networking.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UTMLegacyQemuConfiguration+Networking.m"; sourceTree = ""; };
+ CEA3EDE93016810500AAA8E4 /* dxmt-native.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "dxmt-native.framework"; path = "$(SYSROOT_DIR)/Frameworks/dxmt-native.framework"; sourceTree = ""; };
+ CEA3EDEA3016810500AAA8E4 /* d3dmetal-native.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "d3dmetal-native.framework"; path = "$(SYSROOT_DIR)/Frameworks/d3dmetal-native.framework"; sourceTree = ""; };
+ 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 = ""; };
+ CEA3EE13301C15DD00AAA8E4 /* QEMURenderServer-unsigned.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "QEMURenderServer-unsigned.entitlements"; sourceTree = ""; };
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 = ""; };
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 = ""; };
@@ -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 = "";
@@ -3047,6 +3073,15 @@
path = Remote;
sourceTree = "";
};
+ CEA3EDFB30168C6300AAA8E4 /* QEMURenderServer */ = {
+ isa = PBXGroup;
+ children = (
+ CEA3EDFD30168C9900AAA8E4 /* QEMURenderServer.entitlements */,
+ CEA3EE13301C15DD00AAA8E4 /* QEMURenderServer-unsigned.entitlements */,
+ );
+ path = QEMURenderServer;
+ sourceTree = "";
+ };
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 = (
diff --git a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 1088cbfa..a834a08b 100644
--- a/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/UTM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -16,7 +16,7 @@
"location" : "https://github.com/utmapp/CocoaSpice.git",
"state" : {
"branch" : "main",
- "revision" : "52b1535824657354fc3089eab24f1827280f9143"
+ "revision" : "9873c6cacf866c8f47964e777b53f5c8f47364d7"
}
},
{
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-secure-vars.fd.bz2 b/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-secure-vars.fd.bz2
deleted file mode 100644
index db9447ff..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-secure-vars.fd.bz2 and /dev/null differ
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-vars.fd.bz2 b/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-vars.fd.bz2
deleted file mode 100644
index e072d875..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-arm-vars.fd.bz2 and /dev/null differ
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-secure-vars.fd.bz2 b/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-secure-vars.fd.bz2
deleted file mode 100644
index 9dfabccd..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-secure-vars.fd.bz2 and /dev/null differ
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-vars.fd.bz2 b/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-vars.fd.bz2
deleted file mode 100644
index 5c764345..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/edk2-i386-vars.fd.bz2 and /dev/null differ
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/m68k-declrom b/patches/data/qemu-10.0.2-utm/pc-bios/m68k-declrom
deleted file mode 100755
index 1dffe0a8..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/m68k-declrom and /dev/null differ
diff --git a/patches/data/qemu-10.0.2-utm/pc-bios/ppc-ndrvloader b/patches/data/qemu-10.0.2-utm/pc-bios/ppc-ndrvloader
deleted file mode 100644
index 46de171d..00000000
Binary files a/patches/data/qemu-10.0.2-utm/pc-bios/ppc-ndrvloader and /dev/null differ
diff --git a/patches/qemu-10.0.12-utm.patch b/patches/qemu-10.0.12-utm.patch
new file mode 100644
index 00000000..2cc6a8f2
--- /dev/null
+++ b/patches/qemu-10.0.12-utm.patch
@@ -0,0 +1,548 @@
+From 263d9536fa0f652024b90103b1c9fffd57cc2542 Mon Sep 17 00:00:00 2001
+From: osy
+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
+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
+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)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 tex = (id)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 ; 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
+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
+
diff --git a/patches/qemu-10.0.2-utm.patch b/patches/qemu-10.0.2-utm.patch
deleted file mode 100644
index 55e99028..00000000
--- a/patches/qemu-10.0.2-utm.patch
+++ /dev/null
@@ -1,4974 +0,0 @@
-From 594fe2f680f571dfe1d69281b803bdcbac925f7a Mon Sep 17 00:00:00 2001
-From: osy
-Date: Fri, 18 Jul 2025 13:29:23 -0700
-Subject: [PATCH 1/2] tcg/tcti: implement vector immediate shifts
-
-This now seems to be required as a result of the introduction of
-gen_gvec_rev{16,32,64} in 38f9950c8e0315d7b26803018a3f73d5f42e6703.
----
- tcg/aarch64-tcti/tcg-target-has.h | 2 +-
- tcg/aarch64-tcti/tcg-target-opc.h.inc | 1 +
- tcg/aarch64-tcti/tcg-target.c.inc | 45 +++++++++++++++++--
- tcg/aarch64-tcti/tcti-gadget-gen.py | 63 +++++++++++++++++++++++++--
- 4 files changed, 103 insertions(+), 8 deletions(-)
-
-diff --git a/tcg/aarch64-tcti/tcg-target-has.h b/tcg/aarch64-tcti/tcg-target-has.h
-index 8e39891c02..67b50fcdea 100644
---- a/tcg/aarch64-tcti/tcg-target-has.h
-+++ b/tcg/aarch64-tcti/tcg-target-has.h
-@@ -84,7 +84,7 @@
- #define TCG_TARGET_HAS_roti_vec 0
- #define TCG_TARGET_HAS_rots_vec 0
- #define TCG_TARGET_HAS_rotv_vec 0
--#define TCG_TARGET_HAS_shi_vec 0
-+#define TCG_TARGET_HAS_shi_vec 1
- #define TCG_TARGET_HAS_shs_vec 0
- #define TCG_TARGET_HAS_shv_vec 1
- #define TCG_TARGET_HAS_mul_vec 1
-diff --git a/tcg/aarch64-tcti/tcg-target-opc.h.inc b/tcg/aarch64-tcti/tcg-target-opc.h.inc
-index 88cf2bc53d..5382315c41 100644
---- a/tcg/aarch64-tcti/tcg-target-opc.h.inc
-+++ b/tcg/aarch64-tcti/tcg-target-opc.h.inc
-@@ -12,3 +12,4 @@
- */
-
- DEF(aa64_sshl_vec, 1, 2, 0, TCG_OPF_VECTOR)
-+DEF(aa64_sli_vec, 1, 2, 1, TCG_OPF_VECTOR)
-diff --git a/tcg/aarch64-tcti/tcg-target.c.inc b/tcg/aarch64-tcti/tcg-target.c.inc
-index 7b1186cd01..8b78abe4bb 100644
---- a/tcg/aarch64-tcti/tcg-target.c.inc
-+++ b/tcg/aarch64-tcti/tcg-target.c.inc
-@@ -217,6 +217,8 @@ tcg_target_op_def(TCGOpcode op, TCGType type, unsigned flags)
- return C_O1_I2(w, w, w);
- case INDEX_op_bitsel_vec:
- return C_O1_I3(w, w, w, w);
-+ case INDEX_op_aa64_sli_vec:
-+ return C_O1_I2(w, 0, w);
-
- default:
- return C_NotImplemented;
-@@ -490,6 +492,13 @@ static void tcg_out_ternary_gadget(TCGContext *s, const void *gadget_base[TCG_TA
- tcg_out_gadget(s, gadget_base[reg0][reg1][reg2]);
- }
-
-+
-+/* Write gadget pointer (three registers, last is immediate value). */
-+static void tcg_out_ternary_immediate_gadget(TCGContext *s, const void *gadget_base[TCG_TARGET_GP_REGS][TCG_TARGET_GP_REGS][TCTI_GADGET_IMMEDIATE_ARRAY_LEN], unsigned reg0, unsigned reg1, unsigned reg2)
-+{
-+ tcg_out_gadget(s, gadget_base[reg0][reg1][reg2]);
-+}
-+
- /***************************
- * TCG Scalar Operations *
- ***************************/
-@@ -1558,13 +1567,18 @@ static void tcg_out_nop_fill(tcg_insn_unit *p, int count)
- tcg_out_sized_vector_gadget_no64(s, name, ternary, vece, a, b, c)
-
-
--#define tcg_out_ternary_vector_gadget_with_scalar(s, name, is_scalar, vece, a, b, c) \
-+#define tcg_out_sized_gadget_with_scalar(s, name, arity, is_scalar, vece, args...) \
- if (is_scalar) { \
-- tcg_out_ternary_gadget(s, gadget_ ## name ## _scalar, w0, w1, w2); \
-+ tcg_out_ ## arity ## _gadget(s, gadget_ ## name ## _scalar, args); \
- } else { \
-- tcg_out_ternary_vector_gadget(s, name, vece, w0, w1, w2); \
-+ tcg_out_sized_vector_gadget(s, name, arity, vece, args); \
- }
-
-+#define tcg_out_ternary_vector_gadget_with_scalar(s, name, is_scalar, vece, a, b, c) \
-+ tcg_out_sized_gadget_with_scalar(s, name, ternary, is_scalar, vece, a, b, c)
-+
-+#define tcg_out_ternary_immediate_vector_gadget_with_scalar(s, name, is_scalar, vece, a, b, c) \
-+ tcg_out_sized_gadget_with_scalar(s, name, ternary_immediate, is_scalar, vece, a, b, c)
-
- /* Return true if v16 is a valid 16-bit shifted immediate. */
- static bool is_shimm16(uint16_t v16, int *cmode, int *imm8)
-@@ -1765,6 +1779,20 @@ static void tcg_out_vec_op(TCGContext *s, TCGOpcode opc, unsigned vecl, unsigned
- break;
- }
-
-+ /* inhibit compiler warning because we use imm as a register */
-+ case INDEX_op_shli_vec:
-+ tcg_out_ternary_immediate_vector_gadget_with_scalar(s, shl, is_scalar, vece, w0, w1, r2);
-+ break;
-+ case INDEX_op_shri_vec:
-+ tcg_out_ternary_immediate_vector_gadget_with_scalar(s, ushr, is_scalar, vece, w0, w1, r2 - 1);
-+ break;
-+ case INDEX_op_sari_vec:
-+ tcg_out_ternary_immediate_vector_gadget_with_scalar(s, sshr, is_scalar, vece, w0, w1, r2 - 1);
-+ break;
-+ case INDEX_op_aa64_sli_vec:
-+ tcg_out_ternary_immediate_vector_gadget_with_scalar(s, sli, is_scalar, vece, w0, w2, r3);
-+ break;
-+
- case INDEX_op_mov_vec: /* Always emitted via tcg_out_mov. */
- case INDEX_op_dup_vec: /* Always emitted via tcg_out_dup_vec. */
- default:
-@@ -1787,6 +1815,9 @@ int tcg_can_emit_vec_op(TCGOpcode opc, TCGType type, unsigned vece)
- case INDEX_op_abs_vec:
- case INDEX_op_not_vec:
- case INDEX_op_cmp_vec:
-+ case INDEX_op_shli_vec:
-+ case INDEX_op_shri_vec:
-+ case INDEX_op_sari_vec:
- case INDEX_op_ssadd_vec:
- case INDEX_op_sssub_vec:
- case INDEX_op_usadd_vec:
-@@ -1827,6 +1858,14 @@ void tcg_expand_vec_op(TCGOpcode opc, TCGType type, unsigned vece,
- va_end(va);
-
- switch (opc) {
-+ case INDEX_op_rotli_vec:
-+ t1 = tcg_temp_new_vec(type);
-+ tcg_gen_shri_vec(vece, t1, v1, -a2 & ((8 << vece) - 1));
-+ vec_gen_4(INDEX_op_aa64_sli_vec, type, vece,
-+ tcgv_vec_arg(v0), tcgv_vec_arg(t1), tcgv_vec_arg(v1), a2);
-+ tcg_temp_free_vec(t1);
-+ break;
-+
- case INDEX_op_shrv_vec:
- case INDEX_op_sarv_vec:
- /* Right shifts are negative left shifts for AArch64. */
-diff --git a/tcg/aarch64-tcti/tcti-gadget-gen.py b/tcg/aarch64-tcti/tcti-gadget-gen.py
-index 275c4ba943..ebed824500 100755
---- a/tcg/aarch64-tcti/tcti-gadget-gen.py
-+++ b/tcg/aarch64-tcti/tcti-gadget-gen.py
-@@ -113,7 +113,7 @@ def simple(name, *lines, export=True):
-
-
-
--def with_register_substitutions(name, substitutions, *lines, immediate_range=range(0)):
-+def with_register_substitutions(name, substitutions, *lines, immediate_range=range(0), filter=lambda p: False):
- """ Generates a collection of gadgtes with register substitutions. """
-
- def _expand_op1_immediate(num):
-@@ -166,6 +166,10 @@ def substitutions_for_letter(letter, number, line):
-
- # For each permutation...
- for permutation in permutations:
-+ # Filter any invalid combination
-+ if filter(permutation):
-+ continue
-+
- new_lines = lines
-
- # Replace each placeholder element with its proper value...
-@@ -212,9 +216,9 @@ def with_dnm(name, *lines):
- print("};", file=c_file)
-
-
--def with_dn_immediate(name, *lines, immediate_range):
-+def with_dn_immediate(name, *lines, immediate_range, filter=lambda m: False):
- """ Generates a collection of gadgets with substitutions for Xd, Xn, and Xm, and equivalents. """
-- with_register_substitutions(name, ["d", "n"], *lines, immediate_range=immediate_range)
-+ with_register_substitutions(name, ["d", "n"], *lines, immediate_range=immediate_range, filter=lambda p: filter(p[-1]))
-
- # Fetch the files we'll be using for output.
- c_file, h_file = _get_output_files()
-@@ -236,7 +240,10 @@ def with_dn_immediate(name, *lines, immediate_range):
-
- # M array
- for i in immediate_range:
-- print(f"gadget_{name}_arg{d}_arg{n}_arg{i}", end=", ", file=c_file)
-+ if filter(i):
-+ print(f"(void *)0", end=", ", file=c_file)
-+ else:
-+ print(f"gadget_{name}_arg{d}_arg{n}_arg{i}", end=", ", file=c_file)
-
- print("},", file=c_file)
- print("\t},", file=c_file)
-@@ -625,6 +632,24 @@ def do_size_replacement(line, size):
- sized_lines = (scalar,)
- with_dnm(f"{name}_scalar", *sized_lines)
-
-+def vector_dn_immediate(name, *lines, scalar=None, immediate_range, omit_sizes=(), filter=lambda s, m: False):
-+ """ Creates a set of gadgets for every size of a given vector op. Accepts 'S' as a size placeholder. """
-+
-+ def do_size_replacement(line, size):
-+ return line.replace(".S", f".{size}")
-+
-+ # Create a variant for each size, replacing any placeholders.
-+ for size in VECTOR_SIZES:
-+ if size in omit_sizes:
-+ continue
-+
-+ sized_lines = (do_size_replacement(line, size) for line in lines)
-+ with_dn_immediate(f"{name}_{size}", *sized_lines, immediate_range=immediate_range, filter=lambda m: filter(size, m))
-+
-+ if scalar:
-+ if isinstance(scalar, str):
-+ sized_lines = (scalar,)
-+ with_dn_immediate(f"{name}_scalar", *sized_lines, immediate_range=immediate_range, filter=lambda m: filter(None, m))
-
- def vector_math_dnm(name, operation):
- """ Generates a collection of gadgets for vector math instructions. """
-@@ -647,6 +672,9 @@ def vector_logic_dnm(name, operation):
- with_dnm(f"{name}_d", f"{operation} Vd.8b, Vn.8b, Vm.8b")
- with_dnm(f"{name}_q", f"{operation} Vd.16b, Vn.16b, Vm.16b")
-
-+def vector_math_dn_immediate(name, operation, immediate_range, filter=lambda x: False):
-+ """ Generates a collection of gadgets for vector math instructions. """
-+ vector_dn_immediate(name, f"{operation} Vd.S, Vn.S, #Ii", scalar=f"{operation} Dd, Dn, #Ii", immediate_range=immediate_range, filter=filter)
-
- #
- # Gadget definitions.
-@@ -1088,6 +1116,33 @@ def vector_logic_dnm(name, operation):
- vector_math_dnm("shlv", "ushl")
- vector_math_dnm("sshl", "sshl")
-
-+def filter_shl(size, imm):
-+ match size:
-+ case '16b': return imm >= 8
-+ case '8b': return imm >= 8
-+ case '4h': return imm >= 16
-+ case '8h': return imm >= 16
-+ case '2s': return imm >= 32
-+ case '4s': return imm >= 32
-+ return False
-+
-+def filter_shr(size, imm):
-+ if imm == 0:
-+ return True
-+ match size:
-+ case '16b': return imm > 8
-+ case '8b': return imm > 8
-+ case '4h': return imm > 16
-+ case '8h': return imm > 16
-+ case '2s': return imm > 32
-+ case '4s': return imm > 32
-+ return False
-+
-+vector_math_dn_immediate("shl", "shl", immediate_range=range(64), filter=filter_shl)
-+vector_math_dn_immediate("ushr", "ushr", immediate_range=range(1,65), filter=filter_shr)
-+vector_math_dn_immediate("sshr", "sshr", immediate_range=range(1,65), filter=filter_shr)
-+vector_math_dn_immediate("sli", "sli", immediate_range=range(64), filter=filter_shl)
-+
- vector_dnm("cmeq", "cmeq Vd.S, Vn.S, Vm.S", scalar="cmeq Dd, Dn, Dm")
- vector_dnm("cmgt", "cmgt Vd.S, Vn.S, Vm.S", scalar="cmgt Dd, Dn, Dm")
- vector_dnm("cmge", "cmge Vd.S, Vn.S, Vm.S", scalar="cmge Dd, Dn, Dm")
---
-2.41.0
-
-From 35ba52817dbea01821b429737e989bc54a14d411 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Tue, 22 Jul 2025 10:13:34 -0700
-Subject: [PATCH 2/2] tcg/tcti: fix goto as first instruction
-
----
- accel/tcg/tcg-accel-ops.c | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c
-index 0e8c4c1c67..e07f60357f 100644
---- a/accel/tcg/tcg-accel-ops.c
-+++ b/accel/tcg/tcg-accel-ops.c
-@@ -68,6 +68,7 @@ void tcg_cpu_init_cflags(CPUState *cpu, bool parallel)
- // GOTO_PTR is too complex to emit a simple gadget for.
- // We'll let C handle it, since the overhead is similar.
- cflags |= CF_NO_GOTO_PTR;
-+ cpu->cflags_next_tb = CF_NO_GOTO_PTR;
- #endif
- tcg_cflags_set(cpu, cflags);
- }
---
-2.41.0
-
-From 344a5a3cbe3df0c373743969493afe7d1c4fb4d6 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sat, 2 Aug 2025 19:22:04 -0700
-Subject: [PATCH] pc-bios: add classicvirtio drivers for m68k/ppc
-
----
- pc-bios/m68k-declrom | Bin 0 -> 106496 bytes
- pc-bios/meson.build | 2 ++
- pc-bios/ppc-ndrvloader | Bin 0 -> 191172 bytes
- 3 files changed, 2 insertions(+)
- create mode 100755 pc-bios/m68k-declrom
- create mode 100644 pc-bios/ppc-ndrvloader
-
-diff --git a/pc-bios/meson.build b/pc-bios/meson.build
-index 9fb9659c45..63e10cc6df 100644
---- a/pc-bios/meson.build
-+++ b/pc-bios/meson.build
-@@ -85,6 +85,8 @@ blobs = [
- 'npcm8xx_bootrom.bin',
- 'vof.bin',
- 'vof-nvram.bin',
-+ 'm68k-declrom',
-+ 'ppc-ndrvloader',
- ]
-
- dtc = find_program('dtc', required: false)
-
-From a172998c2f8bcbd29afeb8cab9b97e43ef3a22b5 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 10 Aug 2025 21:54:34 -0700
-Subject: [PATCH] pc-bios: use 2023 Microsoft UEFI certificates
-
-Restore non-secure vars variants as well.
----
- pc-bios/edk2-arm-secure-vars.fd.bz2 | Bin 0 -> 12654 bytes
- pc-bios/edk2-arm-vars.fd.bz2 | Bin 6710 -> 595 bytes
- pc-bios/edk2-i386-secure-vars.fd.bz2 | Bin 0 -> 12986 bytes
- pc-bios/edk2-i386-vars.fd.bz2 | Bin 7727 -> 612 bytes
- pc-bios/meson.build | 2 ++
- 5 files changed, 2 insertions(+)
- create mode 100644 pc-bios/edk2-arm-secure-vars.fd.bz2
- create mode 100644 pc-bios/edk2-i386-secure-vars.fd.bz2
-
-diff --git a/pc-bios/meson.build b/pc-bios/meson.build
-index 63e10cc6df..1e1b553795 100644
---- a/pc-bios/meson.build
-+++ b/pc-bios/meson.build
-@@ -4,11 +4,13 @@ if unpack_edk2_blobs
- 'edk2-aarch64-code.fd',
- 'edk2-aarch64-secure-code.fd',
- 'edk2-arm-code.fd',
-+ 'edk2-arm-secure-vars.fd',
- 'edk2-arm-vars.fd',
- 'edk2-riscv-code.fd',
- 'edk2-riscv-vars.fd',
- 'edk2-i386-code.fd',
- 'edk2-i386-secure-code.fd',
-+ 'edk2-i386-secure-vars.fd',
- 'edk2-i386-vars.fd',
- 'edk2-x86_64-code.fd',
- 'edk2-x86_64-secure-code.fd',
---
-2.41.0
-
-From 0f1d6606c28d0ae81a1b311972c5c54e5e867bf0 Mon Sep 17 00:00:00 2001
-From: Mark Cave-Ayland
-Date: Wed, 11 Jun 2025 14:03:15 +0100
-Subject: [PATCH] target/i386: fix TB exit logic in gen_movl_seg() when writing
- to SS
-
-Before commit e54ef98c8a ("target/i386: do not trigger IRQ shadow for LSS"), any
-write to SS in gen_movl_seg() would cause a TB exit. The changes introduced by
-this commit were intended to restrict the DISAS_EOB_INHIBIT_IRQ exit to the case
-where inhibit_irq is true, but missed that a DISAS_EOB_NEXT exit can still be
-required when writing to SS and inhibit_irq is false.
-
-Comparing the PE(s) && !VM86(s) section with the logic in x86_update_hflags(), we
-can see that the DISAS_EOB_NEXT exit is still required for the !CODE32 case when
-writing to SS in gen_movl_seg() because any change to the SS flags can affect
-hflags. Similarly we can see that the existing CODE32 case is still correct since
-a change to any of DS, ES and SS can affect hflags. Finally for the
-gen_op_movl_seg_real() case an explicit TB exit is not needed because the segment
-register selector does not affect hflags.
-
-Update the logic in gen_movl_seg() so that a write to SS with inhibit_irq set to
-false where PE(s) && !VM86(s) will generate a DISAS_EOB_NEXT exit along with the
-inline comment. This has the effect of allowing Win98SE to boot in QEMU once
-again.
-
-Signed-off-by: Mark Cave-Ayland
-Fixes: e54ef98c8a ("target/i386: do not trigger IRQ shadow for LSS")
-Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2987
-Link: https://lore.kernel.org/r/20250611130315.383151-1-mark.cave-ayland@ilande.co.uk
-Reviewed-by: Peter Maydell
-Signed-off-by: Paolo Bonzini
----
- target/i386/tcg/translate.c | 7 +++++--
- 1 file changed, 5 insertions(+), 2 deletions(-)
-
-diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c
-index 0fcddc2ec0..0cb87d0201 100644
---- a/target/i386/tcg/translate.c
-+++ b/target/i386/tcg/translate.c
-@@ -2033,8 +2033,11 @@ static void gen_movl_seg(DisasContext *s, X86Seg seg_reg, TCGv src, bool inhibit
- tcg_gen_trunc_tl_i32(sel, src);
- gen_helper_load_seg(tcg_env, tcg_constant_i32(seg_reg), sel);
-
-- /* For move to DS/ES/SS, the addseg or ss32 flags may change. */
-- if (CODE32(s) && seg_reg < R_FS) {
-+ /*
-+ * For moves to SS, the SS32 flag may change. For CODE32 only, changes
-+ * to SS, DS and ES may change the ADDSEG flags.
-+ */
-+ if (seg_reg == R_SS || (CODE32(s) && seg_reg < R_FS)) {
- s->base.is_jmp = DISAS_EOB_NEXT;
- }
- } else {
---
-2.41.0
-
-From a3955f90f898cfa8efcdeeab285324dcbb033b31 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Mon, 11 Aug 2025 22:24:52 -0700
-Subject: [PATCH] hw/i386/pc: no floppy when defaults disabled
-
----
- hw/i386/pc_piix.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
-index 6c91e2d292..86978f4671 100644
---- a/hw/i386/pc_piix.c
-+++ b/hw/i386/pc_piix.c
-@@ -465,7 +465,7 @@ static void pc_i440fx_machine_options(MachineClass *m)
- m->default_machine_opts = "firmware=bios-256k.bin";
- m->default_display = "std";
- m->default_nic = "e1000";
-- m->no_floppy = !module_object_class_by_name(TYPE_ISA_FDC);
-+ m->no_floppy = !defaults_enabled() || !module_object_class_by_name(TYPE_ISA_FDC);
- m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);
- machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
- machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
-@@ -811,7 +811,7 @@ static void isapc_machine_options(MachineClass *m)
- pcmc->has_reserved_memory = false;
- m->default_nic = "ne2k_isa";
- m->default_cpu_type = X86_CPU_TYPE_NAME("486");
-- m->no_floppy = !module_object_class_by_name(TYPE_ISA_FDC);
-+ m->no_floppy = !defaults_enabled() || !module_object_class_by_name(TYPE_ISA_FDC);
- m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);
- }
-
---
-2.41.0
-
-From 60fb87f41c44a4fd76d6e4538d1746a6c3be162c Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 14 Sep 2025 00:55:51 -0700
-Subject: [PATCH] tcg: new JIT workaround for iOS 26
-
-We map the JIT region originally as RX and also mirror map
-it as RX. Then we use attached debugger to flag the mirror
-mapping as debugger owned. Finally, we change the original
-map to RW which should not invalidate code-signing as the
-mirror is debugger owned.
-
-Thanks to @JJTech0130 for this workaround.
----
- tcg/region.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++--
- 1 file changed, 62 insertions(+), 2 deletions(-)
-
-diff --git a/tcg/region.c b/tcg/region.c
-index 70996b5ab1..a36c692bc3 100644
---- a/tcg/region.c
-+++ b/tcg/region.c
-@@ -623,14 +623,57 @@ extern kern_return_t mach_vm_remap(vm_map_t target_task,
- vm_prot_t *max_protection,
- vm_inherit_t inheritance);
-
-+#include
-+#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
-+#include
-+#include
-+static int is_debugger_attached(void)
-+{
-+ int mib[4];
-+ struct kinfo_proc info;
-+ size_t size;
-+
-+ info.kp_proc.p_flag = 0;
-+
-+ /* Initialize MIB for sysctl call */
-+ mib[0] = CTL_KERN;
-+ mib[1] = KERN_PROC;
-+ mib[2] = KERN_PROC_PID;
-+ mib[3] = getpid();
-+
-+ size = sizeof(info);
-+
-+ if (sysctl(mib, 4, &info, &size, NULL, 0) == -1) {
-+ return 0; // sysctl failed, be conservative
-+ }
-+
-+ /* P_TRACED means the process is being debugged */
-+ return (info.kp_proc.p_flag & P_TRACED) != 0;
-+}
-+
-+static void break_prepare_jit_region(mach_vm_address_t addr, size_t len)
-+{
-+ asm ("mov x0, %0\n"
-+ "mov x1, %1\n"
-+ "brk #0x69" :: "r" (addr), "r" (len) : "x0", "x1");
-+}
-+#endif
-+
- static int alloc_code_gen_buffer_splitwx_vmremap(size_t size, Error **errp)
- {
- kern_return_t ret;
- mach_vm_address_t buf_rw, buf_rx;
- vm_prot_t cur_prot, max_prot;
-+ int orig_prot = PROT_READ | PROT_WRITE;
-+
-+#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
-+ /* iOS 26 with TXM requires new workaround*/
-+ if (__builtin_available(iOS 26, visionOS 26, watchOS 26, tvOS 26, *)) {
-+ orig_prot = PROT_READ | PROT_EXEC;
-+ }
-+#endif
-
-- /* Map the read-write portion via normal anon memory. */
-- if (!alloc_code_gen_buffer_anon(size, PROT_READ | PROT_WRITE,
-+ if (!alloc_code_gen_buffer_anon(size, orig_prot,
- MAP_PRIVATE | MAP_ANONYMOUS, errp)) {
- return -1;
- }
-@@ -662,6 +705,23 @@ static int alloc_code_gen_buffer_splitwx_vmremap(size_t size, Error **errp)
- return -1;
- }
-
-+#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
-+ if (__builtin_available(iOS 26, visionOS 26, watchOS 26, tvOS 26, *)) {
-+ if (is_debugger_attached()) {
-+ /* let debugger modify the page permission */
-+ break_prepare_jit_region(buf_rx, size);
-+ }
-+
-+ /* finally mark the read-write portion as RW */
-+ if (mprotect((void *)buf_rw, size, PROT_READ | PROT_WRITE) != 0) {
-+ error_setg_errno(errp, errno, "mprotect for jit splitwx (rw)");
-+ munmap((void *)buf_rx, size);
-+ munmap((void *)buf_rw, size);
-+ return -1;
-+ }
-+ }
-+#endif
-+
- tcg_splitwx_diff = buf_rx - buf_rw;
- return PROT_READ | PROT_WRITE;
- }
---
-2.41.0
-
-From 338fdd995e8f665edbdb1acd0f1b63f2ca3c266c Mon Sep 17 00:00:00 2001
-From: osy
-Date: Fri, 2 Jan 2026 12:41:52 -0800
-Subject: [PATCH] ui/spice-display: fix typo in spice_iosurface_resize
-
----
- ui/spice-display.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index b47722ea4f..c7688256e9 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -912,8 +912,8 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
- {
- if (ssd->iosurface) {
-- if (IOSurfaceGetHeight(ssd->iosurface) != width ||
-- IOSurfaceGetWidth(ssd->iosurface) != height) {
-+ if (IOSurfaceGetHeight(ssd->iosurface) != height ||
-+ IOSurfaceGetWidth(ssd->iosurface) != width) {
- spice_iosurface_destroy(ssd);
- return spice_iosurface_create(ssd, width, height);
- } else {
---
-2.50.1 (Apple Git-155)
-
-From cde37cd6ddc4fa4e6ffe6151ef182147d676b87a Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sat, 8 Nov 2025 16:18:44 -0800
-Subject: [PATCH 1/9] Revert "Use virgl_renderer_borrow_texture_for_scanout"
-
-This reverts commit 8b9e3062a4c2e243f0f80482385ebccbb8357f71.
----
- hw/display/virtio-gpu-virgl.c | 105 ++++++++--------------------------
- include/ui/console.h | 25 ++++----
- include/ui/gtk.h | 14 +++--
- include/ui/sdl2.h | 7 ++-
- include/ui/spice-display.h | 4 +-
- ui/console.c | 25 +++++---
- ui/dbus-console.c | 7 ++-
- ui/dbus-listener.c | 23 +-------
- ui/egl-headless.c | 23 +-------
- ui/gtk-egl.c | 27 ++-------
- ui/gtk-gl-area.c | 24 +-------
- ui/sdl2-gl.c | 17 ++----
- ui/spice-display.c | 36 +++++-------
- 13 files changed, 107 insertions(+), 230 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index d78c2e4892..145a0b3879 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -396,87 +396,11 @@ static void virgl_cmd_resource_flush(VirtIOGPU *g,
- }
- }
-
--static GLuint virgl_borrow_texture_for_scanout(uint32_t id, bool *y_0_top,
-- uint32_t *width,
-- uint32_t *height,
-- void **d3d_tex2d)
--{
-- struct virgl_renderer_texture_info info;
-- int ret;
--
-- memset(&info, 0, sizeof(info));
--
-- ret = virgl_renderer_borrow_texture_for_scanout(id, &info);
-- if (ret == -1) {
-- return 0;
-- }
--
-- if (y_0_top) {
-- *y_0_top = info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP;
-- }
--
-- if (width) {
-- *width = info.width;
-- }
--
-- if (height) {
-- *height = info.height;
-- }
--
-- if (d3d_tex2d) {
-- *d3d_tex2d = NULL;
-- }
--
-- return info.tex_id;
--}
--
--#if VIRGL_VERSION_MAJOR >= 1
--static GLuint virgl_borrow_d3d_info_for_scanout(uint32_t id, bool *y_0_top,
-- uint32_t *width,
-- uint32_t *height,
-- void **d3d_tex2d)
--{
-- int ret;
-- struct virgl_renderer_resource_info info;
-- struct virgl_renderer_resource_info_ext ext;
-- void *d3d_tex2d = NULL;
--
-- memset(&ext, 0, sizeof(ext));
--
-- ret = virgl_renderer_resource_get_info_ext(id, &ext);
-- info = ext.base;
-- d3d_tex2d = ext.d3d_tex2d;
-- if (ret) {
-- qemu_log_mask(LOG_GUEST_ERROR,
-- "%s: illegal resource specified %d\n",
-- __func__, id);
-- return 0;
-- }
--
-- if (y_0_top) {
-- *y_0_top = info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP;
-- }
--
-- if (width) {
-- *width = info.width;
-- }
--
-- if (height) {
-- *height = info.height;
-- }
--
-- if (d3d_tex2d) {
-- *d3d_tex2d = ext.d3d_tex2d;
-- }
--
-- return info.tex_id;
--}
--#endif
--
- static void virgl_cmd_set_scanout(VirtIOGPU *g,
- struct virtio_gpu_ctrl_command *cmd)
- {
- struct virtio_gpu_set_scanout ss;
-+ int ret;
-
- VIRTIO_GPU_FILL_CMD(ss);
- trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id,
-@@ -491,18 +415,35 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
- g->parent_obj.enable = 1;
-
- if (ss.resource_id && ss.r.width && ss.r.height) {
-- DisplayGLTextureBorrower borrower;
-+ struct virgl_renderer_resource_info info;
-+ void *d3d_tex2d = NULL;
-+
- #if VIRGL_VERSION_MAJOR >= 1
-- borrower = virgl_borrow_d3d_info_for_scanout;
-+ struct virgl_renderer_resource_info_ext ext;
-+ memset(&ext, 0, sizeof(ext));
-+ ret = virgl_renderer_resource_get_info_ext(ss.resource_id, &ext);
-+ info = ext.base;
-+ d3d_tex2d = ext.d3d_tex2d;
- #else
-- borrower = virgl_borrow_texture_for_scanout;
-+ memset(&info, 0, sizeof(info));
-+ ret = virgl_renderer_resource_get_info(ss.resource_id, &info);
- #endif
-+ if (ret) {
-+ qemu_log_mask(LOG_GUEST_ERROR,
-+ "%s: illegal resource specified %d\n",
-+ __func__, ss.resource_id);
-+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
-+ return;
-+ }
- qemu_console_resize(g->parent_obj.scanout[ss.scanout_id].con,
- ss.r.width, ss.r.height);
- virgl_renderer_force_ctx_0();
- dpy_gl_scanout_texture(
-- g->parent_obj.scanout[ss.scanout_id].con, ss.resource_id,
-- borrower, ss.r.x, ss.r.y, ss.r.width, ss.r.height);
-+ g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
-+ info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
-+ info.width, info.height,
-+ ss.r.x, ss.r.y, ss.r.width, ss.r.height,
-+ d3d_tex2d);
- } else {
- dpy_gfx_replace_surface(
- g->parent_obj.scanout[ss.scanout_id].con, NULL);
-diff --git a/include/ui/console.h b/include/ui/console.h
-index 8717931ed3..46b3128185 100644
---- a/include/ui/console.h
-+++ b/include/ui/console.h
-@@ -131,18 +131,16 @@ struct QemuConsoleClass {
- ObjectClass parent_class;
- };
-
--typedef uint32_t (* DisplayGLTextureBorrower)(uint32_t id, bool *y_0_top,
-- uint32_t *width,
-- uint32_t *height,
-- void **d3d_tex2d);
--
- typedef struct ScanoutTexture {
- uint32_t backing_id;
-- DisplayGLTextureBorrower backing_borrow;
-+ bool backing_y_0_top;
-+ uint32_t backing_width;
-+ uint32_t backing_height;
- uint32_t x;
- uint32_t y;
- uint32_t width;
- uint32_t height;
-+ void *d3d_tex2d;
- } ScanoutTexture;
-
- typedef struct QemuUIInfo {
-@@ -242,9 +240,12 @@ typedef struct DisplayChangeListenerOps {
- /* required if GL */
- void (*dpy_gl_scanout_texture)(DisplayChangeListener *dcl,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h);
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d);
- /* optional (default to true if has dpy_gl_scanout_dmabuf) */
- bool (*dpy_has_dmabuf)(DisplayChangeListener *dcl);
- /* optional */
-@@ -324,9 +325,11 @@ bool dpy_gfx_check_format(QemuConsole *con,
- pixman_format_code_t format);
-
- void dpy_gl_scanout_disable(QemuConsole *con);
--void dpy_gl_scanout_texture(QemuConsole *con, uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-- uint32_t x, uint32_t y, uint32_t w, uint32_t h);
-+void dpy_gl_scanout_texture(QemuConsole *con,
-+ uint32_t backing_id, bool backing_y_0_top,
-+ uint32_t backing_width, uint32_t backing_height,
-+ uint32_t x, uint32_t y, uint32_t w, uint32_t h,
-+ void *d3d_tex2d);
- void dpy_gl_scanout_dmabuf(QemuConsole *con,
- QemuDmaBuf *dmabuf);
- void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
-diff --git a/include/ui/gtk.h b/include/ui/gtk.h
-index bfd1075b6d..aa3d637029 100644
---- a/include/ui/gtk.h
-+++ b/include/ui/gtk.h
-@@ -171,9 +171,12 @@ QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
- void gd_egl_scanout_disable(DisplayChangeListener *dcl);
- void gd_egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h);
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d);
- void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
- QemuDmaBuf *dmabuf);
- void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
-@@ -205,9 +208,12 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
- QemuDmaBuf *dmabuf);
- void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h);
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d);
- void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
- void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h);
-diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
-index c079f9c5e0..dbe6e3d973 100644
---- a/include/ui/sdl2.h
-+++ b/include/ui/sdl2.h
-@@ -88,9 +88,12 @@ int sdl2_gl_make_context_current(DisplayGLCtx *dgc,
- void sdl2_gl_scanout_disable(DisplayChangeListener *dcl);
- void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h);
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d);
- void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h);
-
-diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
-index 6f8305888d..4b9121bfb5 100644
---- a/include/ui/spice-display.h
-+++ b/include/ui/spice-display.h
-@@ -141,8 +141,8 @@ struct SimpleSpiceDisplay {
- #if defined(CONFIG_ANGLE)
- EGLSurface esurface;
- egl_fb iosurface_fb;
-- DisplayGLTextureBorrower backing_borrow;
-- uint32_t backing_id;
-+ GLuint tex_id;
-+ bool y_0_top;
- #endif
- bool render_cursor;
-
-diff --git a/ui/console.c b/ui/console.c
-index 4a0d205d73..480cd63f90 100644
---- a/ui/console.c
-+++ b/ui/console.c
-@@ -288,11 +288,14 @@ static void displaychangelistener_display_console(DisplayChangeListener *dcl,
- dcl->ops->dpy_gl_scanout_texture) {
- dcl->ops->dpy_gl_scanout_texture(dcl,
- con->scanout.texture.backing_id,
-- con->scanout.texture.backing_borrow,
-+ con->scanout.texture.backing_y_0_top,
-+ con->scanout.texture.backing_width,
-+ con->scanout.texture.backing_height,
- con->scanout.texture.x,
- con->scanout.texture.y,
- con->scanout.texture.width,
-- con->scanout.texture.height);
-+ con->scanout.texture.height,
-+ con->scanout.texture.d3d_tex2d);
- }
- }
-
-@@ -1015,25 +1018,31 @@ void dpy_gl_scanout_disable(QemuConsole *con)
-
- void dpy_gl_scanout_texture(QemuConsole *con,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t width, uint32_t height)
-+ uint32_t width, uint32_t height,
-+ void *d3d_tex2d)
- {
- DisplayState *s = con->ds;
- DisplayChangeListener *dcl;
-
- con->scanout.kind = SCANOUT_TEXTURE;
- con->scanout.texture = (ScanoutTexture) {
-- backing_id, backing_borrow,
-- x, y, width, height
-+ backing_id, backing_y_0_top, backing_width, backing_height,
-+ x, y, width, height, d3d_tex2d,
- };
- QLIST_FOREACH(dcl, &s->listeners, next) {
- if (con != dcl->con) {
- continue;
- }
- if (dcl->ops->dpy_gl_scanout_texture) {
-- dcl->ops->dpy_gl_scanout_texture(dcl, backing_id, backing_borrow,
-- x, y, width, height);
-+ dcl->ops->dpy_gl_scanout_texture(dcl, backing_id,
-+ backing_y_0_top,
-+ backing_width, backing_height,
-+ x, y, width, height,
-+ d3d_tex2d);
- }
- }
- }
-diff --git a/ui/dbus-console.c b/ui/dbus-console.c
-index 31939d0207..85e215ef23 100644
---- a/ui/dbus-console.c
-+++ b/ui/dbus-console.c
-@@ -94,9 +94,12 @@ dbus_gl_scanout_disable(DisplayChangeListener *dcl)
- static void
- dbus_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t tex_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d)
- {
- DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
-
-diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
-index abcc96287b..51244c9240 100644
---- a/ui/dbus-listener.c
-+++ b/ui/dbus-listener.c
-@@ -491,7 +491,7 @@ static bool dbus_scanout_map(DBusDisplayListener *ddl)
- #endif /* WIN32 */
-
- #ifdef CONFIG_OPENGL
--static void dbus_scanout_borrowed_texture(DisplayChangeListener *dcl,
-+static void dbus_scanout_texture(DisplayChangeListener *dcl,
- uint32_t tex_id,
- bool backing_y_0_top,
- uint32_t backing_width,
-@@ -540,25 +540,6 @@ static void dbus_scanout_borrowed_texture(DisplayChangeListener *dcl,
- #endif
- }
-
--static void dbus_scanout_texture(DisplayChangeListener *dcl,
-- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
--{
-- bool backing_y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
-- uint32_t tex_id = backing_borrow(backing_id, &backing_y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
--
-- dbus_scanout_borrowed_texture(dcl, tex_id, backing_y_0_top,
-- backing_width, backing_height,
-- x, y, w, h, d3d_tex2d);
--}
--
- #ifdef CONFIG_GBM
- static void dbus_cursor_dmabuf(DisplayChangeListener *dcl,
- QemuDmaBuf *dmabuf, bool have_hot,
-@@ -798,7 +779,7 @@ static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
- int height = surface_height(ddl->ds);
-
- /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
-- dbus_scanout_borrowed_texture(&ddl->dcl, ddl->ds->texture, false,
-+ dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
- width, height, 0, 0, width, height, NULL);
- }
- }
-diff --git a/ui/egl-headless.c b/ui/egl-headless.c
-index 96bbf52fd0..ba8d099f80 100644
---- a/ui/egl-headless.c
-+++ b/ui/egl-headless.c
-@@ -63,7 +63,7 @@ static void egl_scanout_disable(DisplayChangeListener *dcl)
- egl_fb_destroy(&edpy->blit_fb);
- }
-
--static void egl_scanout_imported_texture(DisplayChangeListener *dcl,
-+static void egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
- bool backing_y_0_top,
- uint32_t backing_width,
-@@ -88,25 +88,6 @@ static void egl_scanout_imported_texture(DisplayChangeListener *dcl,
- }
- }
-
--static void egl_scanout_texture(DisplayChangeListener *dcl,
-- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
--{
-- bool backing_y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
--
-- GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
-- egl_scanout_imported_texture(dcl, backing_texture, backing_y_0_top,
-- backing_width, backing_height,
-- x, y, w, h, d3d_tex2d);
--}
--
- #ifdef CONFIG_GBM
-
- static void egl_scanout_dmabuf(DisplayChangeListener *dcl,
-@@ -123,7 +104,7 @@ static void egl_scanout_dmabuf(DisplayChangeListener *dcl,
- width = qemu_dmabuf_get_width(dmabuf);
- height = qemu_dmabuf_get_height(dmabuf);
-
-- egl_scanout_imported_texture(dcl, texture, false, width, height, 0, 0,
-+ egl_scanout_texture(dcl, texture, false, width, height, 0, 0,
- width, height, NULL);
- }
-
-diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
-index d600456159..341afd41fb 100644
---- a/ui/gtk-egl.c
-+++ b/ui/gtk-egl.c
-@@ -233,7 +233,7 @@ void gd_egl_scanout_disable(DisplayChangeListener *dcl)
- gtk_egl_set_scanout_mode(vc, false);
- }
-
--void gd_egl_scanout_borrowed_texture(DisplayChangeListener *dcl,
-+void gd_egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id, bool backing_y_0_top,
- uint32_t backing_width, uint32_t backing_height,
- uint32_t x, uint32_t y,
-@@ -263,25 +263,8 @@ void gd_egl_scanout_borrowed_texture(DisplayChangeListener *dcl,
- backing_id, false);
- }
-
--void gd_egl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
--{
-- bool backing_y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
--
-- GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
-- gd_egl_scanout_borrowed_texture(dcl, backing_texture, backing_y_0_top,
-- backing_width, backing_height,
-- x, y, w, h, d3d_tex2d);
--}
--
--void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf)
-+void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
-+ QemuDmaBuf *dmabuf)
- {
- #ifdef CONFIG_GBM
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
-@@ -305,8 +288,8 @@ void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf)
- backing_height = qemu_dmabuf_get_backing_height(dmabuf);
- y0_top = qemu_dmabuf_get_y0_top(dmabuf);
-
-- gd_egl_scanout_borrowed_texture(dcl, texture, y0_top, backing_width,
-- backing_height, x, y, width, height, NULL);
-+ gd_egl_scanout_texture(dcl, texture, y0_top, backing_width, backing_height,
-+ x, y, width, height, NULL);
-
- if (qemu_dmabuf_get_allow_fences(dmabuf)) {
- vc->gfx.guest_fb.dmabuf = dmabuf;
-diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
-index 32bca91cbb..2c9a0db425 100644
---- a/ui/gtk-gl-area.c
-+++ b/ui/gtk-gl-area.c
-@@ -248,7 +248,7 @@ void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
- g_clear_object(&ctx);
- }
-
--void gd_gl_area_scanout_borrowed_texture(DisplayChangeListener *dcl,
-+void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
- bool backing_y_0_top,
- uint32_t backing_width,
-@@ -277,26 +277,6 @@ void gd_gl_area_scanout_borrowed_texture(DisplayChangeListener *dcl,
- backing_id, false);
- }
-
--void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
-- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
--{
-- bool backing_y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
--
-- GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
-- gd_gl_area_scanout_borrowed_texture(dcl, backing_texture,
-- backing_y_0_top,
-- backing_width, backing_height,
-- x, y, w, h, d3d_tex2d);
--}
--
- void gd_gl_area_scanout_disable(DisplayChangeListener *dcl)
- {
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
-@@ -341,7 +321,7 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
- backing_height = qemu_dmabuf_get_backing_height(dmabuf);
- y0_top = qemu_dmabuf_get_y0_top(dmabuf);
-
-- gd_gl_area_scanout_borrowed_texture(dcl, texture, y0_top,
-+ gd_gl_area_scanout_texture(dcl, texture, y0_top,
- backing_width, backing_height,
- x, y, width, height, NULL);
-
-diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
-index b164fb6b28..061f10c449 100644
---- a/ui/sdl2-gl.c
-+++ b/ui/sdl2-gl.c
-@@ -201,22 +201,17 @@ void sdl2_gl_scanout_disable(DisplayChangeListener *dcl)
-
- void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ bool backing_y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d)
- {
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
-- bool backing_y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
-
- assert(scon->opengl);
-
-- GLuint backing_texture = backing_borrow(backing_id, &backing_y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
--
- scon->x = x;
- scon->y = y;
- scon->w = w;
-@@ -227,7 +222,7 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
-
- sdl2_set_scanout_mode(scon, true);
- egl_fb_setup_for_tex(&scon->guest_fb, backing_width, backing_height,
-- backing_texture, false);
-+ backing_id, false);
- }
-
- void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index c7688256e9..61cdda0e8a 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -1132,36 +1132,30 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
- spice_iosurface_destroy(ssd);
- #endif
- #if defined(CONFIG_ANGLE)
-- ssd->backing_borrow = NULL;
-- ssd->backing_id = -1;
-+ ssd->tex_id = -1;
- #endif
- }
-
- static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
-- uint32_t backing_id,
-- DisplayGLTextureBorrower backing_borrow,
-+ uint32_t tex_id,
-+ bool y_0_top,
-+ uint32_t backing_width,
-+ uint32_t backing_height,
- uint32_t x, uint32_t y,
-- uint32_t w, uint32_t h)
-+ uint32_t w, uint32_t h,
-+ void *d3d_tex2d)
- {
- SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
- EGLint stride = 0, fourcc = 0;
- int fd = -1;
-- bool y_0_top;
-- uint32_t backing_width;
-- uint32_t backing_height;
-- void *d3d_tex2d;
--
-- GLuint tex_id = backing_borrow(backing_id, &y_0_top,
-- &backing_width, &backing_height,
-- &d3d_tex2d);
-- assert(tex_id);
-+
- #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)) {
- #if defined(CONFIG_ANGLE)
-- ssd->backing_borrow = backing_borrow;
-- ssd->backing_id = backing_id;
-+ ssd->tex_id = tex_id;
-+ ssd->y_0_top = y_0_top;
- #endif
- fd = spice_iosurface_create_fd(ssd, &fourcc);
- } else {
-@@ -1253,11 +1247,10 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- EGLint stride = 0, fourcc = 0;
- int fd;
- bool render_cursor = false;
-+ uint32_t texture;
- #endif
- bool y_0_top = false; /* FIXME */
- uint64_t cookie;
-- int fd;
-- uint32_t width, height, texture;
-
- if (!ssd->have_scanout) {
- return;
-@@ -1331,8 +1324,8 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- glFlush();
- }
- #elif defined(CONFIG_ANGLE) && defined(CONFIG_IOSURFACE)
-- GLuint tex_id = ssd->backing_borrow(ssd->backing_id, &y_0_top,
-- NULL, NULL, NULL);
-+ GLuint tex_id = ssd->tex_id;
-+ y_0_top = ssd->y_0_top;
- spice_iosurface_blit(ssd, tex_id, !y_0_top, false);
- //TODO: cursor stuff
- #endif
-@@ -1403,8 +1396,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
- #endif
- #if defined(CONFIG_ANGLE)
- ssd->esurface = EGL_NO_SURFACE;
-- ssd->backing_borrow = NULL;
-- ssd->backing_id = -1;
-+ ssd->tex_id = -1;
- #endif
- }
- #endif
---
-2.50.1 (Apple Git-155)
-
-From 11c36e637e854a63d07dc902e82378c8e2a9071f Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sat, 29 Nov 2025 22:53:18 -0800
-Subject: [PATCH 2/9] egl-helpers: store handle to native device
-
-Make way for other platforms by making the variable more general. Also we
-will be using the device in the future so let's save the pointer in the
-global instead of just a boolean flag.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 2 +-
- include/ui/egl-helpers.h | 2 +-
- ui/egl-helpers.c | 4 ++--
- 3 files changed, 4 insertions(+), 4 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index 145a0b3879..0511c6a478 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -1104,7 +1104,7 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
- }
- #endif
- #ifdef VIRGL_RENDERER_D3D11_SHARE_TEXTURE
-- if (qemu_egl_angle_d3d) {
-+ if (qemu_egl_angle_native_device) {
- flags |= VIRGL_RENDERER_D3D11_SHARE_TEXTURE;
- }
- #endif
-diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h
-index eb08ef1990..2930aef7e3 100644
---- a/include/ui/egl-helpers.h
-+++ b/include/ui/egl-helpers.h
-@@ -15,7 +15,7 @@
- extern EGLDisplay *qemu_egl_display;
- extern EGLConfig qemu_egl_config;
- extern DisplayGLMode qemu_egl_mode;
--extern bool qemu_egl_angle_d3d;
-+extern void *qemu_egl_angle_native_device;
-
- typedef struct egl_fb {
- int width;
-diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
-index 7b403b87a5..42d2d511b1 100644
---- a/ui/egl-helpers.c
-+++ b/ui/egl-helpers.c
-@@ -27,7 +27,7 @@
- EGLDisplay *qemu_egl_display;
- EGLConfig qemu_egl_config;
- DisplayGLMode qemu_egl_mode;
--bool qemu_egl_angle_d3d;
-+void *qemu_egl_angle_native_device;
-
- /* ------------------------------------------------------------------ */
-
-@@ -640,7 +640,7 @@ int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode)
- }
-
- trace_egl_init_d3d11_device(device);
-- qemu_egl_angle_d3d = device != NULL;
-+ qemu_egl_angle_native_device = d3d11_device;
- }
- #endif
-
---
-2.41.0
-
-From e02ff2b56fedb74e1161498950b55d2284a2788d Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sat, 3 Jan 2026 14:32:15 -0800
-Subject: [PATCH 3/9] console: rename `d3d_tex2d` to `native`
-
-In order to support native texture scanout beyond D3D, we make this more
-generic allowing for multiple native texture handle types.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 10 +++++++---
- include/ui/console.h | 20 +++++++++++++++++---
- include/ui/gtk.h | 4 ++--
- include/ui/sdl2.h | 2 +-
- ui/console.c | 8 ++++----
- ui/dbus-console.c | 2 +-
- ui/dbus-listener.c | 8 ++++----
- ui/egl-headless.c | 4 ++--
- ui/gtk-egl.c | 4 ++--
- ui/gtk-gl-area.c | 4 ++--
- ui/sdl2-gl.c | 2 +-
- ui/spice-display.c | 2 +-
- 12 files changed, 44 insertions(+), 26 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index 0511c6a478..eac49fbae8 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -416,14 +416,18 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
-
- if (ss.resource_id && ss.r.width && ss.r.height) {
- struct virgl_renderer_resource_info info;
-- void *d3d_tex2d = NULL;
-+ ScanoutTextureNative native = NO_NATIVE_TEXTURE;
-
- #if VIRGL_VERSION_MAJOR >= 1
- struct virgl_renderer_resource_info_ext ext;
- memset(&ext, 0, sizeof(ext));
- ret = virgl_renderer_resource_get_info_ext(ss.resource_id, &ext);
- info = ext.base;
-- d3d_tex2d = ext.d3d_tex2d;
-+ native = (ScanoutTextureNative){
-+ .type = ext.d3d_tex2d ? SCANOUT_TEXTURE_NATIVE_TYPE_D3D :
-+ SCANOUT_TEXTURE_NATIVE_TYPE_NONE,
-+ .handle = ext.d3d_tex2d,
-+ };
- #else
- memset(&info, 0, sizeof(info));
- ret = virgl_renderer_resource_get_info(ss.resource_id, &info);
-@@ -443,7 +447,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
- info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
- info.width, info.height,
- ss.r.x, ss.r.y, ss.r.width, ss.r.height,
-- d3d_tex2d);
-+ native);
- } else {
- dpy_gfx_replace_surface(
- g->parent_obj.scanout[ss.scanout_id].con, NULL);
-diff --git a/include/ui/console.h b/include/ui/console.h
-index 46b3128185..445e563150 100644
---- a/include/ui/console.h
-+++ b/include/ui/console.h
-@@ -131,6 +131,20 @@ struct QemuConsoleClass {
- ObjectClass parent_class;
- };
-
-+typedef enum ScanoutTextureNativeType {
-+ SCANOUT_TEXTURE_NATIVE_TYPE_NONE,
-+ SCANOUT_TEXTURE_NATIVE_TYPE_D3D,
-+} ScanoutTextureNativeType;
-+
-+typedef struct ScanoutTextureNative {
-+ ScanoutTextureNativeType type;
-+ void *handle;
-+} ScanoutTextureNative;
-+
-+#define NO_NATIVE_TEXTURE ((ScanoutTextureNative){ \
-+ .type = SCANOUT_TEXTURE_NATIVE_TYPE_NONE \
-+})
-+
- typedef struct ScanoutTexture {
- uint32_t backing_id;
- bool backing_y_0_top;
-@@ -140,7 +154,7 @@ typedef struct ScanoutTexture {
- uint32_t y;
- uint32_t width;
- uint32_t height;
-- void *d3d_tex2d;
-+ ScanoutTextureNative native;
- } ScanoutTexture;
-
- typedef struct QemuUIInfo {
-@@ -245,7 +259,7 @@ typedef struct DisplayChangeListenerOps {
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d);
-+ ScanoutTextureNative native);
- /* optional (default to true if has dpy_gl_scanout_dmabuf) */
- bool (*dpy_has_dmabuf)(DisplayChangeListener *dcl);
- /* optional */
-@@ -329,7 +343,7 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- uint32_t backing_id, bool backing_y_0_top,
- uint32_t backing_width, uint32_t backing_height,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h,
-- void *d3d_tex2d);
-+ ScanoutTextureNative native);
- void dpy_gl_scanout_dmabuf(QemuConsole *con,
- QemuDmaBuf *dmabuf);
- void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
-diff --git a/include/ui/gtk.h b/include/ui/gtk.h
-index aa3d637029..6aacd70ea2 100644
---- a/include/ui/gtk.h
-+++ b/include/ui/gtk.h
-@@ -176,7 +176,7 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d);
-+ ScanoutTextureNative native);
- void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
- QemuDmaBuf *dmabuf);
- void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
-@@ -213,7 +213,7 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d);
-+ ScanoutTextureNative native);
- void gd_gl_area_scanout_disable(DisplayChangeListener *dcl);
- void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h);
-diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h
-index dbe6e3d973..fdefb88229 100644
---- a/include/ui/sdl2.h
-+++ b/include/ui/sdl2.h
-@@ -93,7 +93,7 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d);
-+ ScanoutTextureNative native);
- void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h);
-
-diff --git a/ui/console.c b/ui/console.c
-index 480cd63f90..d8d58fc14a 100644
---- a/ui/console.c
-+++ b/ui/console.c
-@@ -295,7 +295,7 @@ static void displaychangelistener_display_console(DisplayChangeListener *dcl,
- con->scanout.texture.y,
- con->scanout.texture.width,
- con->scanout.texture.height,
-- con->scanout.texture.d3d_tex2d);
-+ con->scanout.texture.native);
- }
- }
-
-@@ -1023,7 +1023,7 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t width, uint32_t height,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- DisplayState *s = con->ds;
- DisplayChangeListener *dcl;
-@@ -1031,7 +1031,7 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- con->scanout.kind = SCANOUT_TEXTURE;
- con->scanout.texture = (ScanoutTexture) {
- backing_id, backing_y_0_top, backing_width, backing_height,
-- x, y, width, height, d3d_tex2d,
-+ x, y, width, height, native,
- };
- QLIST_FOREACH(dcl, &s->listeners, next) {
- if (con != dcl->con) {
-@@ -1042,7 +1042,7 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- backing_y_0_top,
- backing_width, backing_height,
- x, y, width, height,
-- d3d_tex2d);
-+ native);
- }
- }
- }
-diff --git a/ui/dbus-console.c b/ui/dbus-console.c
-index 85e215ef23..651f0daeaf 100644
---- a/ui/dbus-console.c
-+++ b/ui/dbus-console.c
-@@ -99,7 +99,7 @@ dbus_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
-
-diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c
-index 51244c9240..f5dabaeb85 100644
---- a/ui/dbus-listener.c
-+++ b/ui/dbus-listener.c
-@@ -498,7 +498,7 @@ static void dbus_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- trace_dbus_scanout_texture(tex_id, backing_y_0_top,
- backing_width, backing_height, x, y, w, h);
-@@ -530,8 +530,8 @@ static void dbus_scanout_texture(DisplayChangeListener *dcl,
- assert(surface_width(ddl->ds) == w);
- assert(surface_height(ddl->ds) == h);
-
-- if (d3d_tex2d) {
-- dbus_scanout_share_d3d_texture(ddl, d3d_tex2d, backing_y_0_top,
-+ if (native.type == SCANOUT_TEXTURE_NATIVE_TYPE_D3D) {
-+ dbus_scanout_share_d3d_texture(ddl, native.handle, backing_y_0_top,
- backing_width, backing_height, x, y, w, h);
- } else {
- dbus_scanout_map(ddl);
-@@ -780,7 +780,7 @@ static void dbus_gl_gfx_switch(DisplayChangeListener *dcl,
-
- /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */
- dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false,
-- width, height, 0, 0, width, height, NULL);
-+ width, height, 0, 0, width, height, NO_NATIVE_TEXTURE);
- }
- }
- #endif
-diff --git a/ui/egl-headless.c b/ui/egl-headless.c
-index ba8d099f80..5a403d13e6 100644
---- a/ui/egl-headless.c
-+++ b/ui/egl-headless.c
-@@ -70,7 +70,7 @@ static void egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
-
-@@ -105,7 +105,7 @@ static void egl_scanout_dmabuf(DisplayChangeListener *dcl,
- height = qemu_dmabuf_get_height(dmabuf);
-
- egl_scanout_texture(dcl, texture, false, width, height, 0, 0,
-- width, height, NULL);
-+ width, height, NO_NATIVE_TEXTURE);
- }
-
- static void egl_cursor_dmabuf(DisplayChangeListener *dcl,
-diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
-index 341afd41fb..2b94079b15 100644
---- a/ui/gtk-egl.c
-+++ b/ui/gtk-egl.c
-@@ -238,7 +238,7 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_width, uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
-
-@@ -289,7 +289,7 @@ void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
- y0_top = qemu_dmabuf_get_y0_top(dmabuf);
-
- gd_egl_scanout_texture(dcl, texture, y0_top, backing_width, backing_height,
-- x, y, width, height, NULL);
-+ x, y, width, height, NO_NATIVE_TEXTURE);
-
- if (qemu_dmabuf_get_allow_fences(dmabuf)) {
- vc->gfx.guest_fb.dmabuf = dmabuf;
-diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
-index 2c9a0db425..f7ed03890d 100644
---- a/ui/gtk-gl-area.c
-+++ b/ui/gtk-gl-area.c
-@@ -255,7 +255,7 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
-
-@@ -323,7 +323,7 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
-
- gd_gl_area_scanout_texture(dcl, texture, y0_top,
- backing_width, backing_height,
-- x, y, width, height, NULL);
-+ x, y, width, height, NO_NATIVE_TEXTURE);
-
- if (qemu_dmabuf_get_allow_fences(dmabuf)) {
- vc->gfx.guest_fb.dmabuf = dmabuf;
-diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
-index 1d8ff3fab0..5b0080a0f9 100644
---- a/ui/sdl2-gl.c
-+++ b/ui/sdl2-gl.c
-@@ -206,7 +206,7 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
-
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 61cdda0e8a..83a54e69a0 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -1143,7 +1143,7 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h,
-- void *d3d_tex2d)
-+ ScanoutTextureNative native)
- {
- SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
- EGLint stride = 0, fourcc = 0;
---
-2.41.0
-
-From 270b5d8db6802cc937e2beaf5c87030da1dc39e2 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 13:39:26 -0800
-Subject: [PATCH 4/9] ui/spice-display: move display early init logic to
- spice-display.c
-
----
- include/ui/qemu-spice.h | 1 +
- ui/spice-core.c | 29 +----------------------------
- ui/spice-display.c | 39 +++++++++++++++++++++++++++++++++++++++
- 3 files changed, 41 insertions(+), 28 deletions(-)
-
-diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h
-index b7d493742c..5f5b9e2147 100644
---- a/include/ui/qemu-spice.h
-+++ b/include/ui/qemu-spice.h
-@@ -27,6 +27,7 @@
- #include "qemu/config-file.h"
-
- void qemu_spice_input_init(void);
-+void qemu_spice_display_early_init(void);
- void qemu_spice_display_init(void);
- void qemu_spice_display_init_done(void);
- bool qemu_spice_have_display_interface(QemuConsole *con);
-diff --git a/ui/spice-core.c b/ui/spice-core.c
-index 1c3b3b395a..43715cb60d 100644
---- a/ui/spice-core.c
-+++ b/ui/spice-core.c
-@@ -837,34 +837,7 @@ static void qemu_spice_init(void)
- g_free(x509_cacert_file);
- g_free(password);
-
--#ifdef HAVE_SPICE_GL
-- if (qemu_opt_get_bool(opts, "gl", 0)) {
-- if ((port != 0) || (tls_port != 0)) {
-- error_report("SPICE GL support is local-only for now and "
-- "incompatible with -spice port/tls-port");
-- exit(1);
-- }
--#if defined(CONFIG_GBM)
-- egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
--#elif defined(CONFIG_ANGLE)
-- if (qemu_egl_init_dpy_angle(DISPLAY_GL_MODE_ES)) {
-- error_report("SPICE GL failed to initialize ANGLE display");
-- exit(1);
-- }
--
-- spice_gl_ctx = qemu_egl_init_ctx();
-- if (!spice_gl_ctx) {
-- error_report("egl: egl_init_ctx failed");
-- exit(1);
-- }
--#else
-- error_report("No backend to support SPICE GL");
-- exit(1);
--#endif
-- display_opengl = 1;
-- spice_opengl = 1;
-- }
--#endif
-+ qemu_spice_display_early_init();
- }
-
- static int qemu_spice_add_interface(SpiceBaseInstance *sin)
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 83a54e69a0..7faa922092 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -24,6 +24,7 @@
- #include "qemu/option.h"
- #include "qemu/queue.h"
- #include "ui/console.h"
-+#include "system/system.h"
- #include "trace.h"
- #ifdef CONFIG_IOSURFACE
- #include
-@@ -1426,6 +1427,44 @@ static void qemu_spice_display_init_one(QemuConsole *con)
- register_displaychangelistener(&ssd->dcl);
- }
-
-+void qemu_spice_display_early_init(void)
-+{
-+#ifdef HAVE_SPICE_GL
-+ QemuOptsList *olist = qemu_find_opts("spice");
-+ QemuOpts *opts = QTAILQ_FIRST(&olist->head);
-+ int port, tls_port;
-+
-+ port = qemu_opt_get_number(opts, "port", 0);
-+ tls_port = qemu_opt_get_number(opts, "tls-port", 0);
-+ if (qemu_opt_get_bool(opts, "gl", 0)) {
-+ if ((port != 0) || (tls_port != 0)) {
-+ error_report("SPICE GL support is local-only for now and "
-+ "incompatible with -spice port/tls-port");
-+ exit(1);
-+ }
-+#if defined(CONFIG_GBM)
-+ egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
-+#elif defined(CONFIG_ANGLE)
-+ if (qemu_egl_init_dpy_angle(DISPLAY_GL_MODE_ES)) {
-+ error_report("SPICE GL failed to initialize ANGLE display");
-+ exit(1);
-+ }
-+
-+ spice_gl_ctx = qemu_egl_init_ctx();
-+ if (!spice_gl_ctx) {
-+ error_report("egl: egl_init_ctx failed");
-+ exit(1);
-+ }
-+#else
-+ error_report("No backend to support SPICE GL");
-+ exit(1);
-+#endif
-+ display_opengl = 1;
-+ spice_opengl = 1;
-+ }
-+#endif
-+}
-+
- void qemu_spice_display_init(void)
- {
- QemuOptsList *olist = qemu_find_opts("spice");
---
-2.41.0
-
-From 11b76bf6761f10a586497200745bda9ef2bb9732 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 15:56:00 -0800
-Subject: [PATCH 5/9] Revert "shaders: support byte swapping of pixels"
-
-This reverts commit e73864e11d4f3224174a677a96925d253ccf892d.
----
- include/ui/egl-helpers.h | 4 ++--
- include/ui/shader.h | 2 +-
- include/ui/surface.h | 1 -
- ui/console-gl.c | 5 ++---
- ui/egl-headless.c | 4 ++--
- ui/egl-helpers.c | 8 ++++----
- ui/gtk-egl.c | 4 ++--
- ui/shader.c | 26 +++++---------------------
- ui/shader/meson.build | 1 -
- ui/shader/texture-blit-swap.frag | 7 -------
- ui/spice-display.c | 10 +++++-----
- 11 files changed, 23 insertions(+), 49 deletions(-)
- delete mode 100644 ui/shader/texture-blit-swap.frag
-
-diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h
-index 2930aef7e3..5dd2a6dac0 100644
---- a/include/ui/egl-helpers.h
-+++ b/include/ui/egl-helpers.h
-@@ -41,9 +41,9 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip);
- void egl_fb_read(DisplaySurface *dst, egl_fb *src);
- void egl_fb_read_rect(DisplaySurface *dst, egl_fb *src, int x, int y, int w, int h);
-
--void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip, bool swap);
-+void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip);
- void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
-- bool swap, int x, int y, double scale_x, double scale_y);
-+ int x, int y, double scale_x, double scale_y);
-
- extern EGLContext qemu_egl_rn_ctx;
-
-diff --git a/include/ui/shader.h b/include/ui/shader.h
-index 252192793a..4c5acb2ce8 100644
---- a/include/ui/shader.h
-+++ b/include/ui/shader.h
-@@ -5,7 +5,7 @@
-
- typedef struct QemuGLShader QemuGLShader;
-
--void qemu_gl_run_texture_blit(QemuGLShader *gls, bool flip, bool swapped);
-+void qemu_gl_run_texture_blit(QemuGLShader *gls, bool flip);
-
- QemuGLShader *qemu_gl_init_shader(void);
- void qemu_gl_fini_shader(QemuGLShader *gls);
-diff --git a/include/ui/surface.h b/include/ui/surface.h
-index 68ce190e26..f16f7be8be 100644
---- a/include/ui/surface.h
-+++ b/include/ui/surface.h
-@@ -22,7 +22,6 @@ typedef struct DisplaySurface {
- GLenum glformat;
- GLenum gltype;
- GLuint texture;
-- bool glswapped;
- #endif
- qemu_pixman_shareable share_handle;
- uint32_t share_handle_offset;
-diff --git a/ui/console-gl.c b/ui/console-gl.c
-index 9cdb0bd482..103b954017 100644
---- a/ui/console-gl.c
-+++ b/ui/console-gl.c
-@@ -56,9 +56,8 @@ void surface_gl_create_texture(QemuGLShader *gls,
- switch (surface_format(surface)) {
- case PIXMAN_BE_b8g8r8x8:
- case PIXMAN_BE_b8g8r8a8:
-- surface->glformat = GL_RGBA;
-+ surface->glformat = GL_BGRA_EXT;
- surface->gltype = GL_UNSIGNED_BYTE;
-- surface->glswapped = true;
- break;
- case PIXMAN_BE_x8r8g8b8:
- case PIXMAN_BE_a8r8g8b8:
-@@ -126,7 +125,7 @@ void surface_gl_render_texture(QemuGLShader *gls,
- glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
-
-- qemu_gl_run_texture_blit(gls, false, false);
-+ qemu_gl_run_texture_blit(gls, false);
- }
-
- void surface_gl_destroy_texture(QemuGLShader *gls,
-diff --git a/ui/egl-headless.c b/ui/egl-headless.c
-index 5a403d13e6..0f0c3b21f5 100644
---- a/ui/egl-headless.c
-+++ b/ui/egl-headless.c
-@@ -161,9 +161,9 @@ static void egl_scanout_flush(DisplayChangeListener *dcl,
- if (edpy->cursor_fb.texture) {
- /* have cursor -> render using textures */
- egl_texture_blit(edpy->gls, &edpy->blit_fb, &edpy->guest_fb,
-- !edpy->y_0_top, false);
-+ !edpy->y_0_top);
- egl_texture_blend(edpy->gls, &edpy->blit_fb, &edpy->cursor_fb,
-- !edpy->y_0_top, false, edpy->pos_x, edpy->pos_y,
-+ !edpy->y_0_top, edpy->pos_x, edpy->pos_y,
- 1.0, 1.0);
- } else {
- /* no cursor -> use simple framebuffer blit */
-diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
-index 42d2d511b1..6d0304fa40 100644
---- a/ui/egl-helpers.c
-+++ b/ui/egl-helpers.c
-@@ -198,17 +198,17 @@ void egl_fb_read_rect(DisplaySurface *dst, egl_fb *src, int x, int y, int w, int
- glPixelStorei(GL_PACK_ROW_LENGTH, 0);
- }
-
--void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip, bool swap)
-+void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip)
- {
- glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
- glViewport(0, 0, dst->width, dst->height);
- glEnable(GL_TEXTURE_2D);
- glBindTexture(src->texture_target, src->texture);
-- qemu_gl_run_texture_blit(gls, flip, swap);
-+ qemu_gl_run_texture_blit(gls, flip);
- }
-
- void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
-- bool swap, int x, int y, double scale_x, double scale_y)
-+ int x, int y, double scale_x, double scale_y)
- {
- glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
- int w = scale_x * src->width;
-@@ -222,7 +222,7 @@ void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
- glBindTexture(src->texture_target, src->texture);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-- qemu_gl_run_texture_blit(gls, flip, swap);
-+ qemu_gl_run_texture_blit(gls, flip);
- glDisable(GL_BLEND);
- }
-
-diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
-index 2b94079b15..6b6ac1789b 100644
---- a/ui/gtk-egl.c
-+++ b/ui/gtk-egl.c
-@@ -355,9 +355,9 @@ void gd_egl_scanout_flush(DisplayChangeListener *dcl,
- egl_fb_setup_default(&vc->gfx.win_fb, ww, wh);
- if (vc->gfx.cursor_fb.texture) {
- egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb,
-- vc->gfx.y0_top, false);
-+ vc->gfx.y0_top);
- egl_texture_blend(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.cursor_fb,
-- vc->gfx.y0_top, false,
-+ vc->gfx.y0_top,
- vc->gfx.cursor_x, vc->gfx.cursor_y,
- vc->gfx.scale_x, vc->gfx.scale_y);
- } else {
-diff --git a/ui/shader.c b/ui/shader.c
-index a0fb21c0c0..c5c9f754cc 100644
---- a/ui/shader.c
-+++ b/ui/shader.c
-@@ -30,13 +30,10 @@
- #include "ui/shader/texture-blit-vert.h"
- #include "ui/shader/texture-blit-flip-vert.h"
- #include "ui/shader/texture-blit-frag.h"
--#include "ui/shader/texture-blit-swap-frag.h"
-
- struct QemuGLShader {
- GLint texture_blit_prog;
- GLint texture_blit_flip_prog;
-- GLint texture_blit_swap_prog;
-- GLint texture_blit_flip_swap_prog;
- GLint texture_blit_vao;
- };
-
-@@ -72,17 +69,11 @@ static GLuint qemu_gl_init_texture_blit(GLint texture_blit_prog)
- return vao;
- }
-
--void qemu_gl_run_texture_blit(QemuGLShader *gls, bool flip, bool swapped)
-+void qemu_gl_run_texture_blit(QemuGLShader *gls, bool flip)
- {
-- if (flip && swapped) {
-- glUseProgram(gls->texture_blit_flip_swap_prog);
-- } else if (flip && !swapped) {
-- glUseProgram(gls->texture_blit_flip_prog);
-- } else if (!flip && swapped) {
-- glUseProgram(gls->texture_blit_swap_prog);
-- } else { // !flip && !swapped
-- glUseProgram(gls->texture_blit_prog);
-- }
-+ glUseProgram(flip
-+ ? gls->texture_blit_flip_prog
-+ : gls->texture_blit_prog);
- glBindVertexArray(gls->texture_blit_vao);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- }
-@@ -174,14 +165,7 @@ QemuGLShader *qemu_gl_init_shader(void)
- strcpy(vert_src_body, texture_blit_flip_vert_src);
- gls->texture_blit_flip_prog = qemu_gl_create_compile_link_program
- (vert_src, frag_src);
-- strcpy(frag_src_body, texture_blit_swap_frag_src);
-- gls->texture_blit_flip_swap_prog = qemu_gl_create_compile_link_program
-- (vert_src, frag_src);
-- strcpy(vert_src_body, texture_blit_vert_src);
-- gls->texture_blit_swap_prog = qemu_gl_create_compile_link_program
-- (vert_src, frag_src);
-- if (!gls->texture_blit_prog || !gls->texture_blit_flip_prog ||
-- !gls->texture_blit_swap_prog || !gls->texture_blit_flip_swap_prog) {
-+ if (!gls->texture_blit_prog || !gls->texture_blit_flip_prog) {
- exit(1);
- }
-
-diff --git a/ui/shader/meson.build b/ui/shader/meson.build
-index 14f4699cec..3137e65578 100644
---- a/ui/shader/meson.build
-+++ b/ui/shader/meson.build
-@@ -1,6 +1,5 @@
- shaders = [
- ['texture-blit', 'frag'],
-- ['texture-blit-swap', 'frag'],
- ['texture-blit', 'vert'],
- ['texture-blit-flip', 'vert'],
- ]
-diff --git a/ui/shader/texture-blit-swap.frag b/ui/shader/texture-blit-swap.frag
-deleted file mode 100644
-index 8c97bcbac5..0000000000
---- a/ui/shader/texture-blit-swap.frag
-+++ /dev/null
-@@ -1,7 +0,0 @@
--uniform sampler2D image;
--in mediump vec2 ex_tex_coord;
--out mediump vec4 out_frag_color;
--
--void main(void) {
-- out_frag_color = texture(image, ex_tex_coord).zyxw;
--}
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 7faa922092..f56f56dd71 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -951,7 +951,7 @@ static int spice_iosurface_create_fd(SimpleSpiceDisplay *ssd, int *fourcc)
- return fds[0];
- }
-
--static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bool flip, bool swap)
-+static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bool flip)
- {
- egl_fb tmp_fb = { .texture = src_texture, .texture_target = GL_TEXTURE_2D };
- if (!ssd->iosurface) {
-@@ -960,7 +960,7 @@ static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bo
-
- #if defined(CONFIG_ANGLE)
- eglMakeCurrent(qemu_egl_display, ssd->esurface, ssd->esurface, spice_gl_ctx);
-- egl_texture_blit(ssd->gls, &ssd->iosurface_fb, &tmp_fb, flip, swap);
-+ egl_texture_blit(ssd->gls, &ssd->iosurface_fb, &tmp_fb, flip);
- #endif
- }
-
-@@ -1045,7 +1045,7 @@ static void spice_gl_update(DisplayChangeListener *dcl,
- surface_gl_update_texture(ssd->gls, ssd->ds, x, y, w, h);
- #if defined(CONFIG_IOSURFACE)
- if (!qemu_console_is_gl_blocked(ssd->dcl.con)) {
-- spice_iosurface_blit(ssd, ssd->ds->texture, true, ssd->ds->glswapped);
-+ spice_iosurface_blit(ssd, ssd->ds->texture, true);
- }
- #endif
- ssd->gl_updates++;
-@@ -1319,7 +1319,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- ptr_y = ssd->ptr_y;
- qemu_mutex_unlock(&ssd->lock);
- egl_texture_blit(ssd->gls, &ssd->blit_fb, &ssd->guest_fb,
-- !y_0_top, false);
-+ !y_0_top);
- egl_texture_blend(ssd->gls, &ssd->blit_fb, &ssd->cursor_fb,
- !y_0_top, false, ptr_x, ptr_y, 1.0, 1.0);
- glFlush();
-@@ -1327,7 +1327,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- #elif defined(CONFIG_ANGLE) && defined(CONFIG_IOSURFACE)
- GLuint tex_id = ssd->tex_id;
- y_0_top = ssd->y_0_top;
-- spice_iosurface_blit(ssd, tex_id, !y_0_top, false);
-+ spice_iosurface_blit(ssd, tex_id, !y_0_top);
- //TODO: cursor stuff
- #endif
-
---
-2.41.0
-
-From e9dc30b4c4cdf10d82681a28b4bf2e328624a8b9 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 16:07:59 -0800
-Subject: [PATCH 6/9] Revert "egl-headless: Allow to test with Mesa on macOS"
-
-This reverts commit edba23a4d49e6c6c7b88e10be5f5f29e5d98f2ee.
----
- include/ui/egl-helpers.h | 2 --
- ui/egl-headless.c | 21 ---------------------
- ui/egl-helpers.c | 5 -----
- ui/meson.build | 2 +-
- 4 files changed, 1 insertion(+), 29 deletions(-)
-
-diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h
-index 5dd2a6dac0..a2961d067b 100644
---- a/include/ui/egl-helpers.h
-+++ b/include/ui/egl-helpers.h
-@@ -68,8 +68,6 @@ EGLSurface qemu_egl_init_buffer_surface(EGLContext ectx, EGLenum buftype,
- EGLClientBuffer buffer, const EGLint *attrib_list);
- bool qemu_egl_destroy_surface(EGLSurface surface);
-
--int qemu_egl_init_dpy_surfaceless(DisplayGLMode mode);
--
- #if defined(CONFIG_X11) || defined(CONFIG_GBM)
-
- int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode);
-diff --git a/ui/egl-headless.c b/ui/egl-headless.c
-index 0f0c3b21f5..ef5cc02dcc 100644
---- a/ui/egl-headless.c
-+++ b/ui/egl-headless.c
-@@ -19,10 +19,6 @@ typedef struct egl_dpy {
- uint32_t pos_y;
- } egl_dpy;
-
--#ifndef CONFIG_GBM
--static EGLContext ctx;
--#endif
--
- /* ------------------------------------------------------------------ */
-
- static void egl_refresh(DisplayChangeListener *dcl)
-@@ -46,12 +42,8 @@ static void egl_gfx_switch(DisplayChangeListener *dcl,
- static QEMUGLContext egl_create_context(DisplayGLCtx *dgc,
- QEMUGLParams *params)
- {
--#ifdef CONFIG_GBM
- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- qemu_egl_rn_ctx);
--#else
-- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx);
--#endif
- return qemu_egl_create_context(dgc, params);
- }
-
-@@ -221,20 +213,7 @@ static void early_egl_headless_init(DisplayOptions *opts)
- mode = opts->gl;
- }
-
--#ifdef CONFIG_GBM
- egl_init(opts->u.egl_headless.rendernode, mode, &error_fatal);
--#else
-- if (qemu_egl_init_dpy_surfaceless(mode)) {
-- error_report("egl: display init failed");
-- exit(1);
-- }
--
-- ctx = qemu_egl_init_ctx();
-- if (!ctx) {
-- error_report("egl: egl_init_ctx failed");
-- exit(1);
-- }
--#endif
- }
-
- static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
-diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
-index 6d0304fa40..6a9c68495b 100644
---- a/ui/egl-helpers.c
-+++ b/ui/egl-helpers.c
-@@ -584,11 +584,6 @@ static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
-
- #endif
-
--int qemu_egl_init_dpy_surfaceless(DisplayGLMode mode)
--{
-- return qemu_egl_init_dpy(EGL_DEFAULT_DISPLAY, EGL_PLATFORM_SURFACELESS_MESA, mode);
--}
--
- #if defined(CONFIG_X11) || defined(CONFIG_GBM)
- int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode)
- {
-diff --git a/ui/meson.build b/ui/meson.build
-index b2f7541dad..a731a60f3a 100644
---- a/ui/meson.build
-+++ b/ui/meson.build
-@@ -69,7 +69,7 @@ endif
- if opengl.found()
- egl_headless_ss = ss.source_set()
- egl_headless_ss.add(when: [opengl, pixman],
-- if_true: files('egl-headless.c'))
-+ if_true: [files('egl-headless.c'), gbm])
- ui_modules += {'egl-headless' : egl_headless_ss}
- endif
-
---
-2.41.0
-
-From 23f2733351953e46875be219b39d024e2cadb675 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 16:28:21 -0800
-Subject: [PATCH 7/9] ui/egl: rename CONFIG_ANGLE to CONFIG_EGL
-
-Synchronize with @akihikodaki's fork
----
- include/ui/egl-helpers.h | 9 +--
- include/ui/spice-display.h | 2 +-
- meson.build | 11 ++-
- ui/egl-helpers.c | 149 +++++++++++++++++++------------------
- ui/spice-core.c | 2 +-
- ui/spice-display.c | 28 ++++---
- 6 files changed, 101 insertions(+), 100 deletions(-)
-
-diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h
-index a2961d067b..ecf814bec3 100644
---- a/include/ui/egl-helpers.h
-+++ b/include/ui/egl-helpers.h
-@@ -6,9 +6,6 @@
- #ifdef CONFIG_GBM
- #include
- #endif
--#ifdef CONFIG_ANGLE
--#include
--#endif
- #include "ui/console.h"
- #include "ui/shader.h"
-
-@@ -68,6 +65,8 @@ EGLSurface qemu_egl_init_buffer_surface(EGLContext ectx, EGLenum buftype,
- EGLClientBuffer buffer, const EGLint *attrib_list);
- bool qemu_egl_destroy_surface(EGLSurface surface);
-
-+int qemu_egl_init_dpy_cocoa(DisplayGLMode mode);
-+
- #if defined(CONFIG_X11) || defined(CONFIG_GBM)
-
- int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode);
-@@ -79,10 +78,6 @@ int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode);
- int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode);
- #endif
-
--#if defined(CONFIG_ANGLE)
--int qemu_egl_init_dpy_angle(DisplayGLMode mode);
--#endif
--
- EGLContext qemu_egl_init_ctx(void);
- bool qemu_egl_has_dmabuf(void);
-
-diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
-index 4b9121bfb5..5a0b5a0c8f 100644
---- a/include/ui/spice-display.h
-+++ b/include/ui/spice-display.h
-@@ -138,7 +138,7 @@ struct SimpleSpiceDisplay {
- IOSurfaceRef iosurface;
- int surface_send_fd;
- #endif
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- EGLSurface esurface;
- egl_fb iosurface_fb;
- GLuint tex_id;
-diff --git a/meson.build b/meson.build
-index abe0dcf01c..fff146e5c2 100644
---- a/meson.build
-+++ b/meson.build
-@@ -1784,14 +1784,13 @@ if not get_option('coreaudio').auto() or (host_os == 'darwin' and have_system)
- required: get_option('coreaudio'))
- endif
-
-+egl = not_found
- opengl = not_found
- if not get_option('opengl').auto() or have_system or have_vhost_user_gpu
-- epoxy = dependency('epoxy', method: 'pkg-config',
-+ opengl = dependency('epoxy', method: 'pkg-config',
- required: get_option('opengl'))
-- if cc.has_header('epoxy/egl.h', dependencies: epoxy)
-- opengl = epoxy
-- elif get_option('opengl').enabled()
-- error('epoxy/egl.h not found')
-+ if cc.has_header('epoxy/egl.h', dependencies: opengl)
-+ egl = opengl
- endif
- endif
- gbm = not_found
-@@ -2538,6 +2537,7 @@ if numa.found()
- cc.has_function('numa_has_preferred_many',
- dependencies: numa))
- endif
-+config_host_data.set('CONFIG_EGL', egl.found())
- config_host_data.set('CONFIG_OPENGL', opengl.found())
- config_host_data.set('CONFIG_PLUGIN', get_option('plugins'))
- config_host_data.set('CONFIG_RBD', rbd.found())
-@@ -2661,7 +2661,6 @@ config_host_data.set('HAVE_PTY_H', cc.has_header('pty.h'))
- config_host_data.set('HAVE_SYS_DISK_H', cc.has_header('sys/disk.h'))
- config_host_data.set('HAVE_SYS_IOCCOM_H', cc.has_header('sys/ioccom.h'))
- config_host_data.set('HAVE_SYS_KCOV_H', cc.has_header('sys/kcov.h'))
--config_host_data.set('CONFIG_ANGLE', cc.has_header('EGL/eglext_angle.h'))
- if host_os == 'windows'
- config_host_data.set('HAVE_AFUNIX_H', cc.has_header('afunix.h'))
- endif
-diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
-index 6a9c68495b..f35223592e 100644
---- a/ui/egl-helpers.c
-+++ b/ui/egl-helpers.c
-@@ -472,9 +472,73 @@ bool qemu_egl_destroy_surface(EGLSurface surface)
- return eglDestroySurface(qemu_egl_display, surface);
- }
-
-+static int qemu_egl_init_dpy(EGLDisplay dpy, DisplayGLMode mode)
-+{
-+ static const EGLint conf_att_core[] = {
-+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
-+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
-+ EGL_RED_SIZE, 5,
-+ EGL_GREEN_SIZE, 5,
-+ EGL_BLUE_SIZE, 5,
-+ EGL_ALPHA_SIZE, 0,
-+ EGL_NONE,
-+ };
-+ static const EGLint conf_att_gles[] = {
-+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
-+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-+ EGL_RED_SIZE, 5,
-+ EGL_GREEN_SIZE, 5,
-+ EGL_BLUE_SIZE, 5,
-+ EGL_ALPHA_SIZE, 0,
-+ EGL_NONE,
-+ };
-+ EGLint major, minor;
-+ EGLBoolean b;
-+ EGLint n;
-+ bool gles = (mode == DISPLAY_GL_MODE_ES);
-+
-+ qemu_egl_display = dpy;
-+
-+ b = eglInitialize(qemu_egl_display, &major, &minor);
-+ if (b == EGL_FALSE) {
-+ error_report("egl: eglInitialize failed");
-+ return -1;
-+ }
-+
-+ b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
-+ if (b == EGL_FALSE) {
-+ error_report("egl: eglBindAPI failed (%s mode)",
-+ gles ? "gles" : "core");
-+ return -1;
-+ }
-+
-+ b = eglChooseConfig(qemu_egl_display,
-+ gles ? conf_att_gles : conf_att_core,
-+ &qemu_egl_config, 1, &n);
-+ if (b == EGL_FALSE || n != 1) {
-+ error_report("egl: eglChooseConfig failed (%s mode)",
-+ gles ? "gles" : "core");
-+ return -1;
-+ }
-+
-+ qemu_egl_mode = gles ? DISPLAY_GL_MODE_ES : DISPLAY_GL_MODE_CORE;
-+ return 0;
-+}
-+
-+int qemu_egl_init_dpy_cocoa(DisplayGLMode mode)
-+{
-+ EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-+ if (dpy == EGL_NO_DISPLAY) {
-+ error_report("egl: eglGetDisplay failed");
-+ return -1;
-+ }
-+
-+ return qemu_egl_init_dpy(dpy, mode);
-+}
-+
- /* ---------------------------------------------------------------------- */
-
--#if defined(CONFIG_X11) || defined(CONFIG_GBM) || defined(CONFIG_ANGLE) || defined(WIN32)
-+#if defined(CONFIG_X11) || defined(CONFIG_GBM) || defined(WIN32)
-
- /*
- * Taken from glamor_egl.h from the Xorg xserver, which is MIT licensed
-@@ -504,15 +568,16 @@ bool qemu_egl_destroy_surface(EGLSurface surface)
- * platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem
- * like mesa will be able to advertise these (even though it can do EGL 1.5).
- */
--static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native,
-- EGLenum platform)
-+static int qemu_egl_init_dpy_platform(EGLNativeDisplayType native,
-+ EGLenum platform,
-+ DisplayGLMode mode)
- {
- EGLDisplay dpy = EGL_NO_DISPLAY;
-
- /* In practise any EGL 1.5 implementation would support the EXT extension */
- if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) {
- if (platform != 0) {
-- dpy = eglGetPlatformDisplayEXT(platform, (void *)native, NULL);
-+ dpy = eglGetPlatformDisplayEXT(platform, native, NULL);
- }
- }
-
-@@ -520,66 +585,13 @@ static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native,
- /* fallback */
- dpy = eglGetDisplay(native);
- }
-- return dpy;
--}
--
--static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
-- EGLenum platform,
-- DisplayGLMode mode)
--{
-- static const EGLint conf_att_core[] = {
-- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
-- EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
-- EGL_RED_SIZE, 5,
-- EGL_GREEN_SIZE, 5,
-- EGL_BLUE_SIZE, 5,
-- EGL_ALPHA_SIZE, 0,
-- EGL_NONE,
-- };
-- static const EGLint conf_att_gles[] = {
-- EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
-- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-- EGL_RED_SIZE, 5,
-- EGL_GREEN_SIZE, 5,
-- EGL_BLUE_SIZE, 5,
-- EGL_ALPHA_SIZE, 0,
-- EGL_NONE,
-- };
-- EGLint major, minor;
-- EGLBoolean b;
-- EGLint n;
-- bool gles = (mode == DISPLAY_GL_MODE_ES);
--
-- qemu_egl_display = qemu_egl_get_display(dpy, platform);
-- if (qemu_egl_display == EGL_NO_DISPLAY) {
-- error_report("egl: eglGetDisplay failed: %s", qemu_egl_get_error_string());
-- return -1;
-- }
--
-- b = eglInitialize(qemu_egl_display, &major, &minor);
-- if (b == EGL_FALSE) {
-- error_report("egl: eglInitialize failed: %s", qemu_egl_get_error_string());
-- return -1;
-- }
--
-- b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
-- if (b == EGL_FALSE) {
-- error_report("egl: eglBindAPI failed (%s mode): %s",
-- gles ? "gles" : "core", qemu_egl_get_error_string());
-- return -1;
-- }
-
-- b = eglChooseConfig(qemu_egl_display,
-- gles ? conf_att_gles : conf_att_core,
-- &qemu_egl_config, 1, &n);
-- if (b == EGL_FALSE || n != 1) {
-- error_report("egl: eglChooseConfig failed (%s mode): %s",
-- gles ? "gles" : "core", qemu_egl_get_error_string());
-+ if (dpy == EGL_NO_DISPLAY) {
-+ error_report("egl: eglGetDisplay failed");
- return -1;
- }
-
-- qemu_egl_mode = gles ? DISPLAY_GL_MODE_ES : DISPLAY_GL_MODE_CORE;
-- return 0;
-+ return qemu_egl_init_dpy(dpy, mode);
- }
-
- #endif
-@@ -588,18 +600,18 @@ static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
- int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode)
- {
- #ifdef EGL_KHR_platform_x11
-- return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR, mode);
-+ return qemu_egl_init_dpy_platform(dpy, EGL_PLATFORM_X11_KHR, mode);
- #else
-- return qemu_egl_init_dpy(dpy, 0, mode);
-+ return qemu_egl_init_dpy_platform(dpy, 0, mode);
- #endif
- }
-
- int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode)
- {
- #ifdef EGL_MESA_platform_gbm
-- return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA, mode);
-+ return qemu_egl_init_dpy_platform(dpy, EGL_PLATFORM_GBM_MESA, mode);
- #else
-- return qemu_egl_init_dpy(dpy, 0, mode);
-+ return qemu_egl_init_dpy_platform(dpy, 0, mode);
- #endif
- }
- #endif
-@@ -643,15 +655,6 @@ int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode)
- }
- #endif
-
--#if defined(CONFIG_ANGLE)
--
--int qemu_egl_init_dpy_angle(DisplayGLMode mode)
--{
-- return qemu_egl_init_dpy(EGL_DEFAULT_DISPLAY, EGL_PLATFORM_ANGLE_ANGLE, mode);
--}
--
--#endif
--
- bool qemu_egl_has_dmabuf(void)
- {
- if (qemu_egl_display == EGL_NO_DISPLAY) {
-diff --git a/ui/spice-core.c b/ui/spice-core.c
-index 43715cb60d..5b0fa38ecf 100644
---- a/ui/spice-core.c
-+++ b/ui/spice-core.c
-@@ -52,7 +52,7 @@ static int spice_have_target_host;
-
- static QemuThread me;
-
--#ifdef CONFIG_ANGLE
-+#ifdef CONFIG_EGL
- extern EGLContext spice_gl_ctx;
- #endif
-
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index f56f56dd71..67aaf9ae89 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -29,7 +29,7 @@
- #ifdef CONFIG_IOSURFACE
- #include
- #endif
--#ifdef CONFIG_ANGLE
-+#ifdef CONFIG_EGL
- #include
- #include
- #endif
-@@ -38,7 +38,7 @@
-
- bool spice_opengl;
-
--#ifdef CONFIG_ANGLE
-+#ifdef CONFIG_EGL
- EGLContext spice_gl_ctx;
- #endif
-
-@@ -806,6 +806,10 @@ static const DisplayChangeListenerOps display_listener_ops = {
-
- #if defined(CONFIG_IOSURFACE)
-
-+#ifndef EGL_IOSURFACE_WRITE_HINT_ANGLE
-+#define EGL_IOSURFACE_WRITE_HINT_ANGLE (0x0002)
-+#endif
-+
- static void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef key, int32_t value)
- {
- CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
-@@ -833,7 +837,7 @@ static int spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height
- return 0;
- }
-
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- EGLint target = 0;
- GLenum tex_target = 0;
- if (eglGetConfigAttrib(qemu_egl_display,
-@@ -892,7 +896,7 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- if (!ssd->iosurface) {
- return;
- }
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- eglMakeCurrent(qemu_egl_display, ssd->esurface, ssd->esurface, spice_gl_ctx);
- eglReleaseTexImage(qemu_egl_display, ssd->esurface, EGL_BACK_BUFFER);
- egl_fb_destroy(&ssd->iosurface_fb);
-@@ -958,7 +962,7 @@ static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bo
- return;
- }
-
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- eglMakeCurrent(qemu_egl_display, ssd->esurface, ssd->esurface, spice_gl_ctx);
- egl_texture_blit(ssd->gls, &ssd->iosurface_fb, &tmp_fb, flip);
- #endif
-@@ -1113,7 +1117,7 @@ static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
- #if defined(CONFIG_GBM)
- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- qemu_egl_rn_ctx);
--#elif defined(CONFIG_ANGLE)
-+#elif defined(CONFIG_EGL)
- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
- spice_gl_ctx);
- #endif
-@@ -1132,7 +1136,7 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
- #if defined(CONFIG_IOSURFACE)
- spice_iosurface_destroy(ssd);
- #endif
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- ssd->tex_id = -1;
- #endif
- }
-@@ -1154,7 +1158,7 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
- fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL);
- #elif defined(CONFIG_IOSURFACE)
- if (spice_iosurface_resize(ssd, backing_width, backing_height)) {
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- ssd->tex_id = tex_id;
- ssd->y_0_top = y_0_top;
- #endif
-@@ -1324,7 +1328,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- !y_0_top, false, ptr_x, ptr_y, 1.0, 1.0);
- glFlush();
- }
--#elif defined(CONFIG_ANGLE) && defined(CONFIG_IOSURFACE)
-+#elif defined(CONFIG_EGL) && defined(CONFIG_IOSURFACE)
- GLuint tex_id = ssd->tex_id;
- y_0_top = ssd->y_0_top;
- spice_iosurface_blit(ssd, tex_id, !y_0_top);
-@@ -1395,7 +1399,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
- ssd->iosurface = NULL;
- ssd->surface_send_fd = -1;
- #endif
--#if defined(CONFIG_ANGLE)
-+#if defined(CONFIG_EGL)
- ssd->esurface = EGL_NO_SURFACE;
- ssd->tex_id = -1;
- #endif
-@@ -1444,8 +1448,8 @@ void qemu_spice_display_early_init(void)
- }
- #if defined(CONFIG_GBM)
- egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
--#elif defined(CONFIG_ANGLE)
-- if (qemu_egl_init_dpy_angle(DISPLAY_GL_MODE_ES)) {
-+#elif defined(CONFIG_EGL)
-+ if (qemu_egl_init_dpy_cocoa(DISPLAY_GL_MODE_ES)) {
- error_report("SPICE GL failed to initialize ANGLE display");
- exit(1);
- }
---
-2.41.0
-
-From 6d6bc66ecf1e8e409b780aaab4988ac8ebcd1baf Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 17:03:42 -0800
-Subject: [PATCH 8/9] ui/spice-display: refactor IOSurface code for EGL
-
----
- ui/spice-display.c | 102 ++++++++++++++++++++++++++++-----------------
- 1 file changed, 63 insertions(+), 39 deletions(-)
-
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 67aaf9ae89..931258ef96 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -817,26 +817,9 @@ static void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef
- CFRelease(number);
- }
-
--static int spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height)
-+static bool spice_iosurface_create_egl(SimpleSpiceDisplay *ssd, int width, int height,
-+ IOSurfaceRef surface)
- {
-- CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
-- kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-- AddIntegerValue(dict, kIOSurfaceWidth, width);
-- AddIntegerValue(dict, kIOSurfaceHeight, height);
-- AddIntegerValue(dict, kIOSurfacePixelFormat, 'BGRA');
-- AddIntegerValue(dict, kIOSurfaceBytesPerElement, 4);
--#if TARGET_OS_OSX
-- CFDictionaryAddValue(dict, kIOSurfaceIsGlobal, kCFBooleanTrue);
--#endif
--
-- ssd->iosurface = IOSurfaceCreate(dict);
-- CFRelease(dict);
--
-- if (!ssd->iosurface) {
-- error_report("spice_iosurface_create: IOSurfaceCreate failed");
-- return 0;
-- }
--
- #if defined(CONFIG_EGL)
- EGLint target = 0;
- GLenum tex_target = 0;
-@@ -845,7 +828,7 @@ static int spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height
- EGL_BIND_TO_TEXTURE_TARGET_ANGLE,
- &target) != EGL_TRUE) {
- error_report("spice_iosurface_create: eglGetConfigAttrib failed");
-- goto gl_error;
-+ return false;
- }
- if (target == EGL_TEXTURE_2D) {
- tex_target = GL_TEXTURE_2D;
-@@ -853,7 +836,7 @@ static int spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height
- tex_target = GL_TEXTURE_RECTANGLE_ANGLE;
- } else {
- error_report("spice_iosurface_create: unsupported texture target");
-- goto gl_error;
-+ return false;
- }
-
- const EGLint attribs[] = {
-@@ -869,33 +852,57 @@ static int spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height
- };
- ssd->esurface = qemu_egl_init_buffer_surface(spice_gl_ctx,
- EGL_IOSURFACE_ANGLE,
-- ssd->iosurface,
-+ surface,
- attribs);
-
- if (ssd->esurface == NULL) {
-- goto gl_error;
-+ return false;
- }
-
- egl_fb_setup_new_tex_target(&ssd->iosurface_fb, width, height, tex_target);
-
- eglBindTexImage(qemu_egl_display, ssd->esurface, EGL_BACK_BUFFER);
-
-- return 1;
--gl_error:
-- CFRelease(ssd->iosurface);
-- ssd->iosurface = NULL;
-- return 0;
-+ return true;
- #else
- error_report("spice_iosurface_create: ANGLE not found");
-- return 0;
-+ return false;
- #endif
- }
-
--static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
-+static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height)
- {
-- if (!ssd->iosurface) {
-- return;
-+ IOSurfaceRef surface;
-+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
-+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-+ AddIntegerValue(dict, kIOSurfaceWidth, width);
-+ AddIntegerValue(dict, kIOSurfaceHeight, height);
-+ AddIntegerValue(dict, kIOSurfacePixelFormat, 'BGRA');
-+ AddIntegerValue(dict, kIOSurfaceBytesPerElement, 4);
-+#if TARGET_OS_OSX
-+ CFDictionaryAddValue(dict, kIOSurfaceIsGlobal, kCFBooleanTrue);
-+#endif
-+
-+ surface = IOSurfaceCreate(dict);
-+ CFRelease(dict);
-+
-+ if (!surface) {
-+ error_report("spice_iosurface_create: IOSurfaceCreate failed");
-+ return false;
-+ }
-+
-+ if (!spice_iosurface_create_egl(ssd, width, height, surface)) {
-+ CFRelease(surface);
-+ return false;
- }
-+
-+ ssd->iosurface = surface;
-+
-+ return true;
-+}
-+
-+static void spice_iosurface_destroy_egl(SimpleSpiceDisplay *ssd)
-+{
- #if defined(CONFIG_EGL)
- eglMakeCurrent(qemu_egl_display, ssd->esurface, ssd->esurface, spice_gl_ctx);
- eglReleaseTexImage(qemu_egl_display, ssd->esurface, EGL_BACK_BUFFER);
-@@ -903,6 +910,16 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- qemu_egl_destroy_surface(ssd->esurface);
- ssd->esurface = EGL_NO_SURFACE;
- #endif
-+}
-+
-+static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
-+{
-+ if (!ssd->iosurface) {
-+ return;
-+ }
-+
-+ spice_iosurface_destroy_egl(ssd);
-+
- if (ssd->surface_send_fd > -1) {
- // this sends POLLHUP and indicates that any unread data is stale
- // and should not be used
-@@ -914,7 +931,7 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- ssd->iosurface = NULL;
- }
-
--static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
-+static bool spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height)
- {
- if (ssd->iosurface) {
- if (IOSurfaceGetHeight(ssd->iosurface) != height ||
-@@ -922,7 +939,7 @@ static int spice_iosurface_resize(SimpleSpiceDisplay *ssd, int width, int height
- spice_iosurface_destroy(ssd);
- return spice_iosurface_create(ssd, width, height);
- } else {
-- return 1;
-+ return true;
- }
- } else {
- return spice_iosurface_create(ssd, width, height);
-@@ -955,19 +972,26 @@ static int spice_iosurface_create_fd(SimpleSpiceDisplay *ssd, int *fourcc)
- return fds[0];
- }
-
--static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bool flip)
-+static void spice_iosurface_blit_egl(SimpleSpiceDisplay *ssd, GLuint src_texture,
-+ bool flip)
- {
-+#if defined(CONFIG_EGL)
- egl_fb tmp_fb = { .texture = src_texture, .texture_target = GL_TEXTURE_2D };
-- if (!ssd->iosurface) {
-- return;
-- }
-
--#if defined(CONFIG_EGL)
- eglMakeCurrent(qemu_egl_display, ssd->esurface, ssd->esurface, spice_gl_ctx);
- egl_texture_blit(ssd->gls, &ssd->iosurface_fb, &tmp_fb, flip);
- #endif
- }
-
-+static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bool flip)
-+{
-+ if (!ssd->iosurface) {
-+ return;
-+ }
-+
-+ spice_iosurface_blit_egl(ssd, src_texture, flip);
-+}
-+
- #endif
-
- static void qemu_spice_gl_monitor_config(SimpleSpiceDisplay *ssd,
---
-2.41.0
-
-From 47f3d4eb42d9fa8d035fb67965840a16fcdc018d Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sun, 4 Jan 2026 20:22:19 -0800
-Subject: [PATCH 9/9] ui/spice-display: support rendering with CGL
-
----
- include/ui/spice-display.h | 4 +-
- meson.build | 2 +
- ui/meson.build | 3 +
- ui/spice-core.c | 2 +-
- ui/spice-display.c | 231 ++++++++++++++++++++++++++++++-------
- 5 files changed, 199 insertions(+), 43 deletions(-)
-
-diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
-index 5a0b5a0c8f..ec55194139 100644
---- a/include/ui/spice-display.h
-+++ b/include/ui/spice-display.h
-@@ -141,9 +141,9 @@ struct SimpleSpiceDisplay {
- #if defined(CONFIG_EGL)
- EGLSurface esurface;
- egl_fb iosurface_fb;
-+#endif
- GLuint tex_id;
- bool y_0_top;
--#endif
- bool render_cursor;
-
- egl_fb guest_fb;
-@@ -167,8 +167,6 @@ struct SimpleSpiceCursor {
- QXLCursor cursor;
- };
-
--extern bool spice_opengl;
--
- int qemu_spice_rect_is_empty(const QXLRect* r);
- void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r);
-
-diff --git a/meson.build b/meson.build
-index fff146e5c2..af9284fb29 100644
---- a/meson.build
-+++ b/meson.build
-@@ -836,6 +836,7 @@ coref = []
- iokit = []
- iosurface = not_found
- pvg = not_found
-+quartzcore = not_found
- emulator_link_args = []
- midl = not_found
- widl = not_found
-@@ -860,6 +861,7 @@ elif host_os == 'darwin'
- host_dsosuf = '.dylib'
- pvg = dependency('appleframeworks', modules: ['ParavirtualizedGraphics', 'Metal'],
- required: get_option('pvg'))
-+ quartzcore = dependency('appleframeworks', modules: ['OpenGL', 'QuartzCore'], required: false)
- elif host_os == 'sunos'
- socket = [cc.find_library('socket'),
- cc.find_library('nsl'),
-diff --git a/ui/meson.build b/ui/meson.build
-index a731a60f3a..8acb9c0ef0 100644
---- a/ui/meson.build
-+++ b/ui/meson.build
-@@ -141,6 +141,9 @@ if spice.found()
- if iosurface.found()
- spice_core_ss.add(iosurface)
- endif
-+ if quartzcore.found()
-+ spice_core_ss.add(quartzcore)
-+ endif
- ui_modules += {'spice-core' : spice_core_ss}
-
- if gio.found()
-diff --git a/ui/spice-core.c b/ui/spice-core.c
-index 5b0fa38ecf..91817b1239 100644
---- a/ui/spice-core.c
-+++ b/ui/spice-core.c
-@@ -510,7 +510,7 @@ static QemuOptsList qemu_spice_opts = {
- #ifdef HAVE_SPICE_GL
- },{
- .name = "gl",
-- .type = QEMU_OPT_BOOL,
-+ .type = QEMU_OPT_STRING,
- },{
- .name = "rendernode",
- .type = QEMU_OPT_STRING,
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 931258ef96..97a915a7ee 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -28,6 +28,10 @@
- #include "trace.h"
- #ifdef CONFIG_IOSURFACE
- #include
-+#if TARGET_OS_OSX
-+#import
-+#define HAVE_SPICE_MAC_CGL
-+#endif
- #endif
- #ifdef CONFIG_EGL
- #include
-@@ -36,11 +40,8 @@
-
- #include "ui/spice-display.h"
-
--bool spice_opengl;
--
--#ifdef CONFIG_EGL
--EGLContext spice_gl_ctx;
--#endif
-+static DisplayGLMode spice_opengl;
-+QEMUGLContext spice_gl_ctx;
-
- int qemu_spice_rect_is_empty(const QXLRect* r)
- {
-@@ -870,6 +871,39 @@ static bool spice_iosurface_create_egl(SimpleSpiceDisplay *ssd, int width, int h
- #endif
- }
-
-+static bool spice_iosurface_create_cgl(SimpleSpiceDisplay *ssd, int width, int height,
-+ IOSurfaceRef surface)
-+{
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ GLuint tex;
-+
-+ CGLSetCurrentContext(spice_gl_ctx);
-+ glGenTextures(1, &tex);
-+ glBindTexture(GL_TEXTURE_RECTANGLE, tex);
-+ if (CGLTexImageIOSurface2D(spice_gl_ctx,
-+ GL_TEXTURE_RECTANGLE,
-+ GL_RGBA,
-+ width,
-+ height,
-+ GL_BGRA,
-+ GL_UNSIGNED_INT_8_8_8_8_REV,
-+ surface,
-+ 0) != kCGLNoError) {
-+ glDeleteTextures(1, &tex);
-+ error_report("spice_iosurface_create: failed to create CGL texture");
-+ return false;
-+ }
-+
-+ egl_fb_setup_for_tex_target(&ssd->iosurface_fb, width, height,
-+ tex, GL_TEXTURE_RECTANGLE, true);
-+
-+ return true;
-+#else
-+ error_report("spice_iosurface_create: CGL not found");
-+ return false;
-+#endif
-+}
-+
- static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int height)
- {
- IOSurfaceRef surface;
-@@ -891,9 +925,16 @@ static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int heigh
- return false;
- }
-
-- if (!spice_iosurface_create_egl(ssd, width, height, surface)) {
-- CFRelease(surface);
-- return false;
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+ if (!spice_iosurface_create_cgl(ssd, width, height, surface)) {
-+ CFRelease(surface);
-+ return false;
-+ }
-+ } else {
-+ if (!spice_iosurface_create_egl(ssd, width, height, surface)) {
-+ CFRelease(surface);
-+ return false;
-+ }
- }
-
- ssd->iosurface = surface;
-@@ -912,13 +953,25 @@ static void spice_iosurface_destroy_egl(SimpleSpiceDisplay *ssd)
- #endif
- }
-
-+static void spice_iosurface_destroy_cgl(SimpleSpiceDisplay *ssd)
-+{
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ CGLSetCurrentContext(spice_gl_ctx);
-+ egl_fb_destroy(&ssd->iosurface_fb);
-+#endif
-+}
-+
- static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- {
- if (!ssd->iosurface) {
- return;
- }
-
-- spice_iosurface_destroy_egl(ssd);
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+ spice_iosurface_destroy_cgl(ssd);
-+ } else {
-+ spice_iosurface_destroy_egl(ssd);
-+ }
-
- if (ssd->surface_send_fd > -1) {
- // this sends POLLHUP and indicates that any unread data is stale
-@@ -983,15 +1036,67 @@ static void spice_iosurface_blit_egl(SimpleSpiceDisplay *ssd, GLuint src_texture
- #endif
- }
-
-+static void spice_iosurface_blit_cgl(SimpleSpiceDisplay *ssd, GLuint src_texture,
-+ bool flip)
-+{
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ egl_fb tmp_fb = { .texture = src_texture, .texture_target = GL_TEXTURE_2D };
-+
-+ CGLSetCurrentContext(spice_gl_ctx);
-+ egl_texture_blit(ssd->gls, &ssd->iosurface_fb, &tmp_fb, flip);
-+#endif
-+}
-+
- static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bool flip)
- {
- if (!ssd->iosurface) {
- return;
- }
-
-- spice_iosurface_blit_egl(ssd, src_texture, flip);
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+ spice_iosurface_blit_cgl(ssd, src_texture, flip);
-+ } else {
-+ spice_iosurface_blit_egl(ssd, src_texture, flip);
-+ }
-+}
-+
-+#endif
-+
-+#ifdef HAVE_SPICE_MAC_CGL
-+static CGLContextObj spice_cgl_create_context(CGLContextObj share)
-+{
-+ CGLPixelFormatAttribute attrs[] = {
-+ kCGLPFAOpenGLProfile,
-+ /* Request 3.2 core; macOS delivers 4.1 for this selector. */
-+ (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
-+ kCGLPFAColorSize, 32,
-+ kCGLPFADoubleBuffer,
-+ 0
-+ };
-+
-+ CGLPixelFormatObj pix;
-+ GLint npix;
-+ CGLChoosePixelFormat(attrs, &pix, &npix);
-+
-+ CGLContextObj ctx;
-+ CGLCreateContext(pix, share, &ctx);
-+ CGLReleasePixelFormat(pix);
-+
-+ return ctx;
- }
-
-+static void spice_cgl_destroy_context(CGLContextObj ctx)
-+{
-+ if (CGLGetCurrentContext() == ctx) {
-+ CGLSetCurrentContext(NULL);
-+ }
-+ CGLReleaseContext(ctx);
-+}
-+
-+static int spice_cgl_make_context_current(CGLContextObj ctx)
-+{
-+ return CGLSetCurrentContext(ctx);
-+}
- #endif
-
- static void qemu_spice_gl_monitor_config(SimpleSpiceDisplay *ssd,
-@@ -1043,6 +1148,9 @@ static void qemu_spice_gl_block_timer(void *opaque)
- warn_report("spice: no gl-draw-done within one second");
- }
-
-+static int qemu_spice_gl_make_context_current(DisplayGLCtx *dgc,
-+ QEMUGLContext ctx);
-+
- static void spice_gl_refresh(DisplayChangeListener *dcl)
- {
- SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
-@@ -1052,6 +1160,7 @@ static void spice_gl_refresh(DisplayChangeListener *dcl)
- return;
- }
-
-+ qemu_spice_gl_make_context_current(NULL, spice_gl_ctx);
- graphic_hw_update(dcl->con);
- if (ssd->gl_updates && ssd->have_surface) {
- qemu_spice_gl_block(ssd, true);
-@@ -1070,6 +1179,7 @@ static void spice_gl_update(DisplayChangeListener *dcl,
- {
- SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
-
-+ qemu_spice_gl_make_context_current(NULL, spice_gl_ctx);
- surface_gl_update_texture(ssd->gls, ssd->ds, x, y, w, h);
- #if defined(CONFIG_IOSURFACE)
- if (!qemu_console_is_gl_blocked(ssd->dcl.con)) {
-@@ -1087,6 +1197,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
- int fd = -1;
- int width = 0, height = 0;
-
-+ qemu_spice_gl_make_context_current(NULL, spice_gl_ctx);
- if (ssd->ds) {
- surface_gl_destroy_texture(ssd->gls, ssd->ds);
- }
-@@ -1138,14 +1249,43 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
- static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
- QEMUGLParams *params)
- {
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ return spice_cgl_create_context(spice_gl_ctx);
-+#endif
-+ } else {
- #if defined(CONFIG_GBM)
-- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
-- qemu_egl_rn_ctx);
-+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
-+ qemu_egl_rn_ctx);
- #elif defined(CONFIG_EGL)
-- eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
-- spice_gl_ctx);
-+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
-+ spice_gl_ctx);
-+#endif
-+ return qemu_egl_create_context(dgc, params);
-+ }
-+}
-+
-+static void qemu_spice_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
-+{
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ spice_cgl_destroy_context(ctx);
-+#endif
-+ } else {
-+ qemu_egl_destroy_context(dgc, ctx);
-+ }
-+}
-+
-+static int qemu_spice_gl_make_context_current(DisplayGLCtx *dgc,
-+ QEMUGLContext ctx)
-+{
-+ if (spice_opengl == DISPLAY_GL_MODE_CORE) {
-+#if defined(HAVE_SPICE_MAC_CGL)
-+ return spice_cgl_make_context_current(ctx);
- #endif
-- return qemu_egl_create_context(dgc, params);
-+ } else {
-+ return qemu_egl_make_context_current(dgc, ctx);
-+ }
- }
-
- static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
-@@ -1160,9 +1300,7 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
- #if defined(CONFIG_IOSURFACE)
- spice_iosurface_destroy(ssd);
- #endif
--#if defined(CONFIG_EGL)
- ssd->tex_id = -1;
--#endif
- }
-
- static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
-@@ -1182,10 +1320,8 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
- fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL);
- #elif defined(CONFIG_IOSURFACE)
- if (spice_iosurface_resize(ssd, backing_width, backing_height)) {
--#if defined(CONFIG_EGL)
- ssd->tex_id = tex_id;
- ssd->y_0_top = y_0_top;
--#endif
- fd = spice_iosurface_create_fd(ssd, &fourcc);
- } else {
- fd = -1;
-@@ -1352,7 +1488,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- !y_0_top, false, ptr_x, ptr_y, 1.0, 1.0);
- glFlush();
- }
--#elif defined(CONFIG_EGL) && defined(CONFIG_IOSURFACE)
-+#elif defined(CONFIG_IOSURFACE)
- GLuint tex_id = ssd->tex_id;
- y_0_top = ssd->y_0_top;
- spice_iosurface_blit(ssd, tex_id, !y_0_top);
-@@ -1396,8 +1532,8 @@ qemu_spice_is_compatible_dcl(DisplayGLCtx *dgc,
- static const DisplayGLCtxOps gl_ctx_ops = {
- .dpy_gl_ctx_is_compatible_dcl = qemu_spice_is_compatible_dcl,
- .dpy_gl_ctx_create = qemu_spice_gl_create_context,
-- .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
-- .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
-+ .dpy_gl_ctx_destroy = qemu_spice_gl_destroy_context,
-+ .dpy_gl_ctx_make_current = qemu_spice_gl_make_context_current,
- };
-
- #endif /* HAVE_SPICE_GL */
-@@ -1410,7 +1546,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
-
- ssd->dcl.ops = &display_listener_ops;
- #ifdef HAVE_SPICE_GL
-- if (spice_opengl) {
-+ if (spice_opengl != DISPLAY_GL_MODE_OFF) {
- ssd->dcl.ops = &display_listener_gl_ops;
- ssd->dgc.ops = &gl_ctx_ops;
- ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
-@@ -1425,8 +1561,8 @@ static void qemu_spice_display_init_one(QemuConsole *con)
- #endif
- #if defined(CONFIG_EGL)
- ssd->esurface = EGL_NO_SURFACE;
-- ssd->tex_id = -1;
- #endif
-+ ssd->tex_id = -1;
- }
- #endif
- ssd->dcl.con = con;
-@@ -1449,7 +1585,7 @@ static void qemu_spice_display_init_one(QemuConsole *con)
-
- qemu_spice_create_host_memslot(ssd);
-
-- if (spice_opengl) {
-+ if (spice_opengl != DISPLAY_GL_MODE_OFF) {
- qemu_console_set_display_gl_ctx(con, &ssd->dgc);
- }
- register_displaychangelistener(&ssd->dcl);
-@@ -1461,34 +1597,51 @@ void qemu_spice_display_early_init(void)
- QemuOptsList *olist = qemu_find_opts("spice");
- QemuOpts *opts = QTAILQ_FIRST(&olist->head);
- int port, tls_port;
-+ const char *gl_str;
-+ bool gl_on = false, gl_core = false;
-
- port = qemu_opt_get_number(opts, "port", 0);
- tls_port = qemu_opt_get_number(opts, "tls-port", 0);
-- if (qemu_opt_get_bool(opts, "gl", 0)) {
-+ gl_str = qemu_opt_get(opts, "gl");
-+ qapi_bool_parse("", gl_str, &gl_on, NULL);
-+ gl_core = g_str_equal(gl_str, "core");
-+ gl_on = gl_on || gl_core || g_str_equal(gl_str, "es");
-+ if (gl_on) {
- if ((port != 0) || (tls_port != 0)) {
- error_report("SPICE GL support is local-only for now and "
- "incompatible with -spice port/tls-port");
- exit(1);
- }
-+ if (gl_core) {
-+#ifdef HAVE_SPICE_MAC_CGL
-+ spice_gl_ctx = spice_cgl_create_context(NULL);
-+ spice_cgl_make_context_current(spice_gl_ctx);
-+ spice_opengl = DISPLAY_GL_MODE_CORE;
-+#else
-+ error_report("No backend to support SPICE Core GL");
-+ exit(1);
-+#endif
-+ } else {
- #if defined(CONFIG_GBM)
-- egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
-+ egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
- #elif defined(CONFIG_EGL)
-- if (qemu_egl_init_dpy_cocoa(DISPLAY_GL_MODE_ES)) {
-- error_report("SPICE GL failed to initialize ANGLE display");
-- exit(1);
-- }
-+ if (qemu_egl_init_dpy_cocoa(DISPLAY_GL_MODE_ES)) {
-+ error_report("SPICE GL failed to initialize ANGLE display");
-+ exit(1);
-+ }
-
-- spice_gl_ctx = qemu_egl_init_ctx();
-- if (!spice_gl_ctx) {
-- error_report("egl: egl_init_ctx failed");
-- exit(1);
-- }
-+ spice_gl_ctx = qemu_egl_init_ctx();
-+ if (!spice_gl_ctx) {
-+ error_report("egl: egl_init_ctx failed");
-+ exit(1);
-+ }
-+ spice_opengl = DISPLAY_GL_MODE_ES;
- #else
-- error_report("No backend to support SPICE GL");
-- exit(1);
-+ error_report("No backend to support SPICE GL");
-+ exit(1);
- #endif
-+ }
- display_opengl = 1;
-- spice_opengl = 1;
- }
- #endif
- }
---
-2.41.0
-
-From 5ab253b54bf6a4081752253751d0f8958e8bb53f Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 00:07:02 -0800
-Subject: [PATCH 1/9] virtio-gpu-virgl: update virglrenderer defines
-
-In order to support additional native texture types, we need to update the
-defines in virglrenderer. The changes are backwards compatible and so
-builds should work with either the new version or the old version.
-
-https://gitlab.freedesktop.org/virgl/virglrenderer/-/merge_requests/1583
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 26 +++++++++++++++++++++++---
- 1 file changed, 23 insertions(+), 3 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index eac49fbae8..d55c2c1625 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -24,6 +24,8 @@
-
- #include
-
-+#define NATIVE_HANDLE_SUPPORT_VERSION (1)
-+
- struct virtio_gpu_virgl_resource {
- struct virtio_gpu_simple_resource base;
- MemoryRegion *mr;
-@@ -423,12 +425,28 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
- memset(&ext, 0, sizeof(ext));
- ret = virgl_renderer_resource_get_info_ext(ss.resource_id, &ext);
- info = ext.base;
-+ /* fallback to older version */
- native = (ScanoutTextureNative){
- .type = ext.d3d_tex2d ? SCANOUT_TEXTURE_NATIVE_TYPE_D3D :
- SCANOUT_TEXTURE_NATIVE_TYPE_NONE,
- .handle = ext.d3d_tex2d,
- };
--#else
-+#if VIRGL_RENDERER_RESOURCE_INFO_EXT_VERSION >= NATIVE_HANDLE_SUPPORT_VERSION
-+ if (ext.version >= VIRGL_RENDERER_RESOURCE_INFO_EXT_VERSION) {
-+ switch (ext.native_type) {
-+ case VIRGL_NATIVE_HANDLE_NONE:
-+ case VIRGL_NATIVE_HANDLE_D3D_TEX2D: {
-+ /* already handled above */
-+ break;
-+ }
-+ default: {
-+ /* ignore unsupported hint texture type */
-+ break;
-+ }
-+ }
-+ }
-+#endif
-+#else /* VIRGL_VERSION_MAJOR < 1 */
- memset(&info, 0, sizeof(info));
- ret = virgl_renderer_resource_get_info(ss.resource_id, &info);
- #endif
-@@ -1107,11 +1125,13 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
- virtio_gpu_3d_cbs.get_egl_display = virgl_get_egl_display;
- }
- #endif
--#ifdef VIRGL_RENDERER_D3D11_SHARE_TEXTURE
- if (qemu_egl_angle_native_device) {
-+#if defined(VIRGL_RENDERER_NATIVE_SHARE_TEXTURE)
-+ flags |= VIRGL_RENDERER_NATIVE_SHARE_TEXTURE;
-+#elif defined(VIRGL_RENDERER_D3D11_SHARE_TEXTURE) && defined(WIN32)
- flags |= VIRGL_RENDERER_D3D11_SHARE_TEXTURE;
-- }
- #endif
-+ }
- #if VIRGL_VERSION_MAJOR >= 1
- if (virtio_gpu_venus_enabled(g->parent_obj.conf)) {
- flags |= VIRGL_RENDERER_VENUS | VIRGL_RENDERER_RENDER_SERVER;
---
-2.41.0
-
-From 5245d2d926c8fcef6bbae0a95b286ed81c42b1b3 Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 00:11:27 -0800
-Subject: [PATCH 2/9] virtio-gpu-virgl: support scanout of Metal textures
-
-When supported, virglrenderer will return a MTLTexture handle that can be
-directly used for scanout.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 12 +++++++++++-
- hw/display/virtio-gpu.c | 10 ++++++++--
- include/ui/console.h | 1 +
- meson.build | 3 +++
- 4 files changed, 23 insertions(+), 3 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index d55c2c1625..98c9bc4d89 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -434,6 +434,13 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
- #if VIRGL_RENDERER_RESOURCE_INFO_EXT_VERSION >= NATIVE_HANDLE_SUPPORT_VERSION
- if (ext.version >= VIRGL_RENDERER_RESOURCE_INFO_EXT_VERSION) {
- switch (ext.native_type) {
-+#ifdef CONFIG_METAL
-+ case VIRGL_NATIVE_HANDLE_METAL_TEXTURE: {
-+ native.type = SCANOUT_TEXTURE_NATIVE_TYPE_METAL;
-+ native.handle = ext.native_handle;
-+ break;
-+ }
-+#endif
- case VIRGL_NATIVE_HANDLE_NONE:
- case VIRGL_NATIVE_HANDLE_D3D_TEX2D: {
- /* already handled above */
-@@ -1134,7 +1141,10 @@ int virtio_gpu_virgl_init(VirtIOGPU *g)
- }
- #if VIRGL_VERSION_MAJOR >= 1
- if (virtio_gpu_venus_enabled(g->parent_obj.conf)) {
-- flags |= VIRGL_RENDERER_VENUS | VIRGL_RENDERER_RENDER_SERVER;
-+ flags |= VIRGL_RENDERER_VENUS;
-+#ifndef CONFIG_METAL /* Metal does not support render server */
-+ flags |= VIRGL_RENDERER_RENDER_SERVER;
-+#endif
- }
- #endif
-
-diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
-index 11a7a85750..1b1e0c4459 100644
---- a/hw/display/virtio-gpu.c
-+++ b/hw/display/virtio-gpu.c
-@@ -1471,12 +1471,18 @@ void virtio_gpu_device_realize(DeviceState *qdev, Error **errp)
- {
- VirtIODevice *vdev = VIRTIO_DEVICE(qdev);
- VirtIOGPU *g = VIRTIO_GPU(qdev);
-+ bool have_ext_memory;
-
- if (virtio_gpu_blob_enabled(g->parent_obj.conf)) {
-+#ifdef CONFIG_METAL
-+ have_ext_memory = virtio_gpu_venus_enabled(g->parent_obj.conf);
-+#else
-+ have_ext_memory = virtio_gpu_have_udmabuf();
-+#endif
- if (!virtio_gpu_rutabaga_enabled(g->parent_obj.conf) &&
- !virtio_gpu_virgl_enabled(g->parent_obj.conf) &&
-- !virtio_gpu_have_udmabuf()) {
-- error_setg(errp, "need rutabaga or udmabuf for blob resources");
-+ !have_ext_memory) {
-+ error_setg(errp, "need rutabaga or ext memory for blob resources");
- return;
- }
-
-diff --git a/include/ui/console.h b/include/ui/console.h
-index 445e563150..f96565d109 100644
---- a/include/ui/console.h
-+++ b/include/ui/console.h
-@@ -134,6 +134,7 @@ struct QemuConsoleClass {
- typedef enum ScanoutTextureNativeType {
- SCANOUT_TEXTURE_NATIVE_TYPE_NONE,
- SCANOUT_TEXTURE_NATIVE_TYPE_D3D,
-+ SCANOUT_TEXTURE_NATIVE_TYPE_METAL,
- } ScanoutTextureNativeType;
-
- typedef struct ScanoutTextureNative {
-diff --git a/meson.build b/meson.build
-index af9284fb29..3ecd4d5b3a 100644
---- a/meson.build
-+++ b/meson.build
-@@ -836,6 +836,7 @@ coref = []
- iokit = []
- iosurface = not_found
- pvg = not_found
-+metal = not_found
- quartzcore = not_found
- emulator_link_args = []
- midl = not_found
-@@ -861,6 +862,7 @@ elif host_os == 'darwin'
- host_dsosuf = '.dylib'
- pvg = dependency('appleframeworks', modules: ['ParavirtualizedGraphics', 'Metal'],
- required: get_option('pvg'))
-+ metal = dependency('appleframeworks', modules: 'Metal', required: false)
- quartzcore = dependency('appleframeworks', modules: ['OpenGL', 'QuartzCore'], required: false)
- elif host_os == 'sunos'
- socket = [cc.find_library('socket'),
-@@ -2627,6 +2629,7 @@ if xen.found()
- ('0' + xen_version[2]).substring(-2)
- config_host_data.set('CONFIG_XEN_CTRL_INTERFACE_VERSION', xen_ctrl_version)
- endif
-+config_host_data.set('CONFIG_METAL', metal.found())
- config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
- config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0])
- config_host_data.set('QEMU_VERSION_MINOR', meson.project_version().split('.')[1])
---
-2.41.0
-
-From b2f9af64f986aa44a6f1171e97fc665cc55ee0ff Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 15:27:49 -0800
-Subject: [PATCH 3/9] console: add cleanup callback for ScanoutTexture
-
-Before we introduce changes that allow for QemuConsole to take ownership
-of a texture handle, we need scaffolding that will allow us to callback
-into a cleanup function any time the ScanoutTexture becomes invalid, which
-is whenever the `scanout.kind` or `scanout.texture` gets updated.
-
-The ordering is important: we need to first update the DisplayScanout,
-then we need to notify all the listeners, and once all the listeners have
-had the chance to finish using the previous native texture, we are safe to
-call the cleanup function. This means we need to hold on to the previous
-scanout native handle locally until all listeners are notified.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 2 +-
- include/ui/console.h | 9 +++++-
- ui/console.c | 56 +++++++++++++++++++++++++++++++----
- 3 files changed, 59 insertions(+), 8 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index 98c9bc4d89..8d511ed562 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -472,7 +472,7 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g,
- info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
- info.width, info.height,
- ss.r.x, ss.r.y, ss.r.width, ss.r.height,
-- native);
-+ native, NULL);
- } else {
- dpy_gfx_replace_surface(
- g->parent_obj.scanout[ss.scanout_id].con, NULL);
-diff --git a/include/ui/console.h b/include/ui/console.h
-index f96565d109..8e7364cfde 100644
---- a/include/ui/console.h
-+++ b/include/ui/console.h
-@@ -146,6 +146,11 @@ typedef struct ScanoutTextureNative {
- .type = SCANOUT_TEXTURE_NATIVE_TYPE_NONE \
- })
-
-+/**
-+ * Cleanup callback function when ScanoutTexture is about to be destroyed
-+ */
-+typedef void (*ScanoutTextureCleanup)(ScanoutTextureNative *native);
-+
- typedef struct ScanoutTexture {
- uint32_t backing_id;
- bool backing_y_0_top;
-@@ -156,6 +161,7 @@ typedef struct ScanoutTexture {
- uint32_t width;
- uint32_t height;
- ScanoutTextureNative native;
-+ ScanoutTextureCleanup cb_cleanup;
- } ScanoutTexture;
-
- typedef struct QemuUIInfo {
-@@ -344,7 +350,8 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- uint32_t backing_id, bool backing_y_0_top,
- uint32_t backing_width, uint32_t backing_height,
- uint32_t x, uint32_t y, uint32_t w, uint32_t h,
-- ScanoutTextureNative native);
-+ ScanoutTextureNative native,
-+ ScanoutTextureCleanup cb_cleanup);
- void dpy_gl_scanout_dmabuf(QemuConsole *con,
- QemuDmaBuf *dmabuf);
- void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
-diff --git a/ui/console.c b/ui/console.c
-index d8d58fc14a..f6d7879eff 100644
---- a/ui/console.c
-+++ b/ui/console.c
-@@ -808,6 +808,41 @@ void dpy_gfx_update_full(QemuConsole *con)
- dpy_gfx_update(con, 0, 0, w, h);
- }
-
-+typedef struct ScanoutChange {
-+ ScanoutTextureNative native;
-+ ScanoutTextureCleanup cb_cleanup;
-+} ScanoutChange;
-+
-+#define SCANOUT_CHANGE_NONE ((ScanoutChange){ NO_NATIVE_TEXTURE })
-+
-+static ScanoutChange dpy_change_scanout_kind(DisplayScanout *scanout,
-+ enum display_scanout kind)
-+{
-+ ScanoutChange change = SCANOUT_CHANGE_NONE;
-+
-+ /**
-+ * We cannot cleanup until the resource is no longer in use, so we record it
-+ * You MUST call dpy_complete_scanout_change after all listeners are updated
-+ */
-+ if (scanout->kind == SCANOUT_TEXTURE && scanout->texture.cb_cleanup) {
-+ change.native = scanout->texture.native;
-+ change.cb_cleanup = scanout->texture.cb_cleanup;
-+ }
-+ scanout->kind = kind;
-+
-+ return change;
-+}
-+
-+static void dpy_complete_scanout_change(ScanoutChange *change)
-+{
-+ /**
-+ * If we previously have a texture and cleanup is required, we call it now
-+ */
-+ if (change->native.type != SCANOUT_TEXTURE_NATIVE_TYPE_NONE && change->cb_cleanup) {
-+ change->cb_cleanup(&change->native);
-+ }
-+}
-+
- void dpy_gfx_replace_surface(QemuConsole *con,
- DisplaySurface *surface)
- {
-@@ -818,6 +853,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
- DisplayChangeListener *dcl;
- int width;
- int height;
-+ ScanoutChange change = SCANOUT_CHANGE_NONE;
-
- if (!surface) {
- if (old_surface) {
-@@ -833,7 +869,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
-
- assert(old_surface != new_surface);
-
-- con->scanout.kind = SCANOUT_SURFACE;
-+ change = dpy_change_scanout_kind(&con->scanout, SCANOUT_SURFACE);
- con->surface = new_surface;
- dpy_gfx_create_texture(con, new_surface);
- QLIST_FOREACH(dcl, &s->listeners, next) {
-@@ -844,6 +880,7 @@ void dpy_gfx_replace_surface(QemuConsole *con,
- }
- dpy_gfx_destroy_texture(con, old_surface);
- qemu_free_displaysurface(old_surface);
-+ dpy_complete_scanout_change(&change);
- }
-
- bool dpy_gfx_check_format(QemuConsole *con,
-@@ -1002,9 +1039,10 @@ void dpy_gl_scanout_disable(QemuConsole *con)
- {
- DisplayState *s = con->ds;
- DisplayChangeListener *dcl;
-+ ScanoutChange change = SCANOUT_CHANGE_NONE;
-
- if (con->scanout.kind != SCANOUT_SURFACE) {
-- con->scanout.kind = SCANOUT_NONE;
-+ change = dpy_change_scanout_kind(&con->scanout, SCANOUT_NONE);
- }
- QLIST_FOREACH(dcl, &s->listeners, next) {
- if (con != dcl->con) {
-@@ -1014,6 +1052,7 @@ void dpy_gl_scanout_disable(QemuConsole *con)
- dcl->ops->dpy_gl_scanout_disable(dcl);
- }
- }
-+ dpy_complete_scanout_change(&change);
- }
-
- void dpy_gl_scanout_texture(QemuConsole *con,
-@@ -1023,15 +1062,17 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- uint32_t backing_height,
- uint32_t x, uint32_t y,
- uint32_t width, uint32_t height,
-- ScanoutTextureNative native)
-+ ScanoutTextureNative native,
-+ ScanoutTextureCleanup cb_cleanup)
- {
- DisplayState *s = con->ds;
- DisplayChangeListener *dcl;
-+ ScanoutChange change = SCANOUT_CHANGE_NONE;
-
-- con->scanout.kind = SCANOUT_TEXTURE;
-+ change = dpy_change_scanout_kind(&con->scanout, SCANOUT_TEXTURE);
- con->scanout.texture = (ScanoutTexture) {
- backing_id, backing_y_0_top, backing_width, backing_height,
-- x, y, width, height, native,
-+ x, y, width, height, native, cb_cleanup
- };
- QLIST_FOREACH(dcl, &s->listeners, next) {
- if (con != dcl->con) {
-@@ -1045,6 +1086,7 @@ void dpy_gl_scanout_texture(QemuConsole *con,
- native);
- }
- }
-+ dpy_complete_scanout_change(&change);
- }
-
- void dpy_gl_scanout_dmabuf(QemuConsole *con,
-@@ -1052,8 +1094,9 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
- {
- DisplayState *s = con->ds;
- DisplayChangeListener *dcl;
-+ ScanoutChange change = SCANOUT_CHANGE_NONE;
-
-- con->scanout.kind = SCANOUT_DMABUF;
-+ change = dpy_change_scanout_kind(&con->scanout, SCANOUT_DMABUF);
- con->scanout.dmabuf = dmabuf;
- QLIST_FOREACH(dcl, &s->listeners, next) {
- if (con != dcl->con) {
-@@ -1063,6 +1106,7 @@ void dpy_gl_scanout_dmabuf(QemuConsole *con,
- dcl->ops->dpy_gl_scanout_dmabuf(dcl, dmabuf);
- }
- }
-+ dpy_complete_scanout_change(&change);
- }
-
- void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
---
-2.41.0
-
-From 4206bd1f7083ad5d1bacae48f37ecdcba123ee2c Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 15:50:56 -0800
-Subject: [PATCH 4/9] virtio-gpu-virgl: add support for native blob scanout
-
-On macOS we do not have dmabuf and so we use MTLTexture as our scanout
-source. For blob scanout, the buffer is untyped and so we cannot get a
-MTLTexture until we pass more information to virglrenderer (surface size,
-pixel format, etc). The new API to do this is currently unstable so we
-need to define `VIRGL_RENDERER_UNSTABLE_APIS`. This should be removed after
-the ABI becomes stable.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 60 +++++++++++++++++++++++++++++++++++
- meson.build | 5 +++
- 2 files changed, 65 insertions(+)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index 8d511ed562..f515165335 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -22,6 +22,7 @@
-
- #include "ui/egl-helpers.h"
-
-+#define VIRGL_RENDERER_UNSTABLE_APIS
- #include
-
- #define NATIVE_HANDLE_SUPPORT_VERSION (1)
-@@ -828,6 +829,59 @@ static void virgl_cmd_resource_unmap_blob(VirtIOGPU *g,
- }
- }
-
-+#if defined(HAVE_VIRGL_RENDERER_NATIVE_SCANOUT)
-+static void virgl_scanout_native_blob_cleanup(ScanoutTextureNative *native)
-+{
-+ assert(native->type == SCANOUT_TEXTURE_NATIVE_TYPE_METAL);
-+ virgl_renderer_release_handle_for_scanout(VIRGL_NATIVE_HANDLE_METAL_TEXTURE,
-+ native->handle);
-+}
-+
-+static bool virgl_scanout_native_blob(VirtIOGPU *g,
-+ struct virtio_gpu_set_scanout_blob *ss)
-+{
-+ struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[ss->scanout_id];
-+ enum virgl_renderer_native_handle_type type;
-+ virgl_renderer_native_handle handle;
-+ ScanoutTextureNative native;
-+
-+ type = virgl_renderer_create_handle_for_scanout(ss->resource_id,
-+ ss->width,
-+ ss->height,
-+ ss->format,
-+ ss->padding,
-+ ss->strides[0],
-+ ss->offsets[0],
-+ &handle);
-+#ifdef CONFIG_METAL
-+ if (type == VIRGL_NATIVE_HANDLE_METAL_TEXTURE) {
-+ native = (ScanoutTextureNative){
-+ .type = SCANOUT_TEXTURE_NATIVE_TYPE_METAL,
-+ .handle = handle,
-+ };
-+ qemu_console_resize(scanout->con,
-+ ss->r.width, ss->r.height);
-+ dpy_gl_scanout_texture(
-+ scanout->con, 0,
-+ false,
-+ ss->width, ss->height,
-+ ss->r.x, ss->r.y, ss->r.width, ss->r.height,
-+ native, virgl_scanout_native_blob_cleanup);
-+ scanout->resource_id = ss->resource_id;
-+
-+ return true;
-+ }
-+#endif
-+
-+ /* don't leak memory if handle type is unknown */
-+ if (type != VIRGL_NATIVE_HANDLE_NONE) {
-+ virgl_renderer_release_handle_for_scanout(type, handle);
-+ }
-+
-+ return false;
-+}
-+#endif
-+
- static void virgl_cmd_set_scanout_blob(VirtIOGPU *g,
- struct virtio_gpu_ctrl_command *cmd)
- {
-@@ -866,6 +920,12 @@ static void virgl_cmd_set_scanout_blob(VirtIOGPU *g,
- return;
- }
-
-+#if defined(HAVE_VIRGL_RENDERER_NATIVE_SCANOUT)
-+ if (virgl_scanout_native_blob(g, &ss)) {
-+ return;
-+ }
-+#endif
-+
- res = virtio_gpu_virgl_find_resource(g, ss.resource_id);
- if (!res) {
- qemu_log_mask(LOG_GUEST_ERROR, "%s: resource does not exist %d\n",
-diff --git a/meson.build b/meson.build
-index 3ecd4d5b3a..84ee2f179d 100644
---- a/meson.build
-+++ b/meson.build
-@@ -2584,6 +2584,11 @@ config_host_data.set('CONFIG_VNC', vnc.found())
- config_host_data.set('CONFIG_VNC_JPEG', jpeg.found())
- config_host_data.set('CONFIG_VNC_SASL', sasl.found())
- if virgl.found()
-+ config_host_data.set('HAVE_VIRGL_RENDERER_NATIVE_SCANOUT',
-+ cc.has_function('virgl_renderer_create_handle_for_scanout',
-+ args: '-DVIRGL_RENDERER_UNSTABLE_APIS',
-+ prefix: '#include ',
-+ dependencies: virgl))
- config_host_data.set('VIRGL_VERSION_MAJOR', virgl.version().split('.')[0])
- endif
- config_host_data.set('CONFIG_VIRTFS', have_virtfs)
---
-2.41.0
-
-From 068267f42709a93f9fabc57b05225e18026f5a97 Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 15:56:50 -0800
-Subject: [PATCH 5/9] hvf: warn when attempting to add unaligned page size
-
-Instead of silently failing, we should print a warning so it is clear why
-a future guest access to that location might cause an exception.
----
- accel/hvf/hvf-accel-ops.c | 8 ++++++++
- 1 file changed, 8 insertions(+)
-
-diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c
-index da719d85b7..dae9600f4b 100644
---- a/accel/hvf/hvf-accel-ops.c
-+++ b/accel/hvf/hvf-accel-ops.c
-@@ -139,6 +139,14 @@ static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
-
- if (!QEMU_IS_ALIGNED(int128_get64(section->size), page_size) ||
- !QEMU_IS_ALIGNED(section->offset_within_address_space, page_size)) {
-+ if (add) {
-+ warn_report("Cannot add 0x%016llX:0x%016llX because it is not "
-+ "aligned to page size (0x%X)",
-+ section->offset_within_address_space,
-+ section->offset_within_address_space +
-+ int128_get64(section->size),
-+ (uint32_t)page_size);
-+ }
- /* Not page aligned, so we can not map as RAM */
- add = false;
- }
---
-2.41.0
-
-From 09ce55779cd608595ea6b5bebf1b426136f45fa0 Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Sun, 30 Nov 2025 15:59:10 -0800
-Subject: [PATCH 6/9] hvf: allow slot size to increase
-
-Instead of having a fixed limit of 32 slots, we allow the number of slots
-to expand. Currently there does not seem to be a need to add a limit, but
-if the allocation fails, we will abort. The KVM backend was used for
-inspiration here.
----
- accel/hvf/hvf-accel-ops.c | 79 ++++++++++++++++++++++++++++++---------
- include/system/hvf_int.h | 12 +++++-
- 2 files changed, 72 insertions(+), 19 deletions(-)
-
-diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c
-index dae9600f4b..13cc72baaf 100644
---- a/accel/hvf/hvf-accel-ops.c
-+++ b/accel/hvf/hvf-accel-ops.c
-@@ -67,6 +67,57 @@ bool hvf_tso_mode = 0;
-
- /* Memory slots */
-
-+/* Default num of memslots to be allocated when VM starts */
-+#define HVF_MEMSLOTS_NUM_ALLOC_DEFAULT 32
-+
-+static bool hvf_slots_grow(HVFState *state, unsigned int num_slots_new)
-+{
-+ unsigned int i, cur = state->num_slots;
-+ hvf_slot *slots;
-+ hvf_mac_slot *mac_slots;
-+
-+ assert(num_slots_new > cur);
-+ if (cur == 0) {
-+ slots = g_new0(hvf_slot, num_slots_new);
-+ if (!slots) {
-+ return false;
-+ }
-+ mac_slots = g_new0(hvf_mac_slot, num_slots_new);
-+ if (!mac_slots) {
-+ g_free(slots);
-+ return false;
-+ }
-+ } else {
-+ slots = g_renew(hvf_slot, state->slots, num_slots_new);
-+ if (!slots) {
-+ return false;
-+ }
-+ mac_slots = g_renew(hvf_mac_slot, state->mac_slots, num_slots_new);
-+ if (!mac_slots) {
-+ /* save allocated slots but not new size */
-+ state->slots = slots;
-+ return false;
-+ }
-+ /*
-+ * g_renew() doesn't initialize extended buffers, however hvf
-+ * memslots require fields to be zero-initialized. E.g. pointers,
-+ * memory_size field, etc.
-+ */
-+ memset(&slots[cur], 0x0, sizeof(slots[0]) * (num_slots_new - cur));
-+ memset(&mac_slots[cur], 0x0, sizeof(mac_slots[0]) * (num_slots_new - cur));
-+ }
-+
-+ for (i = cur; i < num_slots_new; i++) {
-+ slots[i].slot_id = i;
-+ }
-+
-+ state->slots = slots;
-+ state->mac_slots = mac_slots;
-+ state->num_slots = num_slots_new;
-+
-+ return true;
-+}
-+
- hvf_slot *hvf_find_overlap_slot(uint64_t start, uint64_t size)
- {
- hvf_slot *slot;
-@@ -81,21 +132,12 @@ hvf_slot *hvf_find_overlap_slot(uint64_t start, uint64_t size)
- return NULL;
- }
-
--struct mac_slot {
-- int present;
-- uint64_t size;
-- uint64_t gpa_start;
-- uint64_t gva;
--};
--
--struct mac_slot mac_slots[32];
--
- static int do_hvf_set_memory(hvf_slot *slot, hv_memory_flags_t flags)
- {
-- struct mac_slot *macslot;
-+ hvf_mac_slot *macslot;
- hv_return_t ret;
-
-- macslot = &mac_slots[slot->slot_id];
-+ macslot = &hvf_state->mac_slots[slot->slot_id];
-
- if (macslot->present) {
- if (macslot->size != slot->size) {
-@@ -195,8 +237,11 @@ static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
- }
-
- if (x == hvf_state->num_slots) {
-- error_report("No free slots");
-- abort();
-+ if (!hvf_slots_grow(hvf_state, hvf_state->num_slots * 2)) {
-+ error_report("Cannot allocate any more slots");
-+ abort();
-+ }
-+ mem = &hvf_state->slots[x];
- }
-
- mem->size = int128_get64(section->size);
-@@ -333,6 +378,7 @@ static int hvf_accel_init(MachineState *ms)
- HVFState *s;
- int pa_range = 36;
- MachineClass *mc = MACHINE_GET_CLASS(ms);
-+ bool success;
-
- if (mc->hvf_get_physical_address_range) {
- pa_range = mc->hvf_get_physical_address_range(ms);
-@@ -346,11 +392,8 @@ static int hvf_accel_init(MachineState *ms)
-
- s = g_new0(HVFState, 1);
-
-- s->num_slots = ARRAY_SIZE(s->slots);
-- for (x = 0; x < s->num_slots; ++x) {
-- s->slots[x].size = 0;
-- s->slots[x].slot_id = x;
-- }
-+ success = hvf_slots_grow(s, HVF_MEMSLOTS_NUM_ALLOC_DEFAULT);
-+ assert(success);
-
- QTAILQ_INIT(&s->hvf_sw_breakpoints);
-
-diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
-index 763201dd71..52c69ed9d5 100644
---- a/include/system/hvf_int.h
-+++ b/include/system/hvf_int.h
-@@ -37,6 +37,7 @@ extern hv_return_t _hv_vcpu_set_actlr(hv_vcpu_t vcpu, uint64_t value);
- /* hvf_slot flags */
- #define HVF_SLOT_LOG (1 << 0)
-
-+/* Represent memory logically mapped by QEMU */
- typedef struct hvf_slot {
- uint64_t start;
- uint64_t size;
-@@ -46,6 +47,14 @@ typedef struct hvf_slot {
- MemoryRegion *region;
- } hvf_slot;
-
-+/* Represent memory currently mapped in HVF */
-+typedef struct hvf_mac_slot {
-+ int present;
-+ uint64_t size;
-+ uint64_t gpa_start;
-+ uint64_t gva;
-+} hvf_mac_slot;
-+
- typedef struct hvf_vcpu_caps {
- uint64_t vmx_cap_pinbased;
- uint64_t vmx_cap_procbased;
-@@ -57,7 +66,8 @@ typedef struct hvf_vcpu_caps {
-
- struct HVFState {
- AccelState parent;
-- hvf_slot slots[32];
-+ hvf_slot *slots;
-+ hvf_mac_slot *mac_slots;
- int num_slots;
-
- hvf_vcpu_caps *hvf_caps;
---
-2.41.0
-
-From 3f775e8c8540bf6193e5f97202602fe54433fc61 Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Fri, 19 Dec 2025 07:07:12 -0800
-Subject: [PATCH] hvf: support changing IPA granule size
-
-The IPA granule is the smallest page size hv_vm_map() support. For Venus, we
-need to support 4KiB pages. macOS 26 introduces a public API for setting
-the granule size. We can only use this when compiled with macOS 26 SDK and
-run on macOS 26+. Otherwise, we fall back to an older, private, API which
-achieves the same purpose.
-
-Signed-off-by: Joelle van Dyne
----
- accel/hvf/hvf-accel-ops.c | 43 ++++++++++++++++++++++++++++++++-
- include/system/hvf_int.h | 4 +++-
- target/arm/hvf/hvf.c | 50 ++++++++++++++++++++++++++++++++++++++-
- target/i386/hvf/hvf.c | 10 +++++++-
- 4 files changed, 103 insertions(+), 4 deletions(-)
-
-diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c
-index 13cc72baaf..036c121adb 100644
---- a/accel/hvf/hvf-accel-ops.c
-+++ b/accel/hvf/hvf-accel-ops.c
-@@ -54,6 +54,8 @@
- #include "exec/exec-all.h"
- #include "gdbstub/enums.h"
- #include "hw/boards.h"
-+#include "qapi/error.h"
-+#include "qapi/visitor.h"
- #include "system/accel-ops.h"
- #include "system/cpus.h"
- #include "system/hvf.h"
-@@ -64,6 +66,7 @@
-
- HVFState *hvf_state;
- bool hvf_tso_mode = 0;
-+uint32_t hvf_ipa_granule_size = 0;
-
- /* Memory slots */
-
-@@ -179,6 +182,10 @@ static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
- }
- }
-
-+ if (hvf_ipa_granule_size) {
-+ page_size = hvf_ipa_granule_size;
-+ }
-+
- if (!QEMU_IS_ALIGNED(int128_get64(section->size), page_size) ||
- !QEMU_IS_ALIGNED(section->offset_within_address_space, page_size)) {
- if (add) {
-@@ -387,7 +394,7 @@ static int hvf_accel_init(MachineState *ms)
- }
- }
-
-- ret = hvf_arch_vm_create(ms, (uint32_t)pa_range);
-+ ret = hvf_arch_vm_create(ms, (uint32_t)pa_range, hvf_ipa_granule_size);
- assert_hvf_ok(ret);
-
- s = g_new0(HVFState, 1);
-@@ -422,6 +429,34 @@ static inline int hvf_gdbstub_sstep_flags(void)
- return SSTEP_ENABLE | SSTEP_NOIRQ;
- }
-
-+static void hvf_get_ipa_granule_size(Object *obj, Visitor *v,
-+ const char *name, void *opaque,
-+ Error **errp)
-+{
-+ HVFState *s = HVF_STATE(obj);
-+ uint32_t value = hvf_ipa_granule_size;
-+
-+ visit_type_uint32(v, name, &value, errp);
-+}
-+
-+static void hvf_set_ipa_granule_size(Object *obj, Visitor *v,
-+ const char *name, void *opaque,
-+ Error **errp)
-+{
-+ HVFState *s = HVF_STATE(obj);
-+ uint32_t value;
-+
-+ if (!visit_type_uint32(v, name, &value, errp)) {
-+ return;
-+ }
-+ if (value & (value - 1)) {
-+ error_setg(errp, "ipa-granule-size must be a power of two.");
-+ return;
-+ }
-+
-+ hvf_ipa_granule_size = value;
-+}
-+
- static void hvf_accel_class_init(ObjectClass *oc, void *data)
- {
- AccelClass *ac = ACCEL_CLASS(oc);
-@@ -436,6 +471,12 @@ static void hvf_accel_class_init(ObjectClass *oc, void *data)
- object_class_property_set_description(oc, "tso",
- "Set on/off to enable/disable total store ordering mode");
- #endif
-+
-+ object_class_property_add(oc, "ipa-granule-size", "uint32",
-+ hvf_get_ipa_granule_size, hvf_set_ipa_granule_size,
-+ NULL, NULL);
-+ object_class_property_set_description(oc, "ipa-granule-size",
-+ "Size of a single guest page");
- }
-
- static const TypeInfo hvf_accel_type = {
-diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
-index 52c69ed9d5..93e1b23e1a 100644
---- a/include/system/hvf_int.h
-+++ b/include/system/hvf_int.h
-@@ -76,6 +76,7 @@ struct HVFState {
- };
- extern HVFState *hvf_state;
- extern bool hvf_tso_mode;
-+extern uint32_t hvf_ipa_granule_size;
-
- struct AccelCPUState {
- hvf_vcpuid fd;
-@@ -91,7 +92,8 @@ void assert_hvf_ok_impl(hv_return_t ret, const char *file, unsigned int line,
- #define assert_hvf_ok(EX) assert_hvf_ok_impl((EX), __FILE__, __LINE__, #EX)
- const char *hvf_return_string(hv_return_t ret);
- int hvf_arch_init(void);
--hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range);
-+hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range,
-+ uint32_t ipa_granule_size);
- int hvf_arch_init_vcpu(CPUState *cpu);
- void hvf_arch_vcpu_destroy(CPUState *cpu);
- int hvf_vcpu_exec(CPUState *);
-diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
-index aa63f30fcf..51da8009cb 100644
---- a/target/arm/hvf/hvf.c
-+++ b/target/arm/hvf/hvf.c
-@@ -12,6 +12,9 @@
- #include "qemu/osdep.h"
- #include "qemu/error-report.h"
- #include "qemu/log.h"
-+#include
-+#include
-+#include
-
- #include "system/runstate.h"
- #include "system/hvf.h"
-@@ -1035,7 +1038,45 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
- {
- }
-
--hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range)
-+static hv_return_t hvf_set_ipa_granule(hv_vm_config_t config,
-+ uint32_t ipa_granule_size)
-+{
-+ static hv_return_t (*set_ipa_granule)(hv_vm_config_t, uint32_t);
-+ uint64_t page_size = qemu_real_host_page_size();
-+
-+ /* macOS 26 introduces a public API for setting granule size */
-+#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && defined(__MAC_26_0) && \
-+ __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_26_0
-+ if (__builtin_available(macOS 26, *)) {
-+ hv_ipa_granule_t granule = HV_IPA_GRANULE_16KB;
-+
-+ if (ipa_granule_size == 4096) {
-+ granule = HV_IPA_GRANULE_4KB;
-+ } else if (ipa_granule_size != 16384) {
-+ error_report("Unsupported granule size: 0x%x", ipa_granule_size);
-+ return HV_UNSUPPORTED;
-+ }
-+
-+ return hv_vm_config_set_ipa_granule(config, granule);
-+ }
-+#endif
-+
-+ /* older macOS need to use a private API */
-+ if (!set_ipa_granule) {
-+ set_ipa_granule = dlsym(RTLD_NEXT, "_hv_vm_config_set_ipa_granule");
-+ }
-+ if (set_ipa_granule) {
-+ return set_ipa_granule(config, ipa_granule_size);
-+ } else if (ipa_granule_size != page_size) {
-+ error_report("Failed to find _hv_vm_config_set_ipa_granule");
-+ return HV_UNSUPPORTED;
-+ }
-+
-+ return HV_SUCCESS;
-+}
-+
-+hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range,
-+ uint32_t ipa_granule_size)
- {
- hv_return_t ret;
- hv_vm_config_t config = NULL;
-@@ -1056,6 +1097,13 @@ hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range)
- }
- #endif
-
-+ if (ipa_granule_size) {
-+ ret = hvf_set_ipa_granule(config, ipa_granule_size);
-+ if (ret != HV_SUCCESS) {
-+ goto cleanup;
-+ }
-+ }
-+
- ret = hv_vm_create(config);
-
- cleanup:
-diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c
-index 9ba0e04ac7..3cfe84313c 100644
---- a/target/i386/hvf/hvf.c
-+++ b/target/i386/hvf/hvf.c
-@@ -224,8 +224,16 @@ int hvf_arch_init(void)
- return 0;
- }
-
--hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range)
-+hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range,
-+ uint32_t ipa_granule_size)
- {
-+ uint64_t page_size = qemu_real_host_page_size();
-+
-+ if (ipa_granule_size != 0 && ipa_granule_size != page_size) {
-+ error_report("Only supported IPA granule size: 0x%llx", page_size);
-+ return HV_UNSUPPORTED;
-+ }
-+
- return hv_vm_create(HV_VM_DEFAULT);
- }
-
---
-2.41.0
-
-From 1cafc035c4f10591d6064d76568c41249ab819ff Mon Sep 17 00:00:00 2001
-From: Joelle van Dyne
-Date: Mon, 22 Dec 2025 06:04:46 -0800
-Subject: [PATCH 8/9] virtio-gpu-virgl: correct parent for blob memory region
-
-When `owner` == `mr`, `object_unparent` will crash:
-
-object_unparent(mr) ->
-object_property_del_child(mr, mr) ->
-object_finalize_child_property(mr, name, mr) ->
-object_unref(mr) ->
-object_finalize(mr) ->
-object_property_del_all(mr) ->
-object_finalize_child_property(mr, name, mr) ->
-object_unref(mr) ->
-fail on g_assert(obj->ref > 0)
-
-However, passing a different `owner` to `memory_region_init` does not
-work. `memory_region_ref` has an optimization where it takes a ref
-only on the owner. That means when flatviews are created, it does not
-take a ref on the region and you can get a UAF from `flatview_destroy`
-called from RCU.
-
-The correct fix therefore is to use `NULL` as the name which will set
-the `owner` but not the `parent` (which is still NULL). This allows us
-to use `memory_region_ref` on itself while not having to rely on unparent
-for cleanup.
-
-Signed-off-by: Joelle van Dyne
----
- hw/display/virtio-gpu-virgl.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
-index f515165335..6a97bdc67a 100644
---- a/hw/display/virtio-gpu-virgl.c
-+++ b/hw/display/virtio-gpu-virgl.c
-@@ -123,7 +123,7 @@ virtio_gpu_virgl_map_resource_blob(VirtIOGPU *g,
- vmr->g = g;
-
- mr = &vmr->mr;
-- memory_region_init_ram_ptr(mr, OBJECT(mr), "blob", size, data);
-+ memory_region_init_ram_ptr(mr, OBJECT(mr), NULL, size, data);
- memory_region_add_subregion(&b->hostmem, offset, mr);
- memory_region_set_enabled(mr, true);
-
-@@ -185,7 +185,7 @@ virtio_gpu_virgl_unmap_resource_blob(VirtIOGPU *g,
- /* memory region owns self res->mr object and frees it by itself */
- memory_region_set_enabled(mr, false);
- memory_region_del_subregion(&b->hostmem, mr);
-- object_unparent(OBJECT(mr));
-+ object_unref(OBJECT(mr));
- }
-
- return 0;
---
-2.41.0
-
-From fb99e056ac794d77e2c7c00afc50d53205099fc1 Mon Sep 17 00:00:00 2001
-From: osy
-Date: Wed, 7 Jan 2026 12:18:14 -0800
-Subject: [PATCH 9/9] ui/spice-display: support MTLTexture scanout to IOSurface
-
----
- include/ui/spice-display.h | 30 +++++++
- ui/egl-helpers.c | 29 ++++++-
- ui/meson.build | 4 +
- ui/spice-display-metal.m | 166 +++++++++++++++++++++++++++++++++++++
- ui/spice-display.c | 86 ++++++++++++++++++-
- 5 files changed, 312 insertions(+), 3 deletions(-)
- create mode 100644 ui/spice-display-metal.m
-
-diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h
-index ec55194139..8e752f67da 100644
---- a/include/ui/spice-display.h
-+++ b/include/ui/spice-display.h
-@@ -77,6 +77,13 @@ typedef struct QXLCookie {
- QXLRect area;
- int redraw;
- } render;
-+ struct {
-+ QXLInstance *qxl;
-+ uint32_t x;
-+ uint32_t y;
-+ uint32_t w;
-+ uint32_t h;
-+ } gl_draw;
- void *data;
- } u;
- } QXLCookie;
-@@ -86,6 +93,7 @@ QXLCookie *qxl_cookie_new(int type, uint64_t io);
- typedef struct SimpleSpiceDisplay SimpleSpiceDisplay;
- typedef struct SimpleSpiceUpdate SimpleSpiceUpdate;
- typedef struct SimpleSpiceCursor SimpleSpiceCursor;
-+typedef void *SpiceDisplayMetalContext;
-
- struct SimpleSpiceDisplay {
- DisplaySurface *ds;
-@@ -137,6 +145,9 @@ struct SimpleSpiceDisplay {
- #if defined(CONFIG_IOSURFACE)
- IOSurfaceRef iosurface;
- int surface_send_fd;
-+#if defined(CONFIG_METAL)
-+ SpiceDisplayMetalContext metal_context;
-+#endif
- #endif
- #if defined(CONFIG_EGL)
- EGLSurface esurface;
-@@ -197,4 +208,23 @@ void qemu_spice_display_start(void);
- void qemu_spice_display_stop(void);
- int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
-
-+#if defined(CONFIG_METAL) && defined(CONFIG_IOSURFACE)
-+typedef void *MTLTexture_id;
-+typedef void (*SpiceDisplayMetalCompletion)(void *data);
-+
-+SpiceDisplayMetalContext qemu_spice_display_metal_create_context(IOSurfaceRef surface,
-+ uint32_t width,
-+ uint32_t height);
-+void qemu_spice_display_metal_destroy_context(SpiceDisplayMetalContext ctx);
-+void qemu_spice_display_metal_scanout_texture(SpiceDisplayMetalContext ctx,
-+ MTLTexture_id tex, uint32_t x, uint32_t y,
-+ uint32_t w, uint32_t h);
-+void qemu_spice_display_metal_scanout_disable(SpiceDisplayMetalContext ctx);
-+bool qemu_spice_display_metal_has_scanout(SpiceDisplayMetalContext ctx);
-+void qemu_spice_display_metal_draw_frame(SpiceDisplayMetalContext ctx,
-+ uint32_t x, uint32_t y, uint32_t w, uint32_t h,
-+ SpiceDisplayMetalCompletion completion,
-+ void *data);
-+#endif
-+
- #endif
-diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
-index f35223592e..1efac2c994 100644
---- a/ui/egl-helpers.c
-+++ b/ui/egl-helpers.c
-@@ -533,7 +533,32 @@ int qemu_egl_init_dpy_cocoa(DisplayGLMode mode)
- return -1;
- }
-
-- return qemu_egl_init_dpy(dpy, mode);
-+ if (qemu_egl_init_dpy(dpy, mode) < 0) {
-+ return -1;
-+ }
-+
-+#ifdef EGL_METAL_DEVICE_ANGLE
-+ if (epoxy_has_egl_extension(qemu_egl_display, "EGL_EXT_device_query")) {
-+ EGLDeviceEXT device;
-+ void *metal_device;
-+
-+ if (!eglQueryDisplayAttribEXT(qemu_egl_display,
-+ EGL_DEVICE_EXT,
-+ (EGLAttrib *)&device)) {
-+ return 0;
-+ }
-+
-+ if (!eglQueryDeviceAttribEXT(device,
-+ EGL_METAL_DEVICE_ANGLE,
-+ (EGLAttrib *)&metal_device)) {
-+ return 0;
-+ }
-+
-+ qemu_egl_angle_native_device = metal_device;
-+ }
-+#endif
-+
-+ return 0;
- }
-
- /* ---------------------------------------------------------------------- */
-@@ -625,7 +650,7 @@ int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode)
- mode = DISPLAY_GL_MODE_ES;
- }
-
-- if (qemu_egl_init_dpy(dpy, 0, mode) < 0) {
-+ if (qemu_egl_init_dpy(dpy, mode) < 0) {
- return -1;
- }
-
-diff --git a/ui/meson.build b/ui/meson.build
-index 8acb9c0ef0..fffa0da90a 100644
---- a/ui/meson.build
-+++ b/ui/meson.build
-@@ -144,6 +144,10 @@ if spice.found()
- if quartzcore.found()
- spice_core_ss.add(quartzcore)
- endif
-+ if metal.found()
-+ spice_core_ss.add(metal)
-+ spice_core_ss.add(files('spice-display-metal.m'))
-+ endif
- ui_modules += {'spice-core' : spice_core_ss}
-
- if gio.found()
-diff --git a/ui/spice-display-metal.m b/ui/spice-display-metal.m
-new file mode 100644
-index 0000000000..29e58d9461
---- /dev/null
-+++ b/ui/spice-display-metal.m
-@@ -0,0 +1,166 @@
-+/*
-+ * SPDX-License-Identifier: GPL-2.0-or-later
-+ *
-+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
-+ * See the COPYING file in the top-level directory.
-+ */
-+
-+#include "qemu/osdep.h"
-+#include "ui/egl-helpers.h"
-+#include "ui/spice-display.h"
-+#include
-+#include
-+#include
-+
-+@interface SpiceDisplayMetal : NSObject
-+
-+@property (nonatomic, readonly) id renderTarget;
-+@property (nonatomic, readonly) id commandQueue;
-+@property (nonatomic, nullable, retain) id scanoutTexture;
-+@property (nonatomic, assign) CGRect scanoutRect;
-+
-+- (instancetype)initWithDevice:(id)device
-+ surface:(IOSurfaceRef)surface
-+ width:(NSInteger)width
-+ height:(NSInteger)height;
-+- (void)scanoutTexture:(id)texture rect:(CGRect)rect;
-+- (void)scanoutDisable;
-+- (void)drawFrameAtRect:(CGRect)rect completion:(void (^)(void))completion;
-+
-+@end
-+
-+@implementation SpiceDisplayMetal
-+
-+- (instancetype)initWithDevice:(id)device
-+ surface:(IOSurfaceRef)surface
-+ width:(NSInteger)width
-+ height:(NSInteger)height
-+{
-+ if (self = [super init]) {
-+ MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
-+ textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
-+ textureDescriptor.width = width;
-+ textureDescriptor.height = height;
-+ textureDescriptor.usage = MTLTextureUsageRenderTarget;
-+ _renderTarget = [device newTextureWithDescriptor:textureDescriptor iosurface:surface plane:0];
-+ [textureDescriptor release];
-+ if (!_renderTarget) {
-+ return nil;
-+ }
-+ _commandQueue = [device newCommandQueue];
-+ if (!_commandQueue) {
-+ [_renderTarget release];
-+ return nil;
-+ }
-+ }
-+ return self;
-+}
-+
-+- (void)dealloc
-+{
-+ [_scanoutTexture release];
-+ [_commandQueue release];
-+ [_renderTarget release];
-+ [super dealloc];
-+}
-+
-+- (void)scanoutTexture:(id)texture rect:(CGRect)rect
-+{
-+ self.scanoutTexture = texture;
-+ self.scanoutRect = rect;
-+}
-+
-+- (void)scanoutDisable
-+{
-+ self.scanoutTexture = nil;
-+ self.scanoutRect = CGRectMake(0, 0, 0, 0);
-+}
-+
-+- (void)drawFrameAtRect:(CGRect)rect completion:(void (^)(void))completion
-+{
-+ @autoreleasepool {
-+ if (!self.scanoutTexture) {
-+ return;
-+ }
-+
-+ id commandBuffer = [self.commandQueue commandBuffer];
-+ id blit = [commandBuffer blitCommandEncoder];
-+ MTLOrigin origin = MTLOriginMake(self.scanoutRect.origin.x + rect.origin.x,
-+ self.scanoutRect.origin.y + rect.origin.y,
-+ 0);
-+ MTLSize size = MTLSizeMake(rect.size.width, rect.size.height, 1);
-+
-+ [blit copyFromTexture:self.scanoutTexture
-+ sourceSlice:0
-+ sourceLevel:0
-+ sourceOrigin:origin
-+ sourceSize:size
-+ toTexture:self.renderTarget
-+ destinationSlice:0
-+ destinationLevel:0
-+ destinationOrigin:(MTLOrigin){rect.origin.x,rect.origin.y,0}];
-+
-+ [blit endEncoding];
-+
-+ [commandBuffer addCompletedHandler:^(id cb) {
-+ completion();
-+ }];
-+
-+ [commandBuffer commit];
-+ }
-+}
-+
-+@end
-+
-+SpiceDisplayMetalContext qemu_spice_display_metal_create_context(IOSurfaceRef surface,
-+ uint32_t width,
-+ uint32_t height)
-+{
-+ id device = (id)qemu_egl_angle_native_device;
-+
-+ if (!device) {
-+ return NULL;
-+ }
-+
-+ return [[SpiceDisplayMetal alloc] initWithDevice:device
-+ surface:surface
-+ width:width
-+ height:height];
-+}
-+
-+void qemu_spice_display_metal_destroy_context(SpiceDisplayMetalContext ctx)
-+{
-+ [(SpiceDisplayMetal *)ctx release];
-+}
-+
-+void qemu_spice_display_metal_scanout_texture(SpiceDisplayMetalContext ctx,
-+ MTLTexture_id tex, uint32_t x, uint32_t y,
-+ uint32_t w, uint32_t h)
-+{
-+ CGRect rect = CGRectMake(x, y, w, h);
-+
-+ [(SpiceDisplayMetal *)ctx scanoutTexture:(id)tex
-+ rect:rect];
-+}
-+
-+void qemu_spice_display_metal_scanout_disable(SpiceDisplayMetalContext ctx)
-+{
-+ [(SpiceDisplayMetal *)ctx scanoutDisable];
-+}
-+
-+bool qemu_spice_display_metal_has_scanout(SpiceDisplayMetalContext ctx)
-+{
-+ return [(SpiceDisplayMetal *)ctx scanoutTexture] != nil;
-+}
-+
-+void qemu_spice_display_metal_draw_frame(SpiceDisplayMetalContext ctx,
-+ uint32_t x, uint32_t y, uint32_t w, uint32_t h,
-+ SpiceDisplayMetalCompletion completion,
-+ void *data)
-+{
-+ CGRect rect = CGRectMake(x, y, w, h);
-+
-+ [(SpiceDisplayMetal *)ctx drawFrameAtRect:rect completion:^{
-+ completion(data);
-+ }];
-+}
-diff --git a/ui/spice-display.c b/ui/spice-display.c
-index 97a915a7ee..976f68710a 100644
---- a/ui/spice-display.c
-+++ b/ui/spice-display.c
-@@ -939,6 +939,10 @@ static bool spice_iosurface_create(SimpleSpiceDisplay *ssd, int width, int heigh
-
- ssd->iosurface = surface;
-
-+#if defined(CONFIG_METAL)
-+ ssd->metal_context = qemu_spice_display_metal_create_context(surface, width, height);
-+#endif
-+
- return true;
- }
-
-@@ -967,6 +971,13 @@ static void spice_iosurface_destroy(SimpleSpiceDisplay *ssd)
- return;
- }
-
-+#if defined(CONFIG_METAL)
-+ if (ssd->metal_context) {
-+ qemu_spice_display_metal_destroy_context(ssd->metal_context);
-+ ssd->metal_context = NULL;
-+ }
-+#endif
-+
- if (spice_opengl == DISPLAY_GL_MODE_CORE) {
- spice_iosurface_destroy_cgl(ssd);
- } else {
-@@ -1060,6 +1071,52 @@ static void spice_iosurface_blit(SimpleSpiceDisplay *ssd, GLuint src_texture, bo
- }
- }
-
-+#if defined(CONFIG_METAL)
-+static void qemu_spice_gl_block(SimpleSpiceDisplay *ssd, bool block);
-+
-+static void spice_iosurface_blit_completion(void *data)
-+{
-+ QXLCookie *cookie = (QXLCookie *)data;
-+
-+ spice_qxl_gl_draw_async(cookie->u.gl_draw.qxl,
-+ cookie->u.gl_draw.x,
-+ cookie->u.gl_draw.y,
-+ cookie->u.gl_draw.w,
-+ cookie->u.gl_draw.h,
-+ (uintptr_t)cookie);
-+}
-+
-+static bool spice_iosurface_blit_metal(SimpleSpiceDisplay *ssd,
-+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
-+{
-+ SpiceDisplayMetalContext context = ssd->metal_context;
-+ QXLCookie *cookie;
-+
-+ if (!ssd->iosurface || !context) {
-+ return false;
-+ }
-+
-+ if (!qemu_spice_display_metal_has_scanout(context)) {
-+ return false;
-+ }
-+
-+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
-+ cookie->u.gl_draw.qxl = &ssd->qxl;
-+ cookie->u.gl_draw.x = x;
-+ cookie->u.gl_draw.y = y;
-+ cookie->u.gl_draw.w = w;
-+ cookie->u.gl_draw.h = h;
-+
-+ qemu_spice_gl_block(ssd, true);
-+ qemu_spice_display_metal_draw_frame(context,
-+ x, y, w, h,
-+ spice_iosurface_blit_completion,
-+ cookie);
-+
-+ return true;
-+}
-+#endif
-+
- #endif
-
- #ifdef HAVE_SPICE_MAC_CGL
-@@ -1215,6 +1272,11 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
- return;
- }
- #elif defined(CONFIG_IOSURFACE)
-+#if defined(CONFIG_METAL)
-+ if (ssd->metal_context) {
-+ qemu_spice_display_metal_scanout_disable(ssd->metal_context);
-+ }
-+#endif
- if (spice_iosurface_resize(ssd, width, height)) {
- fd = spice_iosurface_create_fd(ssd, &fourcc);
- if (fd < 0) {
-@@ -1252,6 +1314,8 @@ static QEMUGLContext qemu_spice_gl_create_context(DisplayGLCtx *dgc,
- if (spice_opengl == DISPLAY_GL_MODE_CORE) {
- #if defined(HAVE_SPICE_MAC_CGL)
- return spice_cgl_create_context(spice_gl_ctx);
-+#else
-+ return NULL;
- #endif
- } else {
- #if defined(CONFIG_GBM)
-@@ -1282,6 +1346,8 @@ static int qemu_spice_gl_make_context_current(DisplayGLCtx *dgc,
- if (spice_opengl == DISPLAY_GL_MODE_CORE) {
- #if defined(HAVE_SPICE_MAC_CGL)
- return spice_cgl_make_context_current(ctx);
-+#else
-+ return -1;
- #endif
- } else {
- return qemu_egl_make_context_current(dgc, ctx);
-@@ -1298,6 +1364,11 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
- ssd->have_surface = false;
- ssd->have_scanout = false;
- #if defined(CONFIG_IOSURFACE)
-+#if defined(CONFIG_METAL)
-+ if (ssd->metal_context) {
-+ qemu_spice_display_metal_scanout_disable(ssd->metal_context);
-+ }
-+#endif
- spice_iosurface_destroy(ssd);
- #endif
- ssd->tex_id = -1;
-@@ -1326,6 +1397,15 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
- } else {
- fd = -1;
- }
-+#if defined(CONFIG_METAL)
-+ if (ssd->metal_context && native.type == SCANOUT_TEXTURE_NATIVE_TYPE_METAL) {
-+ qemu_spice_display_metal_scanout_texture(ssd->metal_context,
-+ native.handle,
-+ x, y, w, h);
-+ } else if (ssd->metal_context) {
-+ qemu_spice_display_metal_scanout_disable(ssd->metal_context);
-+ }
-+#endif
- #endif
- if (fd < 0) {
- fprintf(stderr, "%s: failed to get fd for texture\n", __func__);
-@@ -1491,8 +1571,12 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
- #elif defined(CONFIG_IOSURFACE)
- GLuint tex_id = ssd->tex_id;
- y_0_top = ssd->y_0_top;
-+#if defined(CONFIG_METAL)
-+ if (spice_iosurface_blit_metal(ssd, x, y, w, h)) {
-+ return;
-+ }
-+#endif
- spice_iosurface_blit(ssd, tex_id, !y_0_top);
-- //TODO: cursor stuff
- #endif
-
- trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y);
---
-2.41.0
-
-From 27e45bc5d22b61a0b1f7094595c8eeb02d1258ec Mon Sep 17 00:00:00 2001
-From: osy
-Date: Sat, 10 Jan 2026 10:16:41 -0800
-Subject: [PATCH] hvf: use private APIs to set IPA size when needed
-
----
- hw/arm/virt.c | 2 -
- target/arm/hvf/hvf.c | 98 ++++++++++++++++++++++++++++++--------------
- target/arm/hvf_arm.h | 2 +-
- 3 files changed, 69 insertions(+), 33 deletions(-)
-
-diff --git a/hw/arm/virt.c b/hw/arm/virt.c
-index e2538ae978..3549a91aba 100644
---- a/hw/arm/virt.c
-+++ b/hw/arm/virt.c
-@@ -3201,9 +3201,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
- mc->valid_cpu_types = valid_cpu_types;
- mc->get_default_cpu_node_id = virt_get_default_cpu_node_id;
- mc->kvm_type = virt_kvm_type;
--#if !defined(CONFIG_HVF_PRIVATE)
- mc->hvf_get_physical_address_range = virt_hvf_get_physical_address_range;
--#endif
- assert(!mc->get_hotplug_handler);
- mc->get_hotplug_handler = virt_machine_get_hotplug_handler;
- hc->pre_plug = virt_machine_device_pre_plug_cb;
-diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
-index 1a0f69b496..230240fec7 100644
---- a/target/arm/hvf/hvf.c
-+++ b/target/arm/hvf/hvf.c
-@@ -24,6 +24,7 @@
- #include "cpregs.h"
-
- #include
-+#include
-
- #include "exec/address-spaces.h"
- #include "hw/boards.h"
-@@ -977,41 +978,53 @@ static hv_return_t hvf_vcpu_set_actlr(hv_vcpu_t vcpu, uint64_t value)
- #endif
- }
-
--#if !defined(CONFIG_HVF_PRIVATE)
--
- uint32_t hvf_arm_get_default_ipa_bit_size(void)
- {
-+#if TARGET_OS_OSX
- if (__builtin_available(macOS 13.0, *)) {
- uint32_t default_ipa_size;
- hv_return_t ret = hv_vm_config_get_default_ipa_size(&default_ipa_size);
- assert_hvf_ok(ret);
-
- return default_ipa_size;
-- } else {
-- return 0;
- }
-+#endif
-+ return 0;
- }
-
- uint32_t hvf_arm_get_max_ipa_bit_size(void)
- {
-+ uint64_t ipa_size_4k, ipa_size_16k;
-+ size_t length;
-+ uint32_t max_ipa_size = 0;
-+
-+#if TARGET_OS_OSX
- if (__builtin_available(macOS 13.0, *)) {
-- uint32_t max_ipa_size;
- hv_return_t ret = hv_vm_config_get_max_ipa_size(&max_ipa_size);
- assert_hvf_ok(ret);
-+ }
-+#endif
-
-- /*
-- * We clamp any IPA size we want to back the VM with to a valid PARange
-- * value so the guest doesn't try and map memory outside of the valid
-- * range. This logic just clamps the passed in IPA bit size to the first
-- * valid PARange value <= to it.
-- */
-- return round_down_to_parange_bit_size(max_ipa_size);
-- } else {
-- return 0;
-+ if (!max_ipa_size) {
-+ length = sizeof(uint64_t);
-+ if (sysctlbyname("kern.hv.ipa_size_16k", &ipa_size_16k, &length, NULL, 0)) {
-+ ipa_size_16k = 0;
-+ }
-+ length = sizeof(uint64_t);
-+ if (sysctlbyname("kern.hv.ipa_size_4k", &ipa_size_4k, &length, NULL, 0)) {
-+ ipa_size_4k = 0;
-+ }
-+ max_ipa_size = MIN(ctz64(ipa_size_16k), ctz64(ipa_size_4k));
- }
--}
-
--#endif
-+ /*
-+ * We clamp any IPA size we want to back the VM with to a valid PARange
-+ * value so the guest doesn't try and map memory outside of the valid
-+ * range. This logic just clamps the passed in IPA bit size to the first
-+ * valid PARange value <= to it.
-+ */
-+ return round_down_to_parange_bit_size(max_ipa_size);
-+}
-
- void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu)
- {
-@@ -1075,27 +1088,54 @@ static hv_return_t hvf_set_ipa_granule(hv_vm_config_t config,
- return HV_SUCCESS;
- }
-
-+static hv_return_t hvf_set_ipa_size(hv_vm_config_t config, uint32_t pa_range)
-+{
-+ static hv_return_t (*set_ipa_size)(hv_vm_config_t, uint64_t);
-+ hv_return_t ret;
-+
-+#if TARGET_OS_OSX
-+ if (__builtin_available(macOS 13.0, *)) {
-+ ret = hv_vm_config_set_ipa_size(config, pa_range);
-+ if (ret == HV_SUCCESS) {
-+ chosen_ipa_bit_size = pa_range;
-+ }
-+ return ret;
-+ }
-+#endif
-+
-+ /* older macOS need to use a private API */
-+ if (!set_ipa_size) {
-+ set_ipa_size = dlsym(RTLD_NEXT, "_hv_vm_config_set_ipa_size");
-+ }
-+ if (set_ipa_size) {
-+ ret = set_ipa_size(config, 1ULL << pa_range);
-+ if (ret == HV_SUCCESS) {
-+ chosen_ipa_bit_size = pa_range;
-+ }
-+ return ret;
-+ } else if (!pa_range) {
-+ return HV_SUCCESS;
-+ }
-+
-+ return HV_UNSUPPORTED;
-+}
-+
- hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range,
- uint32_t ipa_granule_size)
- {
- hv_return_t ret;
-- hv_vm_config_t config = NULL;
-+ hv_vm_config_t config = hv_vm_config_create();
-
- #if defined(CONFIG_HVF_PRIVATE)
- if (hvf_tso_mode) {
-- config = hv_vm_config_create();
- _hv_vm_config_set_isa(config, HV_VM_CONFIG_ISA_PRIVATE);
- }
--#else
-- if (__builtin_available(macOS 13.0, *)) {
-- config = hv_vm_config_create();
-- ret = hv_vm_config_set_ipa_size(config, pa_range);
-- if (ret != HV_SUCCESS) {
-- goto cleanup;
-- }
-- chosen_ipa_bit_size = pa_range;
-- }
- #endif
-+
-+ ret = hvf_set_ipa_size(config, pa_range);
-+ if (ret != HV_SUCCESS) {
-+ goto cleanup;
-+ }
-
- if (ipa_granule_size) {
- ret = hvf_set_ipa_granule(config, ipa_granule_size);
-@@ -1107,9 +1147,7 @@ hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range,
- ret = hv_vm_create(config);
-
- cleanup:
-- if (config) {
-- os_release(config);
-- }
-+ os_release(config);
-
- return ret;
- }
-diff --git a/target/arm/hvf_arm.h b/target/arm/hvf_arm.h
-index 482768baa6..13bc8921b3 100644
---- a/target/arm/hvf_arm.h
-+++ b/target/arm/hvf_arm.h
-@@ -22,7 +22,7 @@ void hvf_arm_init_debug(void);
-
- void hvf_arm_set_cpu_features_from_host(ARMCPU *cpu);
-
--#if defined(CONFIG_HVF) && !defined(CONFIG_HVF_PRIVATE)
-+#if defined(CONFIG_HVF)
-
- uint32_t hvf_arm_get_default_ipa_bit_size(void);
- uint32_t hvf_arm_get_max_ipa_bit_size(void);
---
-2.41.0
-
diff --git a/patches/sources b/patches/sources
index d6f9a376..e7b46267 100644
--- a/patches/sources
+++ b/patches/sources
@@ -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"
diff --git a/scripts/build_dependencies.sh b/scripts/build_dependencies.sh
index ff291778..2cfedfbf 100755
--- a/scripts/build_dependencies.sh
+++ b/scripts/build_dependencies.sh
@@ -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}"
diff --git a/scripts/build_utm.sh b/scripts/build_utm.sh
index 155eaa14..8430795d 100755
--- a/scripts/build_utm.sh
+++ b/scripts/build_utm.sh
@@ -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
diff --git a/scripts/install_d3dmetal.sh b/scripts/install_d3dmetal.sh
new file mode 100755
index 00000000..7602ffa9
--- /dev/null
+++ b/scripts/install_d3dmetal.sh
@@ -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 /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"
diff --git a/scripts/pack_dependencies.sh b/scripts/pack_dependencies.sh
index 550040a8..135d0e35 100755
--- a/scripts/pack_dependencies.sh
+++ b/scripts/pack_dependencies.sh
@@ -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"
diff --git a/scripts/package_mac.sh b/scripts/package_mac.sh
index 7a68b243..160dd404 100755
--- a/scripts/package_mac.sh
+++ b/scripts/package_mac.sh
@@ -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"