Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@
"System language": "System language",
"Is your language missing or incomplete?": "Is your language missing or incomplete?",
"Appearance": "Appearance",
"Rendering": "Rendering",
"Automatically switch to software rendering when Windows has no hardware GPU": "Automatically switch to software rendering when Windows has no hardware GPU",
"UniGetUI on the background and system tray": "UniGetUI on the background and system tray",
"Package lists": "Package lists",
"Use classic mode": "Use classic mode",
Expand Down
22 changes: 19 additions & 3 deletions src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Runtime.InteropServices;
using Avalonia;
#if WINDOWS
using Avalonia.Win32;
#endif
using Avalonia.Threading;
using UniGetUI.Core.Data;
using UniGetUI.Core.Logging;
Expand Down Expand Up @@ -89,9 +92,22 @@ Welcome to UniGetUI Version {CoreData.VersionName}
}

public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
{
AppBuilder builder = AppBuilder.Configure<App>()
.UsePlatformDetect();

#if WINDOWS
if (WindowsAvaloniaRenderingPolicy.ShouldUseSoftwareRendering)
{
builder = builder.With(new Win32PlatformOptions
{
RenderingMode = [Win32RenderingMode.Software],
});
}
#endif

return builder.LogToTrace();
}

private static bool ShouldPrepareCliConsole(IReadOnlyList<string> args)
{
Expand Down
6 changes: 6 additions & 0 deletions src/UniGetUI.Avalonia/Infrastructure/MotionPreference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ public static bool ReducedMotion
get
{
if (OperatingSystem.IsWindows())
{
if (WindowsAvaloniaRenderingPolicy.ShouldReduceMotion)
return true;

return GetWindowsReducedMotion();
}

return _cachedUnix ??= GetUnixReducedMotion();
}
}
Expand Down
186 changes: 186 additions & 0 deletions src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using UniGetUI.Core.Logging;
using CoreSettings = global::UniGetUI.Core.SettingsEngine.Settings;

namespace UniGetUI.Avalonia.Infrastructure;

internal static class WindowsAvaloniaRenderingPolicy
{
private static bool? _hasHardwareGpu;
private static bool? _shouldUseSoftwareRendering;

public static bool ShouldUseSoftwareRendering
{
get
{
if (_shouldUseSoftwareRendering is not null)
return _shouldUseSoftwareRendering.Value;

if (!OperatingSystem.IsWindows() || Design.IsDesignMode)
return false;

if (CoreSettings.Get(CoreSettings.K.DisableAutoSoftwareRenderingOnGpuLessHosts))
return false;

_shouldUseSoftwareRendering = !HasHardwareGpu;
if (_shouldUseSoftwareRendering.Value)
{
Logger.Warn(
"No hardware GPU detected. Using Avalonia software rendering and reduced motion.");
}

return _shouldUseSoftwareRendering.Value;
}
}

public static bool ShouldReduceMotion => ShouldUseSoftwareRendering;

private static bool HasHardwareGpu
{
get
{
if (_hasHardwareGpu is not null)
return _hasHardwareGpu.Value;

Stopwatch stopwatch = Stopwatch.StartNew();
_hasHardwareGpu = DetectHardwareGpu();
stopwatch.Stop();

Logger.Info(
$"DXGI hardware GPU detection took {stopwatch.Elapsed.TotalMilliseconds:F1} ms; hardware GPU: {_hasHardwareGpu.Value}");

return _hasHardwareGpu.Value;
}
}

private static bool DetectHardwareGpu()
{
try
{
Guid factoryIid = typeof(IDXGIFactory1).GUID;
if (CreateDXGIFactory1(ref factoryIid, out object factoryObj) != HResult.Ok

Check warning on line 63 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

P/invoke method 'UniGetUI.Avalonia.Infrastructure.WindowsAvaloniaRenderingPolicy.CreateDXGIFactory1(ref Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.

Check warning on line 63 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

P/invoke method 'UniGetUI.Avalonia.Infrastructure.WindowsAvaloniaRenderingPolicy.CreateDXGIFactory1(ref Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.

Check warning on line 63 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

P/invoke method 'UniGetUI.Avalonia.Infrastructure.WindowsAvaloniaRenderingPolicy.CreateDXGIFactory1(ref Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.

Check warning on line 63 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / test-codebase

P/invoke method 'UniGetUI.Avalonia.Infrastructure.WindowsAvaloniaRenderingPolicy.CreateDXGIFactory1(ref Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.

Check warning on line 63 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / test-codebase

P/invoke method 'UniGetUI.Avalonia.Infrastructure.WindowsAvaloniaRenderingPolicy.CreateDXGIFactory1(ref Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.
|| factoryObj is not IDXGIFactory1 factory)
{
Logger.Warn("Could not create DXGI factory; assuming a hardware GPU is present.");
return true;
}

try
{
for (uint i = 0; ; i++)
{
int hr = factory.EnumAdapters1(i, out IDXGIAdapter1 adapter);
if (hr == HResult.DxgiErrorNotFound || hr != HResult.Ok || adapter is null)
break;

try
{
if (adapter.GetDesc1(out DXGI_ADAPTER_DESC1 desc) != HResult.Ok)
continue;

bool isSoftwareAdapter =
(desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) != 0
|| (desc.VendorId == MicrosoftVendorId
&& desc.DeviceId == BasicRenderDriverDeviceId);

if (!isSoftwareAdapter)
return true;
}
finally
{
Marshal.ReleaseComObject(adapter);

Check warning on line 93 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

This call site is reachable on all platforms. 'Marshal.ReleaseComObject(object)' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
}
}
}
finally
{
Marshal.ReleaseComObject(factory);

Check warning on line 99 in src/UniGetUI.Avalonia/Infrastructure/WindowsAvaloniaRenderingPolicy.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

This call site is reachable on all platforms. 'Marshal.ReleaseComObject(object)' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
}

return false;
}
catch (Exception ex)
{
Logger.Warn("Could not detect DXGI hardware GPU; assuming one is present.");
Logger.Warn(ex);
return true;
}
}

[DllImport("dxgi.dll", ExactSpelling = true)]
private static extern int CreateDXGIFactory1(
ref Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] out object ppFactory
);

