Compare commits

...

8 Commits

Author SHA1 Message Date
Mary
fe9c49949a vulkan: Enforce Vulkan 1.2+ at instance API level and 1.1+ at device level (#4408)
* vulkan: Enforce Vulkan 1.2+ at instance API level and 1.1+ at device level

This ensure we don't end up trying to initialize with anything currently incompatible.

* Address riperiperi's comment
2023-02-13 22:04:55 +00:00
Mary
052b23c83c vulkan: Do not call vkCmdSetViewport when viewportCount is 0 (#4406)
This fix validation error "VUID-vkCmdSetViewport-viewportCount-arraylength".
2023-02-13 20:32:20 +00:00
riperiperi
e4f68592c3 Fix partial updates for textures. (#4401)
I was forcing some types of texture to partially update when investigating performance with games that stream in data, and noticed that partially loading texture data was really broken on both backends.

Fixes Vulkan texture set by getting the correct expected size for the texture. Fixes partial upload on both backends for both Texture 2D Array and Cubemap using the wrong offset and uploading to the first layer/level for a handle. 3D might also be affected.

This might fix textures randomly having incorrect data in games that render to it - jumbled in the case of OpenGL, and outdated/black in the case of Vulkan. This case typically happens in UE4 games.
2023-02-12 10:30:26 +01:00
Logan Stromberg
1dcd44b94f Treat NpadIdType < 0 as invalid. Filter invalid SupportedPlayers inside IHidServer.SetSupportedNpadIdType(). (#4377)
Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
2023-02-10 12:37:20 -03:00
gdkchan
61b1ce252f Allow partially mapped textures with unmapped start (#4394) 2023-02-10 11:47:59 -03:00
gdkchan
5f38086f94 Fix SPIR-V when all inputs/outputs are indexed (#4389) 2023-02-09 04:48:25 +01:00
Isaac Marovitz
7bae440d3a ObjectiveC Helper Class (#4286)
* `NativeMacOS` Helper Class

* Corrections

* Make CFString IDisposable

* Fix `openURL:`

* `dealloc` metal layer

* Remove releases

* Use NSString

* Update Ryujinx.Ui.Common/Helper/NativeMacOS.cs

Co-authored-by: merry <git@mary.rs>

* Programatically select updates in Finder

* Address feedback

* Feedback

* Ptr

* Fix whoopsie

* Ack suggestions

* Update Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* GDK Suggestions

---------

Co-authored-by: merry <git@mary.rs>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-02-09 04:08:15 +01:00
riperiperi
f1943fd0b6 Log shader compile errors with Warning level (#2617)
* Log shader compile errors with Warning level

These are infrequent enough that I think it's worth dumping any errors into the log. They also keep causing graphical glitches, and the only indication that anything went wrong is a debug log that is never enabled.

* Add maximum length for shader log
2023-02-09 03:50:18 +01:00
15 changed files with 313 additions and 217 deletions

View File

@@ -1,127 +0,0 @@
using System;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;
using Avalonia;
namespace Ryujinx.Ava.UI.Helpers
{
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
[SupportedOSPlatform("macos")]
static partial class MetalHelper
{
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
private struct Selector
{
public readonly IntPtr NativePtr;
public unsafe Selector(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
NativePtr = sel_registerName(data);
}
public static implicit operator Selector(string value) => new Selector(value);
}
private static unsafe IntPtr GetClass(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
return objc_getClass(data);
}
private struct NSPoint
{
public double X;
public double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
private struct NSRect
{
public NSPoint Pos;
public NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
{
// Create a new CAMetalLayer.
IntPtr layerClass = GetClass("CAMetalLayer");
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = GetClass("NSView");
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
objc_msgSend(child, "setWantsLayer:", (byte)1);
objc_msgSend(child, "setLayer:", metalLayer);
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
updateBounds = (Rect rect) => {
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
};
nsView = child;
return metalLayer;
}
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
{
// TODO
}
[LibraryImport(LibObjCImport)]
private static unsafe partial IntPtr sel_registerName(byte* data);
[LibraryImport(LibObjCImport)]
private static unsafe partial IntPtr objc_getClass(byte* data);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[LibraryImport(LibObjCImport)]
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
[LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")]
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
}
}

View File

@@ -2,9 +2,9 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Configuration;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;
@@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Renderer
protected IntPtr NsView { get; set; }
protected IntPtr MetalLayer { get; set; }
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<IntPtr> WindowCreated;
@@ -237,8 +238,29 @@ namespace Ryujinx.Ava.UI.Renderer
[SupportedOSPlatform("macos")]
IPlatformHandle CreateMacOS()
{
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
// Create a new CAMetalLayer.
IntPtr layerClass = ObjectiveC.objc_getClass("CAMetalLayer");
IntPtr metalLayer = ObjectiveC.IntPtr_objc_msgSend(layerClass, "alloc");
ObjectiveC.objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = ObjectiveC.objc_getClass("NSView");
IntPtr child = ObjectiveC.IntPtr_objc_msgSend(nsViewClass, "alloc");
ObjectiveC.objc_msgSend(child, "init", new ObjectiveC.NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
ObjectiveC.objc_msgSend(child, "setWantsLayer:", 1);
ObjectiveC.objc_msgSend(child, "setLayer:", metalLayer);
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
_updateBoundsCallback = rect =>
{
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
};
IntPtr nsView = child;
MetalLayer = metalLayer;
NsView = nsView;
return new PlatformHandle(nsView, "NSView");
@@ -260,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
[SupportedOSPlatform("macos")]
void DestroyMacOS()
{
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
// TODO
}
}
}

View File

@@ -474,6 +474,13 @@ namespace Ryujinx.Graphics.Gpu.Image
{
address = memoryManager.Translate(info.GpuAddress);
// If the start address is unmapped, let's try to find a page of memory that is mapped.
if (address == MemoryManager.PteUnmapped)
{
address = memoryManager.TranslateFirstMapped(info.GpuAddress, (ulong)info.CalculateSizeInfo(layerSize).TotalSize);
}
// If address is still invalid, the texture is fully unmapped, so it has no data, just return null.
if (address == MemoryManager.PteUnmapped)
{
return null;

View File

@@ -336,24 +336,23 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_loadNeeded[baseHandle + i])
{
var info = GetHandleInformation(baseHandle + i);
int offsetIndex = info.Index;
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
for (int layer = 0; layer < info.Layers; layer++)
{
for (int level = 0; level < info.Levels; level++)
{
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
int offset = _allOffsets[offsetIndex];
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
int size = endOffset - offset;
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer, info.BaseLevel);
offsetIndex++;
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
}
}
}

View File

@@ -583,6 +583,38 @@ namespace Ryujinx.Graphics.Gpu.Memory
return UnpackPaFromPte(pte) + (va & PageMask);
}
/// <summary>
/// Translates a GPU virtual address to a CPU virtual address on the first mapped page of memory
/// on the specified region.
/// If no page is mapped on the specified region, <see cref="PteUnmapped"/> is returned.
/// </summary>
/// <param name="va">GPU virtual address to be translated</param>
/// <param name="size">Size of the range to be translated</param>
/// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns>
public ulong TranslateFirstMapped(ulong va, ulong size)
{
if (!ValidateAddress(va))
{
return PteUnmapped;
}
ulong endVa = va + size;
ulong pte = GetPte(va);
for (; va < endVa && pte == PteUnmapped; va += PageSize - (va & PageMask))
{
pte = GetPte(va);
}
if (pte == PteUnmapped)
{
return PteUnmapped;
}
return UnpackPaFromPte(pte) + (va & PageMask);
}
/// <summary>
/// Gets the kind of a given memory page.
/// This might indicate the type of resource that can be allocated on the page, and also texture tiling.

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
{
class Program : IProgram
{
private const int MaxShaderLogLength = 2048;
public int Handle { get; private set; }
public bool IsLinked
@@ -115,9 +117,16 @@ namespace Ryujinx.Graphics.OpenGL
if (status == 0)
{
// Use GL.GetProgramInfoLog(Handle), it may be too long to print on the log.
_status = ProgramLinkStatus.Failure;
Logger.Debug?.Print(LogClass.Gpu, "Shader linking failed.");
string log = GL.GetProgramInfoLog(Handle);
if (log.Length > MaxShaderLogLength)
{
log = log.Substring(0, MaxShaderLogLength) + "...";
}
Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{log}");
}
else
{

View File

@@ -397,6 +397,31 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
{
bool iaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing);
if (iaIndexing && !perPatch)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
if (context.Config.Stage == ShaderStage.Geometry)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
}
var spvType = context.TypePointer(StorageClass.Input, attrType);
var spvVar = context.Variable(spvType, StorageClass.Input);
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.InputsArray = spvVar;
}
var inputs = perPatch ? info.InputsPerPatch : info.Inputs;
foreach (int attr in inputs)
@@ -410,60 +435,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (iaIndexing && isUserAttr && !perPatch)
{
if (context.InputsArray == null)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
if (context.Config.Stage == ShaderStage.Geometry)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)context.InputVertices));
}
var spvType = context.TypePointer(StorageClass.Input, attrType);
var spvVar = context.Variable(spvType, StorageClass.Input);
if (context.Config.PassthroughAttributes != 0 && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.InputsArray = spvVar;
}
continue;
}
else
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
{
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
{
if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd)
{
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
}
else
{
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
iq = context.Config.ImapTypes[(attr - AttributeConsts.UserAttributeBase) / 16].GetFirstUsedType();
}
else
{
AttributeInfo attrInfo = AttributeInfo.From(context.Config, attr, isOutAttr: false);
AggregateType elemType = attrInfo.Type & AggregateType.ElementTypeMask;
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
{
iq = PixelImap.Constant;
}
if (attrInfo.IsBuiltin && (elemType == AggregateType.S32 || elemType == AggregateType.U32))
{
iq = PixelImap.Constant;
}
}
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
}
DeclareInputOrOutput(context, attr, perPatch, isOutAttr: false, iq);
}
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info, bool perPatch)
{
bool oaIndexing = context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing);
if (oaIndexing && !perPatch)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
if (context.Config.Stage == ShaderStage.TessellationControl)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(StorageClass.Output, attrType);
var spvVar = context.Variable(spvType, StorageClass.Output);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.OutputsArray = spvVar;
}
var outputs = perPatch ? info.OutputsPerPatch : info.Outputs;
foreach (int attr in outputs)
@@ -477,29 +498,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (oaIndexing && isUserAttr && !perPatch)
{
if (context.OutputsArray == null)
{
var attrType = context.TypeVector(context.TypeFP32(), (LiteralInteger)4);
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), (LiteralInteger)MaxAttributes));
if (context.Config.Stage == ShaderStage.TessellationControl)
{
attrType = context.TypeArray(attrType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
}
var spvType = context.TypePointer(StorageClass.Output, attrType);
var spvVar = context.Variable(spvType, StorageClass.Output);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)0);
context.AddGlobalVariable(spvVar);
context.OutputsArray = spvVar;
}
}
else
{
DeclareOutputAttribute(context, attr, perPatch);
continue;
}
DeclareOutputAttribute(context, attr, perPatch);
}
if (context.Config.Stage == ShaderStage.Vertex)

