Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions AIDevGallery.Tests/UnitTests/Utils/CudaDllManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using AIDevGallery.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AIDevGallery.Tests.UnitTests;

[TestClass]
public class CudaDllManagerTests
{
[TestMethod]
public void GetCudaDllFolderPathReturnsValidPath()
{
// Act
var folderPath = CudaDllManager.GetCudaDllFolderPath();

// Assert
Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath), "Folder path should not be null or empty");
Assert.IsTrue(folderPath.Contains("CudaDlls"), "Folder path should contain 'CudaDlls' directory name");
}

[TestMethod]
public void GetStatusMessageReflectsDllAvailability()
{
// Arrange
var isAvailable = CudaDllManager.IsCudaDllAvailable();
var message = CudaDllManager.GetStatusMessage();

// Assert - Verify status message logic is consistent with actual DLL availability
if (isAvailable)
{
Assert.IsTrue(
message.Contains("available") || message.Contains("CUDA is available"),
"Status message should indicate CUDA is available when DLL is present");
}
else
{
Assert.IsTrue(
message.Contains("download") || message.Contains("failed") || message.Contains("detected"),
"Status message should indicate download option or status when DLL is not available");
}
}
}
3 changes: 3 additions & 0 deletions AIDevGallery/AIDevGallery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@
<ItemGroup Condition="'$(IsWindowsAppSDKPreRelease)' == 'true' And '$(SelfContainedIfPreviewWASDK)' == 'true'">
<None Include="$(PkgMicrosoft_WindowsAppSDK_AI)\runtimes-framework\win-$(Platform)\native\*.onnxe" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<!-- Exclude CUDA DLL from package (will be downloaded on-demand) -->
<Import Project="ExcludeExtraLibs.props"/>

<ItemGroup>
<Page Update="Samples\WCRAPIs\RestyleImage.xaml">
Expand Down
36 changes: 36 additions & 0 deletions AIDevGallery/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
using AIDevGallery.Utils;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AIDevGallery;
Expand Down Expand Up @@ -46,6 +49,39 @@ protected override async void OnLaunched(LaunchActivatedEventArgs args)
MainWindow = new MainWindow(activationParam);

MainWindow.Activate();

// Check for NVIDIA GPU and optionally download CUDA DLL in background
// This runs after the main window is shown to avoid impacting startup time
// Using Task.Run with ConfigureAwait(false) and low priority to ensure minimal impact
_ = Task.Run(
async () =>
{
await Task.Delay(1000).ConfigureAwait(false);

try
{
Debug.WriteLine("[CUDA] Background check initiated");
if (CudaDllManager.HasNvidiaGpu() && !CudaDllManager.IsCudaDllAvailable())
{
Debug.WriteLine("[CUDA] NVIDIA GPU detected without CUDA DLL, starting background download");

// Silently attempt to download CUDA DLL in background
var success = await CudaDllManager.EnsureCudaDllAsync().ConfigureAwait(false);
Debug.WriteLine($"[CUDA] Background download completed, success: {success}");
}
else
{
Debug.WriteLine("[CUDA] No background download needed");
}
}
catch (Exception ex)
{
Debug.WriteLine($"[CUDA] Background download error: {ex.Message}");

// Silently fail - app will work without CUDA
}
},
CancellationToken.None);
}

internal static List<ModelType> FindSampleItemById(string id)
Expand Down
38 changes: 38 additions & 0 deletions AIDevGallery/ExcludeExtraLibs.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project>
<!-- Remove CUDA EP on Windows x64. onnxruntime-genai-cuda.dll will be downloaded on-demand for NVIDIA GPU users. -->
<Target Name="ExcludeCudaLibs" Condition="'$(RuntimeIdentifier)'=='win-x64' Or '$(Platform)'=='x64'" AfterTargets="ResolvePackageAssets">
<ItemGroup>
<!-- Exclude onnxruntime-genai-cuda.dll (81 MB, will be downloaded on-demand for NVIDIA GPUs) -->
<NativeCopyLocalItems Remove="@(NativeCopyLocalItems)"
Condition="'%(Filename)' == 'onnxruntime-genai-cuda'" />
<ReferenceCopyLocalPaths Remove="@(ReferenceCopyLocalPaths)"
Condition="'%(Filename)' == 'onnxruntime-genai-cuda'" />
<RuntimeCopyLocalItems Remove="@(RuntimeCopyLocalItems)"
Condition="'%(Filename)' == 'onnxruntime-genai-cuda'" />
</ItemGroup>
<Message Importance="Normal" Text="Excluded onnxruntime-genai-cuda.dll from package (will be downloaded on-demand)." />
</Target>

