Skip to content
This repository has been archived by the owner on Oct 1, 2024. It is now read-only.

Commit

Permalink
Add support for avalonia (#6)
Browse files Browse the repository at this point in the history
* add avalonia support

* only lock around skia flush

* addressed review

* cleanup

* add fallback size if avalonia attempts to render but the window size is 0. read desktop scale after enabling dpi check

* fix getting window handle on linux. skip render is size is 0
  • Loading branch information
emmauss authored and gdkchan committed Jul 23, 2022
1 parent 7a34599 commit f034eae
Show file tree
Hide file tree
Showing 38 changed files with 3,095 additions and 311 deletions.
32 changes: 26 additions & 6 deletions Ryujinx.Ava/AppHost.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC;
using Avalonia;
using Avalonia.Input;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
Expand All @@ -13,6 +14,7 @@
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Vulkan;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
Expand All @@ -22,6 +24,7 @@
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
Expand Down Expand Up @@ -584,7 +587,23 @@ private void InitializeSwitchInstance()
{
VirtualFileSystem.ReloadKeySet();

IRenderer renderer = new Renderer();
IRenderer renderer;

if (Program.UseVulkan)
{
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
renderer = new VulkanGraphicsDevice(vulkan.Instance.InternalHandle,
vulkan.Device.InternalHandle,
vulkan.PhysicalDevice.InternalHandle,
vulkan.Device.Queue.InternalHandle,
vulkan.PhysicalDevice.QueueFamilyIndex,
vulkan.Device.Lock);
}
else
{
renderer = new Renderer();
}

IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();

BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
Expand Down Expand Up @@ -792,9 +811,12 @@ private unsafe void RenderLoop()

_renderer.ScreenCaptured += Renderer_ScreenCaptured;

(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GameContext));
if (!Program.UseVulkan)
{
(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));

Renderer.MakeCurrent();
Renderer.MakeCurrent();
}

Device.Gpu.Renderer.Initialize(_glLogLevel);

Expand Down Expand Up @@ -853,16 +875,14 @@ private void Present(object image)
dockedMode += $" ({scale}x)";
}

string vendor = _renderer is Renderer renderer ? renderer.GpuVendor : "";

StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
Device.EnableDeviceVsync,
Device.GetVolume(),
dockedMode,
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
$"GPU: {vendor}"));
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));