View File

@@ -650,9 +650,7 @@ namespace Ryujinx.Graphics.Vulkan
_newState.DepthWriteEnable = oldDepthWriteEnable;
_newState.Topology = oldTopology;
DynamicState.Viewports = oldViewports;
DynamicState.ViewportsCount = (int)oldViewportsCount;
DynamicState.SetViewportsDirty();
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
_newState.ViewportsCount = oldViewportsCount;
SignalStateChange();
@@ -1183,6 +1181,8 @@ namespace Ryujinx.Graphics.Vulkan
return Math.Clamp(value, 0f, 1f);
}
DynamicState.ViewportsCount = (uint)count;
for (int i = 0; i < count; i++)
{
var viewport = viewports[i];
@@ -1196,8 +1196,6 @@ namespace Ryujinx.Graphics.Vulkan
Clamp(viewport.DepthFar)));
}
DynamicState.ViewportsCount = count;
float disableTransformF = disableTransform ? 1.0f : 0.0f;
if (SupportBufferUpdater.Data.ViewportInverse.W != disableTransformF || disableTransform)
{

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
private Array4<float> _blendConstants;
public int ViewportsCount;
public uint ViewportsCount;
public Array16<Viewport> Viewports;
private enum DirtyFlags
@@ -88,9 +88,15 @@ namespace Ryujinx.Graphics.Vulkan
_dirty |= DirtyFlags.Viewport;
}
public void SetViewportsDirty()
public void SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
{
_dirty |= DirtyFlags.Viewport;
Viewports = viewports;
ViewportsCount = viewportsCount;
if (ViewportsCount != 0)
{
_dirty |= DirtyFlags.Viewport;
}
}
public void ForceAllDirty()
@@ -155,7 +161,10 @@ namespace Ryujinx.Graphics.Vulkan
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
{
api.CmdSetViewport(commandBuffer, 0, (uint)ViewportsCount, Viewports.AsSpan());
if (ViewportsCount != 0)
{
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
}
}
}
}

