Skip to content

VisionOS support #299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 23, 2025
51 changes: 19 additions & 32 deletions Editor/LLMBuildProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
#if UNITY_IOS || UNITY_VISIONOS
using System.IO;

#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif

Expand All @@ -18,27 +17,7 @@ public class LLMBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWi
public void OnPreprocessBuild(BuildReport report)
{
Application.logMessageReceived += OnBuildError;
string platform = null;
switch (report.summary.platform)
{
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
platform = "windows";
break;
case BuildTarget.StandaloneLinux64:
platform = "linux";
break;
case BuildTarget.StandaloneOSX:
platform = "macos";
break;
case BuildTarget.Android:
platform = "android";
break;
case BuildTarget.iOS:
platform = "ios";
break;
}
LLMBuilder.Build(platform);
LLMBuilder.Build(report.summary.platform);
AssetDatabase.Refresh();
}

Expand All @@ -48,13 +27,16 @@ private void OnBuildError(string condition, string stacktrace, LogType type)
if (type == LogType.Error) BuildCompleted();
}

#if UNITY_IOS
#if UNITY_IOS || UNITY_VISIONOS
/// <summary>
/// Postprocess the iOS Build
/// </summary>
public static void PostprocessIOSBuild(string outputPath)
public static void PostprocessIOSBuild(BuildTarget buildTarget, string outputPath)
{
string projPath = PBXProject.GetPBXProjectPath(outputPath);
#if UNITY_VISIONOS
projPath = projPath.Replace("Unity-iPhone", "Unity-VisionOS");
#endif
PBXProject project = new PBXProject();
project.ReadFromFile(projPath);

Expand All @@ -67,8 +49,7 @@ public static void PostprocessIOSBuild(string outputPath)
project.AddFrameworkToProject(unityMainTargetGuid, "Accelerate.framework", false);
project.AddFrameworkToProject(targetGuid, "Accelerate.framework", false);

// Remove libundreamai_ios.a from Embed Frameworks
string libraryFile = Path.Combine("Libraries", LLMBuilder.PluginLibraryDir("iOS", true), "libundreamai_ios.a");
string libraryFile = LLMUnitySetup.RelativePath(LLMUnitySetup.SearchDirectory(outputPath, $"libundreamai_{buildTarget.ToString().ToLower()}.a"), outputPath);
string fileGuid = project.FindFileGuidByProjectPath(libraryFile);
if (string.IsNullOrEmpty(fileGuid)) Debug.LogError($"Library file {libraryFile} not found in project");
else
Expand All @@ -81,21 +62,27 @@ public static void PostprocessIOSBuild(string outputPath)
break;
}
}
project.RemoveFileFromBuild(unityMainTargetGuid, fileGuid);

project.AddFileToBuild(unityMainTargetGuid, fileGuid);
project.AddFileToBuild(targetGuid, fileGuid);
}

project.WriteToFile(projPath);
AssetDatabase.ImportAsset(projPath);
}

#endif

// called after the build
public void OnPostprocessBuild(BuildReport report)
{
#if UNITY_IOS
PostprocessIOSBuild(report.summary.outputPath);
#if UNITY_IOS || UNITY_VISIONOS
PostprocessIOSBuild(report.summary.platform, report.summary.outputPath);
#endif
BuildCompleted();
EditorApplication.delayCall += () =>
{
BuildCompleted();
};
}