<!-- Additional cleanup after CopyFilesToOutputDirectory to ensure onnxruntime-genai-cuda.dll is removed -->
<Target Name="RemoveCudaDllsFromOutput" Condition="'$(RuntimeIdentifier)'=='win-x64' Or '$(Platform)'=='x64'" AfterTargets="CopyFilesToOutputDirectory">
<ItemGroup>
<CudaDllsToDelete Include="$(OutDir)**\onnxruntime-genai-cuda.dll" />
</ItemGroup>
<Delete Files="@(CudaDllsToDelete)" />
<Message Importance="High" Text="Deleted onnxruntime-genai-cuda.dll from output directory: @(CudaDllsToDelete)" />
</Target>

<!-- Remove CUDA DLLs before MSIX packaging -->
<Target Name="RemoveCudaDllsBeforePackaging" Condition="'$(RuntimeIdentifier)'=='win-x64' Or '$(Platform)'=='x64'"
BeforeTargets="_GenerateCurrentProjectAppxManifest;_GenerateAppxPackage">
<ItemGroup>
<AppxPackagePayload Remove="@(AppxPackagePayload)"
Condition="'%(Filename)' == 'onnxruntime-genai-cuda'" />
<AppxUploadPackagePayload Remove="@(AppxUploadPackagePayload)"
Condition="'%(Filename)' == 'onnxruntime-genai-cuda'" />
<CudaDllsInLayout Include="$(LayoutDir)**\onnxruntime-genai-cuda.dll" />
</ItemGroup>
<Delete Files="@(CudaDllsInLayout)" />
<Message Importance="High" Text="Removed onnxruntime-genai-cuda.dll from MSIX package payload" />
</Target>
</Project>
25 changes: 25 additions & 0 deletions AIDevGallery/Pages/SettingsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,31 @@
</toolkit:SettingsExpander.ItemTemplate>
</toolkit:SettingsExpander>

<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="GPU Acceleration" />
<toolkit:SettingsCard
x:Name="CudaAccelerationCard"
Description="Improved performance on RTX GPUs"
Header="NVIDIA GPU Acceleration"
HeaderIcon="{ui:FontIcon Glyph=&#xE950;}"
Visibility="Collapsed">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock
x:Name="CudaStatusText"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Button
x:Name="DownloadCudaButton"
Click="DownloadCuda_Click"
Content="Download CUDA Support"
Visibility="Collapsed" />
<ProgressBar
x:Name="CudaDownloadProgress"
IsIndeterminate="False"
Maximum="100"
Visibility="Collapsed" />
</StackPanel>
</toolkit:SettingsCard>

<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="App Content Search" />
<toolkit:SettingsExpander
Description="Enable app semantic search with AppContentIndexer. If disabled, search falls back to in-memory lexical search."
Expand Down
145 changes: 145 additions & 0 deletions AIDevGallery/Pages/SettingsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ protected override void OnNavigatedTo(NavigationEventArgs e)

DiagnosticDataToggleSwitch.IsOn = App.AppData.IsDiagnosticDataEnabled;
SemanticSearchToggleSwitch.IsOn = App.AppData.IsAppContentSearchEnabled;

// Initialize CUDA acceleration UI
InitializeCudaAccelerationUI();

if (e.Parameter is string manageModels && manageModels == "ModelManagement")
{
ModelsExpander.IsExpanded = true;
Expand Down Expand Up @@ -352,4 +356,145 @@ private void EndMovingCache()
_cts?.Dispose();
_cts = null;
}