View File

@@ -712,7 +712,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int level = 0; level < levels; level++)
{
int mipSize = GetBufferDataLength(Info.GetMipSize(dstLevel + level));
int mipSize = GetBufferDataLength(Info.GetMipSize2D(dstLevel + level) * dstLayers);
int endOffset = offset + mipSize;

View File

@@ -14,6 +14,9 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe static class VulkanInitialization
{
private const uint InvalidIndex = uint.MaxValue;
private static uint MinimalVulkanVersion = Vk.Version11.Value;
private static uint MinimalInstanceVulkanVersion = Vk.Version12.Value;
private static uint MaximumVulkanVersion = Vk.Version12.Value;
private const string AppName = "Ryujinx.Graphics.Vulkan";
private const int QueuesCount = 2;
@@ -99,7 +102,7 @@ namespace Ryujinx.Graphics.Vulkan
ApplicationVersion = 1,
PEngineName = (byte*)appName,
EngineVersion = 1,
ApiVersion = Vk.Version12.Value
ApiVersion = MaximumVulkanVersion
};
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
@@ -224,7 +227,7 @@ namespace Ryujinx.Graphics.Vulkan
ApplicationVersion = 1,
PEngineName = (byte*)appName,
EngineVersion = 1,
ApiVersion = Vk.Version12.Value
ApiVersion = MaximumVulkanVersion
};
var instanceCreateInfo = new InstanceCreateInfo
@@ -239,6 +242,27 @@ namespace Ryujinx.Graphics.Vulkan
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
// We ensure that vkEnumerateInstanceVersion is present (added in 1.1).
// If the instance doesn't support it, no device is going to be 1.1 compatible.
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
{
api.DestroyInstance(instance, null);
return Array.Empty<DeviceInfo>();
}
// We currently assume that the instance is compatible with Vulkan 1.2
// TODO: Remove this once we relax our initialization codepaths.
uint instanceApiVerison = 0;
api.EnumerateInstanceVersion(ref instanceApiVerison).ThrowOnError();
if (instanceApiVerison < MinimalInstanceVulkanVersion)
{
api.DestroyInstance(instance, null);
return Array.Empty<DeviceInfo>();
}
Marshal.FreeHGlobal(appName);
uint physicalDeviceCount;
@@ -259,6 +283,11 @@ namespace Ryujinx.Graphics.Vulkan
var physicalDevice = physicalDevices[i];
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
if (properties.ApiVersion < MinimalVulkanVersion)
{
continue;
}
devices[i] = new DeviceInfo(
StringFromIdPair(properties.VendorID, properties.DeviceID),
VendorUtils.GetNameFromId(properties.VendorID),

View File

@@ -38,7 +38,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
public static bool IsValidNpadIdType(NpadIdType npadIdType)
{
return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown;
return (npadIdType >= NpadIdType.Player1 && npadIdType <= NpadIdType.Player8) ||
npadIdType == NpadIdType.Handheld ||
npadIdType == NpadIdType.Unknown;
}
}
}