public void BuildCompleted()
Expand All @@ -108,4 +95,4 @@ public void BuildCompleted()
};
}
}
}
}
78 changes: 57 additions & 21 deletions Runtime/LLMBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace LLMUnity
/// <summary>
/// Class implementing the LLMUnity builder.
/// </summary>
public class LLMBuilder
public class LLMBuilder : AssetPostprocessor
{
static List<StringPair> movedPairs = new List<StringPair>();
public static string BuildTempDir = Path.Combine(Application.temporaryCachePath, "LLMUnityBuild");
Expand Down Expand Up @@ -98,7 +98,7 @@ public static void MovePath(string source, string target)
/// <param name="path">path</param>
public static bool DeletePath(string path)
{
string[] allowedDirs = new string[] { LLMUnitySetup.GetAssetPath(), BuildTempDir, PluginDir("Android"), PluginDir("iOS")};
string[] allowedDirs = new string[] { LLMUnitySetup.GetAssetPath(), BuildTempDir, PluginDir("Android"), PluginDir("iOS"), PluginDir("VisionOS")};
bool deleteOK = false;
foreach (string allowedDir in allowedDirs) deleteOK = deleteOK || LLMUnitySetup.IsSubPath(path, allowedDir);
if (!deleteOK)
Expand Down Expand Up @@ -162,39 +162,75 @@ static void AddActionAddMeta(string target)
/// Moves libraries in the correct place for building
/// </summary>
/// <param name="platform">target platform</param>
public static void BuildLibraryPlatforms(string platform)
public static void BuildLibraryPlatforms(BuildTarget buildTarget)
{
List<string> platforms = new List<string>(){ "windows", "macos", "linux", "android", "ios", "setup" };
platforms.Remove(platform);
string platform = "";
switch (buildTarget)
{
case BuildTarget.StandaloneWindows:
case BuildTarget.StandaloneWindows64:
platform = "windows";
break;
case BuildTarget.StandaloneLinux64:
platform = "linux";
break;
case BuildTarget.StandaloneOSX:
platform = "macos";
break;
case BuildTarget.Android:
platform = "android";
break;
case BuildTarget.iOS:
platform = "ios";
break;
case BuildTarget.VisionOS:
platform = "visionos";
break;
}

foreach (string source in Directory.GetDirectories(LLMUnitySetup.libraryPath))
{
string sourceName = Path.GetFileName(source);
foreach (string platformPrefix in platforms)
bool move = !sourceName.StartsWith(platform);
move = move || (sourceName.Contains("cuda") && !sourceName.Contains("full") && LLMUnitySetup.FullLlamaLib);
move = move || (sourceName.Contains("cuda") && sourceName.Contains("full") && !LLMUnitySetup.FullLlamaLib);
if (move)
{
bool move = sourceName.StartsWith(platformPrefix);
move = move || (sourceName.Contains("cuda") && !sourceName.Contains("full") && LLMUnitySetup.FullLlamaLib);
move = move || (sourceName.Contains("cuda") && sourceName.Contains("full") && !LLMUnitySetup.FullLlamaLib);
if (move)
{
string target = Path.Combine(BuildTempDir, sourceName);
MoveAction(source, target);
MoveAction(source + ".meta", target + ".meta");
}
string target = Path.Combine(BuildTempDir, sourceName);
MoveAction(source, target);
MoveAction(source + ".meta", target + ".meta");
}
}

if (platform == "android" || platform == "ios")
if (buildTarget == BuildTarget.Android || buildTarget == BuildTarget.iOS || buildTarget == BuildTarget.VisionOS)
{
string pluginPlatform = platform == "android" ? "Android" : "iOS";
string source = Path.Combine(LLMUnitySetup.libraryPath, platform);
string target = PluginLibraryDir(pluginPlatform);
string pluginDir = PluginDir(pluginPlatform);
string target = PluginLibraryDir(buildTarget.ToString());
string pluginDir = PluginDir(buildTarget.ToString());
MoveAction(source, target);
MoveAction(source + ".meta", target + ".meta");
AddActionAddMeta(pluginDir);
}
}


static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
{
string pathToPlugin = Path.Combine("Assets", PluginLibraryDir(BuildTarget.VisionOS.ToString(), true), "libundreamai_visionos.a");
for (int i = 0; i < movedAssets.Length; i++)
{
if(movedAssets[i] == pathToPlugin)
{
var importer = AssetImporter.GetAtPath(pathToPlugin) as PluginImporter;
if (importer != null && importer.isNativePlugin) {
importer.SetCompatibleWithPlatform(BuildTarget.VisionOS, true);
importer.SetPlatformData(BuildTarget.VisionOS, "CPU", "ARM64");
AssetDatabase.ImportAsset(pathToPlugin);
}
}
}
}

/// <summary>
/// Bundles the model information
/// </summary>
Expand All @@ -207,11 +243,11 @@ public static void BuildModels()
/// <summary>
/// Bundles the models and libraries
/// </summary>
public static void Build(string platform)
public static void Build(BuildTarget buildTarget)
{
DeletePath(BuildTempDir);
Directory.CreateDirectory(BuildTempDir);
BuildLibraryPlatforms(platform);
BuildLibraryPlatforms(buildTarget);
BuildModels();
}

Expand Down
63 changes: 13 additions & 50 deletions Runtime/LLMLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,8 @@ public static IntPtr LoadLibrary(string libraryName)
handle = Linux.dlopen(libraryName);
else if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXServer)
handle = Mac.dlopen(libraryName);
else if (Application.platform == RuntimePlatform.Android)
handle = Android.dlopen(libraryName);
else if (Application.platform == RuntimePlatform.IPhonePlayer)
handle = iOS.dlopen(libraryName);
else if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.VisionOS)
handle = Mobile.dlopen(libraryName);
else
throw new PlatformNotSupportedException($"Current platform is unknown, unable to load library '{libraryName}'.");