Renderer.Present(image);
}
Expand Down
38 changes: 33 additions & 5 deletions Ryujinx.Ava/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Avalonia.OpenGL;
using Avalonia.Rendering;
using Avalonia.Threading;
using Ryujinx.Ava.Ui.Backend;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
Expand All @@ -11,9 +12,12 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Modules;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -25,17 +29,20 @@ namespace Ryujinx.Ava
internal class Program
{
public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static string CommandLineProfile { get; set; }
public static bool PreviewerDetached { get; private set; }

public static RenderTimer RenderTimer { get; private set; }
public static bool UseVulkan { get; private set; }

[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);

private const uint MB_ICONWARNING = 0x30;
private const int BaseDpi = 96;

public static void Main(string[] args)
{
Expand Down Expand Up @@ -66,7 +73,7 @@ public static AppBuilder BuildAvaloniaApp()
EnableMultiTouch = true,
EnableIme = true,
UseEGL = false,
UseGpu = true,
UseGpu = !UseVulkan,
GlProfiles = new List<GlVersion>()
{
new GlVersion(GlProfileType.OpenGL, 4, 3)
Expand All @@ -75,7 +82,7 @@ public static AppBuilder BuildAvaloniaApp()
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
UseWgl = true,
UseWgl = !UseVulkan,
WglProfiles = new List<GlVersion>()
{
new GlVersion(GlProfileType.OpenGL, 4, 3)
Expand All @@ -84,6 +91,18 @@ public static AppBuilder BuildAvaloniaApp()
CompositionBackdropCornerRadius = 8f,
})
.UseSkia()
.With(new Ui.Vulkan.VulkanOptions()
{
ApplicationName = "Ryujinx.Graphics.Vulkan",
VulkanVersion = new Version(1, 2),
MaxQueueCount = 2,
PreferDiscreteGpu = true,
UseDebug = !PreviewerDetached ? false : ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value != GraphicsDebugLevel.None,
})
.With(new SkiaOptions()
{
CustomGpuFactory = UseVulkan ? SkiaGpuFactory.CreateVulkanGpu : null
})
.AfterSetup(_ =>
{
AvaloniaLocator.CurrentMutable
Expand Down Expand Up @@ -136,9 +155,6 @@ private static void Initialize(string[] args)
}
}

// Make process DPI aware for proper window sizing on high-res screens.
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();

// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);

Expand All @@ -162,6 +178,18 @@ private static void Initialize(string[] args)

ReloadConfig();

UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;

if (UseVulkan)
{
// With a custom gpu backend, avalonia doesn't enable dpi awareness, so the backend must handle it. This isn't so for the opengl backed,
// as that uses avalonia's gpu backend and it's enabled there.
ForceDpiAware.Windows();
}

WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;

// Logging system information.
PrintSystemInfo();

Expand Down
7 changes: 5 additions & 2 deletions Ryujinx.Ava/Ryujinx.Ava.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />

<PackageReference Include="OpenTK.Core" Version="4.7.2" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="SPB" Version="0.0.4-build17" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public void Dispose()
Dispatcher.UIThread.Post(() =>
{
_hiddenTextBox.Clear();
_parent.GlRenderer.Focus();
_parent.RendererControl.Focus();
_parent = null;
});
Expand Down
71 changes: 71 additions & 0 deletions Ryujinx.Ava/Ui/Backend/BackendSurface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Avalonia;
using System;
using System.Runtime.InteropServices;
using static Ryujinx.Ava.Ui.Backend.Interop;

namespace Ryujinx.Ava.Ui.Backend
{
public abstract class BackendSurface : IDisposable
{
protected IntPtr Display => _display;

private IntPtr _display = IntPtr.Zero;

[DllImport("libX11.so.6")]
public static extern IntPtr XOpenDisplay(IntPtr display);

[DllImport("libX11.so.6")]
public static extern int XCloseDisplay(IntPtr display);

private PixelSize _currentSize;
public IntPtr Handle { get; protected set; }

public bool IsDisposed { get; private set; }

public BackendSurface(IntPtr handle)
{
Handle = handle;

if (OperatingSystem.IsLinux())
{
_display = XOpenDisplay(IntPtr.Zero);
}
}

public PixelSize Size
{
get
{
PixelSize size = new PixelSize();
if (OperatingSystem.IsWindows())
{
GetClientRect(Handle, out var rect);
size = new PixelSize(rect.right, rect.bottom);
}
else if (OperatingSystem.IsLinux())
{
XWindowAttributes attributes = new XWindowAttributes();
XGetWindowAttributes(Display, Handle, ref attributes);

size = new PixelSize(attributes.width, attributes.height);
}

_currentSize = size;

return size;
}
}

public PixelSize CurrentSize => _currentSize;

public virtual void Dispose()
{
IsDisposed = true;

if (_display != IntPtr.Zero)
{
XCloseDisplay(_display);
}
}
}
}
49 changes: 49 additions & 0 deletions Ryujinx.Ava/Ui/Backend/Interop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using FluentAvalonia.Interop;
using System;
using System.Runtime.InteropServices;

namespace Ryujinx.Ava.Ui.Backend
{
public static class Interop
{
[StructLayout(LayoutKind.Sequential)]
public struct XWindowAttributes
{
public int x;
public int y;
public int width;
public int height;
public int border_width;
public int depth;
public IntPtr visual;
public IntPtr root;
public int c_class;
public int bit_gravity;
public int win_gravity;
public int backing_store;
public IntPtr backing_planes;
public IntPtr backing_pixel;
public int save_under;
public IntPtr colormap;
public int map_installed;
public int map_state;
public IntPtr all_event_masks;
public IntPtr your_event_mask;
public IntPtr do_not_propagate_mask;
public int override_direct;
public IntPtr screen;
}

[DllImport("user32.dll")]
public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect);

[DllImport("libX11.so.6")]
public static extern int XCloseDisplay(IntPtr display);

[DllImport("libX11.so.6")]
public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes);

[DllImport("libX11.so.6")]
public static extern IntPtr XOpenDisplay(IntPtr display);
}
}
23 changes: 23 additions & 0 deletions Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Skia;
using Ryujinx.Ava.Ui.Vulkan;
using Ryujinx.Ava.Ui.Backend.Vulkan;

namespace Ryujinx.Ava.Ui.Backend
{
public static class SkiaGpuFactory
{
public static ISkiaGpu CreateVulkanGpu()
{
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>() ?? new SkiaOptions();
var platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
if (platformInterface == null)
{
VulkanPlatformInterface.TryInitialize();
}
var gpu = new VulkanSkiaGpu(skiaOptions.MaxGpuResourceSizeBytes);
AvaloniaLocator.CurrentMutable.Bind<VulkanSkiaGpu>().ToConstant(gpu);
return gpu;
}
}
}
13 changes: 13 additions & 0 deletions Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Silk.NET.Vulkan;

namespace Ryujinx.Ava.Ui.Vulkan
{
public static class ResultExtensions
{
public static void ThrowOnError(this Result result)
{
if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\".");
}
}
}
Loading

0 comments on commit f034eae

Please sign in to comment.