View File

@@ -722,7 +722,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
for (int i = 0; i < supportedPlayerIds.Length; ++i)
{
if (supportedPlayerIds[i] >= 0)
if (HidUtils.IsValidNpadIdType(supportedPlayerIds[i]))
{
context.Device.Hid.Npads.SetSupportedPlayer(HidUtils.GetIndexFromNpadIdType(supportedPlayerIds[i]));
}
@@ -1101,7 +1101,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{
if (npadIdType >= (NpadIdType.Player8 + 1) && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown)
if (!HidUtils.IsValidNpadIdType(npadIdType))
{
return ResultCode.InvalidNpadIdType;
}

View File

@@ -0,0 +1,97 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
namespace Ryujinx.Ui.Common.Helper
{
[SupportedOSPlatform("macos")]
public static partial class ObjectiveC
{
private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
private static unsafe partial IntPtr sel_getUid(string name);
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr objc_getClass(string name);
[LibraryImport(ObjCRuntime)]
public static partial void objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime)]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[LibraryImport(ObjCRuntime)]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[LibraryImport(ObjCRuntime)]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[LibraryImport(ObjCRuntime)]
public static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
public struct Selector
{
public readonly IntPtr SelPtr;
public unsafe Selector(string name)
{
SelPtr = sel_getUid(name);
}
public static implicit operator Selector(string value) => new(value);
}
public struct NSString
{
public readonly IntPtr StrPtr;
public NSString(string aString)
{
IntPtr nsString = objc_getClass("NSString");
StrPtr = IntPtr_objc_msgSend(nsString, "stringWithUTF8String:", aString);
}
public static implicit operator IntPtr(NSString nsString) => nsString.StrPtr;
}
public readonly struct NSPoint
{
public readonly double X;
public readonly double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
public readonly struct NSRect
{
public readonly NSPoint Pos;
public readonly NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
}
}

View File

@@ -55,7 +55,17 @@ namespace Ryujinx.Ui.Common.Helper
}
else if (OperatingSystem.IsMacOS())
{
Process.Start("open", $"-R \"{path}\"");
ObjectiveC.NSString nsStringPath = new(path);
IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "fileURLWithPath:", nsStringPath);
IntPtr nsArray = ObjectiveC.objc_getClass("NSArray");
IntPtr urlArray = ObjectiveC.IntPtr_objc_msgSend(nsArray, "arrayWithObject:", urlPtr);
IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
ObjectiveC.objc_msgSend(sharedWorkspace, "activateFileViewerSelectingURLs:", urlArray);
}
else if (OperatingSystem.IsLinux())
{
@@ -84,7 +94,14 @@ namespace Ryujinx.Ui.Common.Helper
}
else if (OperatingSystem.IsMacOS())
{
Process.Start("open", url);
ObjectiveC.NSString nsStringPath = new(url);
IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "URLWithString:", nsStringPath);
IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
ObjectiveC.bool_objc_msgSend(sharedWorkspace, "openURL:", urlPtr);
}
else
{