Expand All @@ -167,10 +165,8 @@ public static IntPtr GetSymbol(IntPtr library, string symbolName)
handle = Linux.dlsym(library, symbolName);
else if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXServer)
handle = Mac.dlsym(library, symbolName);
else if (Application.platform == RuntimePlatform.Android)
handle = Android.dlsym(library, symbolName);
else if (Application.platform == RuntimePlatform.IPhonePlayer)
handle = iOS.dlsym(library, symbolName);
else if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.VisionOS)
handle = Mobile.dlsym(library, symbolName);
else
throw new PlatformNotSupportedException($"Current platform is unknown, unable to load symbol '{symbolName}' from library {library}.");

Expand All @@ -192,10 +188,8 @@ public static void FreeLibrary(IntPtr library)
Linux.dlclose(library);
else if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.OSXServer)
Mac.dlclose(library);
else if (Application.platform == RuntimePlatform.Android)
Android.dlclose(library);
else if (Application.platform == RuntimePlatform.IPhonePlayer)
iOS.dlclose(library);
else if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.VisionOS)
Mobile.dlclose(library);
else
throw new PlatformNotSupportedException($"Current platform is unknown, unable to close library '{library}'.");
}
Expand Down Expand Up @@ -289,11 +283,11 @@ private static class Win32
public static extern void FreeLibrary(IntPtr hModule);
}

private static class Android
private static class Mobile
{
public static IntPtr dlopen(string path) => dlopen(path, 1);

#if UNITY_ANDROID
#if UNITY_ANDROID || UNITY_IOS || UNITY_VISIONOS
[DllImport("__Internal")]
public static extern IntPtr dlopen(string filename, int flags);

Expand All @@ -318,41 +312,6 @@ public static int dlclose(IntPtr handle)
return default;
}

#endif
}

private static class iOS
{
public static IntPtr dlopen(string path) => dlopen(path, 1);

#if UNITY_IOS
// LoadLibrary for iOS
[DllImport("__Internal")]
public static extern IntPtr dlopen(string filename, int flags);

// GetSymbol for iOS
[DllImport("__Internal")]
public static extern IntPtr dlsym(IntPtr handle, string symbol);

// FreeLibrary for iOS
[DllImport("__Internal")]
public static extern int dlclose(IntPtr handle);
#else
public static IntPtr dlopen(string filename, int flags)
{
return default;
}

public static IntPtr dlsym(IntPtr handle, string symbol)
{
return default;
}

public static int dlclose(IntPtr handle)
{
return default;
}

#endif
}
}
Expand All @@ -369,7 +328,7 @@ public class LLMLib
static bool has_avx512 = false;
List<IntPtr> dependencyHandles = new List<IntPtr>();

#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR
#if (UNITY_ANDROID || UNITY_IOS || UNITY_VISIONOS) && !UNITY_EDITOR

public LLMLib(string arch) {}

Expand Down Expand Up @@ -720,6 +679,10 @@ public static List<string> PossibleArchitectures(bool gpu = false)
{
architectures.Add("ios");
}
else if (Application.platform == RuntimePlatform.VisionOS)
{
architectures.Add("visionos");
}
else
{
string error = "Unknown OS";
Expand Down
15 changes: 14 additions & 1 deletion Runtime/LLMUnitySetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public static string GetAssetPath(string relPath = "")

public static string GetDownloadAssetPath(string relPath = "")
{
string assetsDir = (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer) ? Application.persistentDataPath : Application.streamingAssetsPath;
string assetsDir = (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.VisionOS) ? Application.persistentDataPath : Application.streamingAssetsPath;
return Path.Combine(assetsDir, relPath).Replace('\\', '/');
}

Expand Down Expand Up @@ -380,6 +380,19 @@ public static string RelativePath(string fullPath, string basePath)
return relativePath;
}

public static string SearchDirectory(string directory, string targetFileName)
{
string[] files = Directory.GetFiles(directory, targetFileName);
if (files.Length > 0) return files[0];
string[] subdirectories = Directory.GetDirectories(directory);
foreach (var subdirectory in subdirectories)
{
string result = SearchDirectory(subdirectory, targetFileName);
if (result != null) return result;
}
return null;
}

#if UNITY_EDITOR

[HideInInspector] public static float libraryProgress = 1;
Expand Down
Loading