private void InitializeCudaAccelerationUI()
{
Debug.WriteLine("[CUDA] Initializing CUDA acceleration UI");

// Always show the card
CudaAccelerationCard.Visibility = Visibility.Visible;

// Check if user has NVIDIA GPU
if (CudaDllManager.HasNvidiaGpu())
{
Debug.WriteLine("[CUDA] NVIDIA GPU detected, showing download options");
UpdateCudaStatus();
}
else
{
Debug.WriteLine("[CUDA] No NVIDIA GPU detected, showing informational message");

// Show message that NVIDIA GPU is not supported
CudaStatusText.Text = "NVIDIA GPU not detected.";
DownloadCudaButton.Visibility = Visibility.Collapsed;
CudaDownloadProgress.Visibility = Visibility.Collapsed;
}
}

private void UpdateCudaStatus()
{
CudaStatusText.Text = CudaDllManager.GetStatusMessage();

if (CudaDllManager.IsCudaDllAvailable())
{
DownloadCudaButton.Content = "Open Folder";
DownloadCudaButton.Visibility = Visibility.Visible;
CudaDownloadProgress.Visibility = Visibility.Collapsed;
}
else
{
DownloadCudaButton.Content = "Download CUDA Support";
DownloadCudaButton.Visibility = Visibility.Visible;
CudaDownloadProgress.Visibility = Visibility.Collapsed;
}
}

private async void DownloadCuda_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("[CUDA] Download button clicked");

// If CUDA is already available, open the folder
if (CudaDllManager.IsCudaDllAvailable())
{
Debug.WriteLine("[CUDA] CUDA already available, opening folder");
try
{
var folderPath = CudaDllManager.GetCudaDllFolderPath();
if (System.IO.Directory.Exists(folderPath))
{
Debug.WriteLine($"[CUDA] Opening folder: {folderPath}");
System.Diagnostics.Process.Start("explorer.exe", folderPath);
}
else
{
Debug.WriteLine($"[CUDA] Folder does not exist: {folderPath}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"[CUDA] Error opening folder: {ex.Message}");
}

return;
}

Debug.WriteLine("[CUDA] Starting manual download from UI");
try
{
DownloadCudaButton.Visibility = Visibility.Collapsed;
CudaDownloadProgress.Visibility = Visibility.Visible;
CudaDownloadProgress.IsIndeterminate = false;
CudaDownloadProgress.Value = 0;

var progress = new Progress<float>(value =>
{
DispatcherQueue.TryEnqueue(() =>
{
CudaDownloadProgress.Value = value * 100;
CudaStatusText.Text = $"Downloading... {value:P0}";
});
});

using var cts = new CancellationTokenSource();

// Allow retry when user manually clicks the download button
bool success = await CudaDllManager.EnsureCudaDllAsync(progress, forceRetry: true, cts.Token);

Debug.WriteLine($"[CUDA] Manual download completed, success: {success}");

if (success)
{
CudaStatusText.Text = "NVIDIA GPU acceleration is now available!";

ContentDialog successDialog = new()
{
Title = "Download Complete",
Content = "NVIDIA CUDA acceleration has been successfully downloaded. Restart the app to use improved GPU performance.",
CloseButtonText = "OK",
XamlRoot = this.XamlRoot
};
await successDialog.ShowAsync();
}
else
{
CudaStatusText.Text = "Download failed. Using DirectML acceleration instead.";

ContentDialog errorDialog = new()
{
Title = "Download Failed",
Content = "Failed to download CUDA support. The app will continue to use DirectML for GPU acceleration.",
CloseButtonText = "OK",
XamlRoot = this.XamlRoot
};
await errorDialog.ShowAsync();
}

UpdateCudaStatus();
}
catch (Exception ex)
{
CudaStatusText.Text = "An error occurred during download.";

ContentDialog errorDialog = new()
{
Title = "Error",
Content = $"An error occurred: {ex.Message}",
CloseButtonText = "OK",
XamlRoot = this.XamlRoot
};
await errorDialog.ShowAsync();

UpdateCudaStatus();
}
}
}
Loading
Loading