private static class HResult
{
public const int Ok = 0;
public const int DxgiErrorNotFound = unchecked((int)0x887A0002);
}

private const uint DXGI_ADAPTER_FLAG_SOFTWARE = 2;

// Microsoft Basic Render Driver (WARP) is enumerated with this VendorId/DeviceId pair.
private const uint MicrosoftVendorId = 0x1414;
private const uint BasicRenderDriverDeviceId = 0x8C;

[ComImport]
[Guid("770aae78-f26f-4dba-a829-253c83d1b387")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IDXGIFactory1
{
void SetPrivateData();
void SetPrivateDataInterface();
void GetPrivateData();
void GetParent();
void EnumAdapters();
void MakeWindowAssociation();
void GetWindowAssociation();
void CreateSwapChain();
void CreateSoftwareAdapter();

[PreserveSig]
int EnumAdapters1(uint adapter, out IDXGIAdapter1 ppAdapter);

[PreserveSig]
bool IsCurrent();
}

[ComImport]
[Guid("29038f61-3839-4626-91fd-086879011a05")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IDXGIAdapter1
{
void SetPrivateData();
void SetPrivateDataInterface();
void GetPrivateData();
void GetParent();
void EnumOutputs();
void GetDesc();
void CheckInterfaceSupport();

[PreserveSig]
int GetDesc1(out DXGI_ADAPTER_DESC1 pDesc);
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DXGI_ADAPTER_DESC1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string Description;

public uint VendorId;
public uint DeviceId;
public uint SubSysId;
public uint Revision;
public UIntPtr DedicatedVideoMemory;
public UIntPtr DedicatedSystemMemory;
public UIntPtr SharedSystemMemory;
public uint AdapterLuidLowPart;
public int AdapterLuidHighPart;
public uint Flags;
}
}
1 change: 1 addition & 0 deletions src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@
<Compile Include="Infrastructure\RelayCommand.cs" />
<Compile Include="Infrastructure\SingleInstanceRedirector.cs" />
<Compile Include="Infrastructure\UninstallConfirmationDialog.cs" />
<Compile Include="Infrastructure\WindowsAvaloniaRenderingPolicy.cs" />
<Compile Update="App.axaml.cs">
<DependentUpon>App.axaml</DependentUpon>
</Compile>
Expand Down
16 changes: 16 additions & 0 deletions src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Interface_P.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@
CornerRadius="0,0,8,8"
BorderThickness="1,0,1,1"/>

<Border Height="16"/>

<settings:TranslatedTextBlock Text="Rendering"
FontSize="14"
FontWeight="SemiBold"
Margin="44,32,4,8"
automation:AutomationProperties.HeadingLevel="2"
IsVisible="{Binding IsWindows}"/>

<settings:CheckboxCard SettingName="DisableAutoSoftwareRenderingOnGpuLessHosts"
Text="{t:Translate Automatically switch to software rendering when Windows has no hardware GPU}"
WarningText="{t:Translate Restart UniGetUI to apply this change}"
StateChangedCommand="{Binding ShowRestartRequiredCommand}"
CornerRadius="8"
IsVisible="{Binding IsWindows}"/>

<Border Height="16"/>

<StackPanel x:Name="SystemTraySection" Orientation="Vertical">
Expand Down
2 changes: 2 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public enum K
RedactUsernameInLog,
DisableReleaseNotesOnUpdate,
LastKnownBuildNumber,
DisableAutoSoftwareRenderingOnGpuLessHosts,

Test1,
Test2,
Expand Down Expand Up @@ -205,6 +206,7 @@ public static string ResolveKey(K key)
K.RedactUsernameInLog => "RedactUsernameInLog",
K.DisableReleaseNotesOnUpdate => "DisableReleaseNotesOnUpdate",
K.LastKnownBuildNumber => "LastKnownBuildNumber",
K.DisableAutoSoftwareRenderingOnGpuLessHosts => "DisableAutoSoftwareRenderingOnGpuLessHosts",

K.Test1 => "TestSetting1",
K.Test2 => "TestSetting2",
Expand Down
Loading