Compare commits

..

5 Commits

Author SHA1 Message Date
riperiperi
9ba73ffbe5 Prefetch capabilities before spawning translation threads. (#3338)
* Prefetch capabilities before spawning translation threads.

The Backend Multithreading only expects one thread to submit commands at a time. When compiling shaders, the translator may request the host GPU capabilities from the backend. It's possible for a bunch of translators to do this at the same time.

There's a caching mechanism in place so that the capabilities are only fetched once. By triggering this before spawning the thread, the async translation threads no longer try to queue onto the backend queue all at the same time.

The Capabilities do need to be checked from the GPU thread, due to OpenGL needing a context to check them, so it's not possible to call the underlying backend directly.

* Initialize the capabilities when setting the GPU thread + missing call in headless

* Remove private variables
2022-05-14 11:58:33 -03:00
riperiperi
43b4b34376 Implement Viewport Transform Disable (#3328)
* Initial implementation (no specialization)

* Use specialization

* Fix render scale, increase code gen version

* Revert accidental change

* Address Feedback
2022-05-12 10:47:13 -03:00
Ac_K
92ca1cb0cb hid: Various fixes and cleanup (#3326)
* hid: Various fix and cleanup

* Add IsValidNpadIdType

* remove ()
2022-05-08 00:28:54 +02:00
riperiperi
50d7ecf76d Add alternative "GL" enum values for StencilOp (#3321)
This PR adds the alternative enum values for StencilOp. Similar to the other enums, I added these with the same names but with Gl added to the end. These are used by homebrew using Nouveau, though they might be used by games with the official Vulkan driver.

39d90be897/rnndb/graph/nv_3ddefs.xml (L77)

Fixes some broken graphics in Citra, such as missing shadows in Mario Kart 7. Likely fixes other homebrew.
2022-05-05 21:16:58 +02:00
gdkchan
42a2a80b87 Enable JIT service LLE (#2959)
* Enable JIT service LLE

* Force disable PPTC when using the JIT service

PPTC does not support multiple guest processes

* Fix build

* Make SM service registration per emulation context rather than global

* Address PR feedback
2022-05-05 15:23:30 -03:00
52 changed files with 597 additions and 195 deletions

View File

@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.GAL
void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs); void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs);
void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers); void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers);
void SetViewports(int first, ReadOnlySpan<Viewport> viewports); void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform);
void TextureBarrier(); void TextureBarrier();
void TextureBarrierTiled(); void TextureBarrierTiled();

View File

@@ -9,17 +9,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public CommandType CommandType => CommandType.SetViewports; public CommandType CommandType => CommandType.SetViewports;
private int _first; private int _first;
private SpanRef<Viewport> _viewports; private SpanRef<Viewport> _viewports;
private bool _disableTransform;
public void Set(int first, SpanRef<Viewport> viewports) public void Set(int first, SpanRef<Viewport> viewports, bool disableTransform)
{ {
_first = first; _first = first;
_viewports = viewports; _viewports = viewports;
_disableTransform = disableTransform;
} }
public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer) public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded); ReadOnlySpan<Viewport> viewports = command._viewports.Get(threaded);
renderer.Pipeline.SetViewports(command._first, viewports); renderer.Pipeline.SetViewports(command._first, viewports, command._disableTransform);
command._viewports.Dispose(threaded); command._viewports.Dispose(threaded);
} }
} }

View File

@@ -304,9 +304,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand(); _renderer.QueueCommand();
} }
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports) public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{ {
_renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports)); _renderer.New<SetViewportsCommand>().Set(first, _renderer.CopySpan(viewports), disableTransform);
_renderer.QueueCommand(); _renderer.QueueCommand();
} }

View File

@@ -9,6 +9,15 @@ namespace Ryujinx.Graphics.GAL
DecrementAndClamp, DecrementAndClamp,
Invert, Invert,
IncrementAndWrap, IncrementAndWrap,
DecrementAndWrap DecrementAndWrap,
ZeroGl = 0x0,
InvertGl = 0x150a,
KeepGl = 0x1e00,
ReplaceGl = 0x1e01,
IncrementAndClampGl = 0x1e02,
DecrementAndClampGl = 0x1e03,
IncrementAndWrapGl = 0x8507,
DecrementAndWrapGl = 0x8508
} }
} }

View File

@@ -72,6 +72,13 @@ namespace Ryujinx.Graphics.GAL
UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count); UpdateGenericField(SupportBuffer.FragmentIsBgraOffset, data, Data.FragmentIsBgra.ToSpan(), offset, count);
} }
public void UpdateViewportInverse(Vector4<float> data)
{
Data.ViewportInverse = data;
MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize);
}
public void Commit() public void Commit()
{ {
if (_startOffset != -1) if (_startOffset != -1)

View File

@@ -113,7 +113,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.DepthMode), nameof(ThreedClassState.DepthMode),
nameof(ThreedClassState.ViewportTransform), nameof(ThreedClassState.ViewportTransform),
nameof(ThreedClassState.ViewportExtents), nameof(ThreedClassState.ViewportExtents),
nameof(ThreedClassState.YControl)), nameof(ThreedClassState.YControl),
nameof(ThreedClassState.ViewportTransformEnable)),
new StateUpdateCallbackEntry(UpdatePolygonMode, new StateUpdateCallbackEntry(UpdatePolygonMode,
nameof(ThreedClassState.PolygonModeFront), nameof(ThreedClassState.PolygonModeFront),
@@ -200,7 +201,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// of the shader for the new state. // of the shader for the new state.
if (_shaderSpecState != null) if (_shaderSpecState != null)
{ {
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState())) if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState(), GetGraphicsState()))
{ {
ForceShaderUpdate(); ForceShaderUpdate();
} }
@@ -568,6 +569,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
var yControl = _state.State.YControl; var yControl = _state.State.YControl;
var face = _state.State.FaceState; var face = _state.State.FaceState;
bool disableTransform = _state.State.ViewportTransformEnable == 0;
UpdateFrontFace(yControl, face.FrontFace); UpdateFrontFace(yControl, face.FrontFace);
UpdateDepthMode(); UpdateDepthMode();
@@ -577,6 +580,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
for (int index = 0; index < Constants.TotalViewports; index++) for (int index = 0; index < Constants.TotalViewports; index++)
{ {
if (disableTransform)
{
ref var scissor = ref _state.State.ScreenScissorState;
float rScale = _channel.TextureManager.RenderTargetScale;
var scissorRect = new RectangleF(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale);
viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1);
continue;
}
ref var transform = ref _state.State.ViewportTransform[index]; ref var transform = ref _state.State.ViewportTransform[index];
ref var extents = ref _state.State.ViewportExtents[index]; ref var extents = ref _state.State.ViewportExtents[index];
@@ -628,7 +642,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
} }
_context.Renderer.Pipeline.SetViewports(0, viewports); _context.Renderer.Pipeline.SetViewports(0, viewports, disableTransform);
} }
/// <summary> /// <summary>
@@ -1194,7 +1208,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
return new GpuChannelGraphicsState( return new GpuChannelGraphicsState(
_state.State.EarlyZForce, _state.State.EarlyZForce,
_drawState.Topology, _drawState.Topology,
_state.State.TessMode); _state.State.TessMode,
_state.State.ViewportTransformEnable == 0);
} }
/// <summary> /// <summary>

View File

@@ -82,27 +82,13 @@ namespace Ryujinx.Graphics.Gpu
/// <summary> /// <summary>
/// Host hardware capabilities. /// Host hardware capabilities.
/// </summary> /// </summary>
internal ref Capabilities Capabilities internal Capabilities Capabilities { get; private set; }
{
get
{
if (!_capsLoaded)
{
_caps = Renderer.GetCapabilities();
_capsLoaded = true;
}
return ref _caps;
}
}
/// <summary> /// <summary>
/// Event for signalling shader cache loading progress. /// Event for signalling shader cache loading progress.
/// </summary> /// </summary>
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged; public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
private bool _capsLoaded;
private Capabilities _caps;
private Thread _gpuThread; private Thread _gpuThread;
/// <summary> /// <summary>
@@ -254,6 +240,8 @@ namespace Ryujinx.Graphics.Gpu
public void SetGpuThread() public void SetGpuThread()
{ {
_gpuThread = Thread.CurrentThread; _gpuThread = Thread.CurrentThread;
Capabilities = Renderer.GetCapabilities();
} }
/// <summary> /// <summary>

View File

@@ -166,7 +166,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState( GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce), accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
topology, topology,
tessMode); tessMode,
false);
TransformFeedbackDescriptor[] tfdNew = null; TransformFeedbackDescriptor[] tfdNew = null;

View File

@@ -185,6 +185,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.EarlyZForce; return _oldSpecState.GraphicsState.EarlyZForce;
} }
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/> /// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot) public void RegisterTexture(int handle, int cbufSlot)
{ {

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 1; private const ushort FileFormatVersionMinor = 1;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 0; private const uint CodeGenVersion = 1;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@@ -217,6 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.EarlyZForce; return _state.GraphicsState.EarlyZForce;
} }
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/> /// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot) public void RegisterTexture(int handle, int cbufSlot)
{ {

View File

@@ -25,17 +25,24 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
public readonly TessMode TessellationMode; public readonly TessMode TessellationMode;
/// <summary>
/// Indicates whenever the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary> /// <summary>
/// Creates a new GPU graphics state. /// Creates a new GPU graphics state.
/// </summary> /// </summary>
/// <param name="earlyZForce">Early Z force enable</param> /// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param> /// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param> /// <param name="tessellationMode">Tessellation mode</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode) /// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
{ {
EarlyZForce = earlyZForce; EarlyZForce = earlyZForce;
Topology = topology; Topology = topology;
TessellationMode = tessellationMode; TessellationMode = tessellationMode;
ViewportTransformDisable = viewportTransformDisable;
} }
} }
} }

View File

@@ -249,12 +249,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuChannelGraphicsState graphicsState, GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses) ShaderAddresses addresses)
{ {
if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, gpShaders, addresses)) if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, poolState, graphicsState, gpShaders, addresses))
{ {
return gpShaders; return gpShaders;
} }
if (_graphicsShaderCache.TryFind(channel, poolState, addresses, out gpShaders, out var cachedGuestCode)) if (_graphicsShaderCache.TryFind(channel, poolState, graphicsState, addresses, out gpShaders, out var cachedGuestCode))
{ {
_gpPrograms[addresses] = gpShaders; _gpPrograms[addresses] = gpShaders;
return gpShaders; return gpShaders;
@@ -429,12 +429,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel using the shader</param> /// <param name="channel">GPU channel using the shader</param>
/// <param name="poolState">GPU channel state to verify shader compatibility</param> /// <param name="poolState">GPU channel state to verify shader compatibility</param>
/// <param name="graphicsState">GPU channel graphics state to verify shader compatibility</param>
/// <param name="gpShaders">Cached graphics shaders</param> /// <param name="gpShaders">Cached graphics shaders</param>
/// <param name="addresses">GPU virtual addresses of all enabled shader stages</param> /// <param name="addresses">GPU virtual addresses of all enabled shader stages</param>
/// <returns>True if the code is different, false otherwise</returns> /// <returns>True if the code is different, false otherwise</returns>
private static bool IsShaderEqual( private static bool IsShaderEqual(
GpuChannel channel, GpuChannel channel,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
CachedShaderProgram gpShaders, CachedShaderProgram gpShaders,
ShaderAddresses addresses) ShaderAddresses addresses)
{ {
@@ -452,7 +454,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
} }
} }
return gpShaders.SpecializationState.MatchesGraphics(channel, poolState); return gpShaders.SpecializationState.MatchesGraphics(channel, poolState, graphicsState);
} }
/// <summary> /// <summary>

View File

@@ -208,6 +208,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </remarks> /// </remarks>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="addresses">Guest addresses of the shaders to find</param> /// <param name="addresses">Guest addresses of the shaders to find</param>
/// <param name="program">Cached host program for the given state, if found</param> /// <param name="program">Cached host program for the given state, if found</param>
/// <param name="guestCode">Cached guest code, if any found</param> /// <param name="guestCode">Cached guest code, if any found</param>
@@ -215,6 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool TryFind( public bool TryFind(
GpuChannel channel, GpuChannel channel,
GpuChannelPoolState poolState, GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses, ShaderAddresses addresses,
out CachedShaderProgram program, out CachedShaderProgram program,
out CachedGraphicsGuestCode guestCode) out CachedGraphicsGuestCode guestCode)
@@ -234,7 +236,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList)) if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
{ {
return specList.TryFindForGraphics(channel, poolState, out program); return specList.TryFindForGraphics(channel, poolState, graphicsState, out program);
} }
return false; return false;

View File

@@ -24,13 +24,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <param name="program">Cached program, if found</param> /// <param name="program">Cached program, if found</param>
/// <returns>True if a compatible program is found, false otherwise</returns> /// <returns>True if a compatible program is found, false otherwise</returns>
public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program) public bool TryFindForGraphics(
GpuChannel channel,
GpuChannelPoolState poolState,
GpuChannelGraphicsState graphicsState,
out CachedShaderProgram program)
{ {
foreach (var entry in _entries) foreach (var entry in _entries)
{ {
if (entry.SpecializationState.MatchesGraphics(channel, poolState)) if (entry.SpecializationState.MatchesGraphics(channel, poolState, graphicsState))
{ {
program = entry; program = entry;
return true; return true;

View File

@@ -395,9 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary> /// </summary>
/// <param name="channel">GPU channel</param> /// <param name="channel">GPU channel</param>
/// <param name="poolState">Texture pool state</param> /// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">Graphics state</param>
/// <returns>True if the state matches, false otherwise</returns> /// <returns>True if the state matches, false otherwise</returns>
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState) public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState)
{ {
if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable)
{
return false;
}
return Matches(channel, poolState, isCompute: false); return Matches(channel, poolState, isCompute: false);
} }

View File

@@ -379,20 +379,28 @@ namespace Ryujinx.Graphics.OpenGL
switch (op) switch (op)
{ {
case GAL.StencilOp.Keep: case GAL.StencilOp.Keep:
case GAL.StencilOp.KeepGl:
return OpenTK.Graphics.OpenGL.StencilOp.Keep; return OpenTK.Graphics.OpenGL.StencilOp.Keep;
case GAL.StencilOp.Zero: case GAL.StencilOp.Zero:
case GAL.StencilOp.ZeroGl:
return OpenTK.Graphics.OpenGL.StencilOp.Zero; return OpenTK.Graphics.OpenGL.StencilOp.Zero;
case GAL.StencilOp.Replace: case GAL.StencilOp.Replace:
case GAL.StencilOp.ReplaceGl:
return OpenTK.Graphics.OpenGL.StencilOp.Replace; return OpenTK.Graphics.OpenGL.StencilOp.Replace;
case GAL.StencilOp.IncrementAndClamp: case GAL.StencilOp.IncrementAndClamp:
case GAL.StencilOp.IncrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Incr; return OpenTK.Graphics.OpenGL.StencilOp.Incr;
case GAL.StencilOp.DecrementAndClamp: case GAL.StencilOp.DecrementAndClamp:
case GAL.StencilOp.DecrementAndClampGl:
return OpenTK.Graphics.OpenGL.StencilOp.Decr; return OpenTK.Graphics.OpenGL.StencilOp.Decr;
case GAL.StencilOp.Invert: case GAL.StencilOp.Invert:
case GAL.StencilOp.InvertGl:
return OpenTK.Graphics.OpenGL.StencilOp.Invert; return OpenTK.Graphics.OpenGL.StencilOp.Invert;
case GAL.StencilOp.IncrementAndWrap: case GAL.StencilOp.IncrementAndWrap:
case GAL.StencilOp.IncrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap; return OpenTK.Graphics.OpenGL.StencilOp.IncrWrap;
case GAL.StencilOp.DecrementAndWrap: case GAL.StencilOp.DecrementAndWrap:
case GAL.StencilOp.DecrementAndWrapGl:
return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap; return OpenTK.Graphics.OpenGL.StencilOp.DecrWrap;
} }

View File

@@ -1266,7 +1266,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexArray.SetVertexBuffers(vertexBuffers); _vertexArray.SetVertexBuffers(vertexBuffers);
} }
public void SetViewports(int first, ReadOnlySpan<Viewport> viewports) public void SetViewports(int first, ReadOnlySpan<Viewport> viewports, bool disableTransform)
{ {
Array.Resize(ref _viewportArray, viewports.Length * 4); Array.Resize(ref _viewportArray, viewports.Length * 4);
Array.Resize(ref _depthRangeArray, viewports.Length * 2); Array.Resize(ref _depthRangeArray, viewports.Length * 2);
@@ -1305,6 +1305,19 @@ namespace Ryujinx.Graphics.OpenGL
GL.ViewportArray(first, viewports.Length, viewportArray); GL.ViewportArray(first, viewports.Length, viewportArray);
GL.DepthRangeArray(first, viewports.Length, depthRangeArray); GL.DepthRangeArray(first, viewports.Length, depthRangeArray);
float disableTransformF = disableTransform ? 1.0f : 0.0f;
if (_supportBuffer.Data.ViewportInverse.W != disableTransformF || disableTransform)
{
float scale = _renderScale[0].X;
_supportBuffer.UpdateViewportInverse(new Vector4<float>
{
X = scale * 2f / viewports[first].Region.Width,
Y = scale * 2f / viewports[first].Region.Height,
Z = 1,
W = disableTransformF
});
}
} }
public void TextureBarrier() public void TextureBarrier()

View File

@@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine(); context.AppendLine();
} }
} }
else if (isFragment) else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
{ {
DeclareSupportUniformBlock(context, context.Config.Stage, 0); DeclareSupportUniformBlock(context, context.Config.Stage, 0);
} }
@@ -615,8 +615,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements) private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{ {
bool isFragment = stage == ShaderStage.Fragment; bool needsSupportBlock = stage == ShaderStage.Fragment ||
if (!isFragment && scaleElements == 0) (context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)
{ {
return; return;
} }
@@ -630,6 +632,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
case ShaderStage.Vertex: case ShaderStage.Vertex:
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};"); context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];"); context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};"); context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
break; break;
case ShaderStage.Compute: case ShaderStage.Compute:

View File

@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public const string SupportBlockName = "support_block"; public const string SupportBlockName = "support_block";
public const string SupportBlockAlphaTestName = "s_alpha_test"; public const string SupportBlockAlphaTestName = "s_alpha_test";
public const string SupportBlockIsBgraName = "s_is_bgra"; public const string SupportBlockIsBgraName = "s_is_bgra";
public const string SupportBlockViewportInverse = "s_viewport_inverse";
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count"; public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
public const string SupportBlockRenderScaleName = "s_render_scale"; public const string SupportBlockRenderScaleName = "s_render_scale";

View File

@@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) }, { AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) }, { AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) }, { AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", VariableType.Bool) },
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) } { AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", VariableType.Bool) },
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", VariableType.F32) },
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", VariableType.F32) }
}; };
private Dictionary<AstOperand, string> _locals; private Dictionary<AstOperand, string> _locals;

View File

@@ -329,6 +329,15 @@ namespace Ryujinx.Graphics.Shader
return false; return false;
} }
/// <summary>
/// Queries if host state disables the viewport transform.
/// </summary>
/// <returns>True if the viewport transform is disabled</returns>
bool QueryViewportTransformDisable()
{
return false;
}
/// <summary> /// <summary>
/// Registers a texture used by the shader. /// Registers a texture used by the shader.
/// </summary> /// </summary>

View File

@@ -206,7 +206,33 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (emit) if (emit)
{ {
context.EmitVertex(); if (context.Config.LastInVertexPipeline)
{
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
context.EmitVertex();
// Restore output position value before transformation.
if (tempXLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionX), tempXLocal);
}
if (tempYLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionY), tempYLocal);
}
if (tempZLocal != null)
{
context.Copy(Attribute(AttributeConsts.PositionZ), tempZLocal);
}
}
else
{
context.EmitVertex();
}
} }
if (cut) if (cut)

View File

@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
public static int FragmentAlphaTestOffset; public static int FragmentAlphaTestOffset;
public static int FragmentIsBgraOffset; public static int FragmentIsBgraOffset;
public static int ViewportInverseOffset;
public static int FragmentRenderScaleCountOffset; public static int FragmentRenderScaleCountOffset;
public static int GraphicsRenderScaleOffset; public static int GraphicsRenderScaleOffset;
public static int ComputeRenderScaleOffset; public static int ComputeRenderScaleOffset;
@@ -40,6 +41,7 @@ namespace Ryujinx.Graphics.Shader
FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest); FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest);
FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra); FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra);
ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse);
FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount); FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount);
GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale); GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale);
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize; ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
@@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Shader
public Vector4<int> FragmentAlphaTest; public Vector4<int> FragmentAlphaTest;
public Array8<Vector4<int>> FragmentIsBgra; public Array8<Vector4<int>> FragmentIsBgra;
public Vector4<float> ViewportInverse;
public Vector4<int> FragmentRenderScaleCount; public Vector4<int> FragmentRenderScaleCount;
// Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs. // Render scale max count: 1 + 32 + 8. First scale is fragment output scale, others are textures/image inputs.

View File

@@ -67,6 +67,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int FragmentOutputIsBgraBase = 0x1000100; public const int FragmentOutputIsBgraBase = 0x1000100;
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4; public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
public const int SupportBlockViewInverseX = 0x1000200;
public const int SupportBlockViewInverseY = 0x1000204;
public const int ThreadIdX = 0x2000000; public const int ThreadIdX = 0x2000000;
public const int ThreadIdY = 0x2000004; public const int ThreadIdY = 0x2000004;
public const int ThreadIdZ = 0x2000008; public const int ThreadIdZ = 0x2000008;

View File

@@ -154,9 +154,56 @@ namespace Ryujinx.Graphics.Shader.Translation
return label; return label;
} }
public void PrepareForVertexReturn()
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
Operand x = Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask);
Operand y = Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask);
Operand xScale = Attribute(AttributeConsts.SupportBlockViewInverseX);
Operand yScale = Attribute(AttributeConsts.SupportBlockViewInverseY);
Operand negativeOne = ConstF(-1.0f);
this.Copy(Attribute(AttributeConsts.PositionX), this.FPFusedMultiplyAdd(x, xScale, negativeOne));
this.Copy(Attribute(AttributeConsts.PositionY), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
}
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
{
oldXLocal = Local();
this.Copy(oldXLocal, Attribute(AttributeConsts.PositionX | AttributeConsts.LoadOutputMask));
oldYLocal = Local();
this.Copy(oldYLocal, Attribute(AttributeConsts.PositionY | AttributeConsts.LoadOutputMask));
}
else
{
oldXLocal = null;
oldYLocal = null;
}
// Will be used by Vulkan backend for depth mode emulation.
oldZLocal = null;
PrepareForVertexReturn();
}
public void PrepareForReturn() public void PrepareForReturn()
{ {
if (!IsNonMain && Config.Stage == ShaderStage.Fragment) if (IsNonMain)
{
return;
}
if (Config.LastInVertexPipeline &&
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
{
PrepareForVertexReturn();
}
else if (Config.Stage == ShaderStage.Fragment)
{ {
if (Config.OmapDepth) if (Config.OmapDepth)
{ {

View File

@@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderStage Stage { get; } public ShaderStage Stage { get; }
public bool GpPassthrough { get; } public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; private set; }
public int ThreadsPerInputPrimitive { get; } public int ThreadsPerInputPrimitive { get; }
@@ -135,6 +136,7 @@ namespace Ryujinx.Graphics.Shader.Translation
OmapSampleMask = header.OmapSampleMask; OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth; OmapDepth = header.OmapDepth;
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled(); TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
} }
public int GetDepthRegister() public int GetDepthRegister()
@@ -274,6 +276,11 @@ namespace Ryujinx.Graphics.Shader.Translation
NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents; NextInputAttributesPerPatchComponents = config.ThisInputAttributesPerPatchComponents;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr); NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch); MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
if (config.Stage != ShaderStage.Fragment)
{
LastInVertexPipeline = false;
}
} }
public void MergeOutputUserAttributes(int mask, int maskPerPatch) public void MergeOutputUserAttributes(int mask, int maskPerPatch)

View File

@@ -24,6 +24,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using static LibHac.Fs.ApplicationSaveDataManagement; using static LibHac.Fs.ApplicationSaveDataManagement;
using static Ryujinx.HLE.HOS.ModLoader; using static Ryujinx.HLE.HOS.ModLoader;
@@ -308,6 +309,94 @@ namespace Ryujinx.HLE.HOS
LoadNca(nca, null, null); LoadNca(nca, null, null);
} }
public void LoadServiceNca(string ncaFile)
{
// Disable PPTC here as it does not support multiple processes running.
// TODO: This should be eventually removed and it should stop using global state and
// instead manage the cache per process.
Ptc.Close();
PtcProfiler.Stop();
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
if (mainNca.Header.ContentType != NcaContentType.Program)
{
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
return;
}
IFileSystem codeFs = null;
if (mainNca.CanOpenSection(NcaSectionType.Code))
{
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
}
if (codeFs == null)
{
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
return;
}
using var npdmFile = new UniqueRef<IFile>();
Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
MetaLoader metaData;
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
var npdmBuffer = new byte[fileSize];
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
metaData = new MetaLoader();
metaData.Load(npdmBuffer).ThrowIfFailure();
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
for (int i = 0; i < nsos.Length; i++)
{
string name = ExeFsPrefixes[i];
if (!codeFs.FileExists($"/{name}"))
{
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
using var nsoFile = new UniqueRef<IFile>();
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
}
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0');
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
}
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
{ {
if (mainNca.Header.ContentType != NcaContentType.Program) if (mainNca.Header.ContentType != NcaContentType.Program)
@@ -508,7 +597,7 @@ namespace Ryujinx.HLE.HOS
{ {
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{ {
metaData = null; //TODO: Check if we should retain old npdm metaData = null; // TODO: Check if we should retain old npdm.
} }
metaData ??= ReadNpdm(codeFs); metaData ??= ReadNpdm(codeFs);
@@ -521,7 +610,7 @@ namespace Ryujinx.HLE.HOS
if (!codeFs.FileExists($"/{name}")) if (!codeFs.FileExists($"/{name}"))
{ {
continue; // file doesn't exist, skip continue; // File doesn't exist, skip.
} }
Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
@@ -533,13 +622,13 @@ namespace Ryujinx.HLE.HOS
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
} }
// ExeFs file replacements // ExeFs file replacements.
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
// collect the nsos, ignoring ones that aren't used // Collect the nsos, ignoring ones that aren't used.
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
// take the npdm from mods if present // Take the npdm from mods if present.
if (modLoadResult.Npdm != null) if (modLoadResult.Npdm != null)
{ {
metaData = modLoadResult.Npdm; metaData = modLoadResult.Npdm;
@@ -598,7 +687,7 @@ namespace Ryujinx.HLE.HOS
executable = obj; executable = obj;
// homebrew NRO can actually have some data after the actual NRO // Homebrew NRO can actually have some data after the actual NRO.
if (input.Length > obj.FileSize) if (input.Length > obj.FileSize)
{ {
input.Position = obj.FileSize; input.Position = obj.FileSize;
@@ -677,7 +766,7 @@ namespace Ryujinx.HLE.HOS
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0; TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
// Explicitly null titleid to disable the shader cache // Explicitly null titleid to disable the shader cache.
Graphics.Gpu.GraphicsConfig.TitleId = null; Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set(); _device.Gpu.HostInitalized.Set();

View File

@@ -72,6 +72,8 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; } internal List<NfpDevice> NfpDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; } internal ServerBase SmServer { get; private set; }
internal ServerBase BsdServer { get; private set; } internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; } internal ServerBase AudRenServer { get; private set; }
@@ -291,7 +293,8 @@ namespace Ryujinx.HLE.HOS
private void InitializeServices() private void InitializeServices()
{ {
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext)); SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization, // Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe. // only then doing connections to SM is safe.
@@ -468,7 +471,7 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose(); AudioRendererManager.Dispose();
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
KernelContext.Dispose(); KernelContext.Dispose();
} }
} }

View File

@@ -2,9 +2,12 @@ using LibHac;
using LibHac.Account; using LibHac.Account;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -15,6 +18,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using System; using System;
using System.Numerics; using System.Numerics;
using System.Threading;
using static LibHac.Fs.ApplicationSaveDataManagement; using static LibHac.Fs.ApplicationSaveDataManagement;
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
@@ -37,6 +41,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private int _notificationStorageChannelEventHandle; private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle; private int _healthWarningDisappearedSystemEventHandle;
private bool _gamePlayRecordingState;
private int _jitLoaded;
private HorizonClient _horizon; private HorizonClient _horizon;
public IApplicationFunctions(Horizon system) public IApplicationFunctions(Horizon system)
@@ -342,6 +350,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(65)] // 3.0.0+
// IsGamePlayRecordingSupported() -> u8
public ResultCode IsGamePlayRecordingSupported(ServiceCtx context)
{
context.ResponseData.Write(_gamePlayRecordingState);
return ResultCode.Success;
}
[CommandHipc(66)] // 3.0.0+ [CommandHipc(66)] // 3.0.0+
// InitializeGamePlayRecording(u64, handle<copy>) // InitializeGamePlayRecording(u64, handle<copy>)
public ResultCode InitializeGamePlayRecording(ServiceCtx context) public ResultCode InitializeGamePlayRecording(ServiceCtx context)
@@ -355,9 +372,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
// SetGamePlayRecordingState(u32) // SetGamePlayRecordingState(u32)
public ResultCode SetGamePlayRecordingState(ServiceCtx context) public ResultCode SetGamePlayRecordingState(ServiceCtx context)
{ {
int state = context.RequestData.ReadInt32(); _gamePlayRecordingState = context.RequestData.ReadInt32() != 0;
Logger.Stub?.PrintStub(LogClass.ServiceAm, new { state }); Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState });
return ResultCode.Success; return ResultCode.Success;
} }
@@ -631,5 +648,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(1001)] // 10.0.0+
// PrepareForJit()
public ResultCode PrepareForJit(ServiceCtx context)
{
if (Interlocked.Exchange(ref _jitLoaded, 1) == 0)
{
string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program);
string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath);
if (string.IsNullOrWhiteSpace(filePath))
{
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
}
context.Device.Application.LoadServiceNca(filePath);
// FIXME: Most likely not how this should be done?
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
{
context.Device.System.SmRegistry.WaitForServiceRegistration();
}
}
return ResultCode.Success;
}
} }
} }

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private ControllerType[] _configuredTypes; private ControllerType[] _configuredTypes;
private KEvent[] _styleSetUpdateEvents; private KEvent[] _styleSetUpdateEvents;
private bool[] _supportedPlayers; private bool[] _supportedPlayers;
private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue private static VibrationValue _neutralVibrationValue = new VibrationValue
{ {
AmplitudeLow = 0f, AmplitudeLow = 0f,
FrequencyLow = 160f, FrequencyLow = 160f,
@@ -33,8 +33,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
internal ControllerType SupportedStyleSets { get; set; } internal ControllerType SupportedStyleSets { get; set; }
public Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>>(); public Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>();
public Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (HidVibrationValue, HidVibrationValue)>(); public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>();
public NpadDevices(Switch device, bool active = true) : base(device, active) public NpadDevices(Switch device, bool active = true) : base(device, active)
{ {
@@ -588,21 +588,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState); WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
} }
public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, HidVibrationValue> dualVibrationValues) public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues)
{ {
if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue)) if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue))
{ {
if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue)) if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue))
{ {
leftVibrationValue = _neutralVibrationValue; leftVibrationValue = _neutralVibrationValue;
} }
if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue)) if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue))
{ {
rightVibrationValue = _neutralVibrationValue; rightVibrationValue = _neutralVibrationValue;
} }
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
{ {
currentQueue.Enqueue((leftVibrationValue, rightVibrationValue)); currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
@@ -611,9 +611,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
} }
public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position) public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
{ {
if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue))
{ {
return _neutralVibrationValue; return _neutralVibrationValue;
} }
@@ -621,11 +621,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2; return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
} }
public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index) public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index)
{ {
if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue)) if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue))
{ {
rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>(); rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>();
_device.Hid.Npads.RumbleQueues[index] = rumbleQueue; _device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
} }

View File

@@ -35,5 +35,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
PlayerIndex.Unknown => NpadIdType.Unknown, PlayerIndex.Unknown => NpadIdType.Unknown,
_ => throw new ArgumentOutOfRangeException(nameof(index)) _ => throw new ArgumentOutOfRangeException(nameof(index))
}; };
public static bool IsValidNpadIdType(NpadIdType npadIdType)
{
return npadIdType <= NpadIdType.Player8 || npadIdType == NpadIdType.Handheld || npadIdType == NpadIdType.Unknown;
}
} }
} }

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum HidNpadJoyAssignmentMode
{
Dual,
Single
}
}

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidNpadHandheldActivationMode public enum NpadHandheldActivationMode
{ {
Dual, Dual,
Single, Single,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidNpadJoyDeviceType public enum NpadJoyDeviceType
{ {
Left, Left,
Right Right

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public struct HidAccelerometerParameters public struct AccelerometerParameters
{ {
public float X; public float X;
public float Y; public float Y;

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidGyroscopeZeroDriftMode public enum GyroscopeZeroDriftMode
{ {
Loose, Loose,
Standard, Standard,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public struct HidSensorFusionParameters public struct SensorFusionParameters
{ {
public float RevisePower; public float RevisePower;
public float ReviseRange; public float ReviseRange;

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationDeviceValue
{
public HidVibrationDeviceType DeviceType;
public HidVibrationDevicePosition Position;
}
}

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public struct HidVibrationDeviceHandle public struct VibrationDeviceHandle
{ {
public byte DeviceType; public byte DeviceType;
public byte PlayerId; public byte PlayerId;

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidVibrationDevicePosition public enum VibrationDevicePosition
{ {
None, None,
Left, Left,

View File

@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidVibrationDeviceType public enum VibrationDeviceType
{ {
None, None,
LinearResonantActuator, LinearResonantActuator,

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct VibrationDeviceValue
{
public VibrationDeviceType DeviceType;
public VibrationDevicePosition Position;
}
}

View File

@@ -1,9 +1,8 @@
using Ryujinx.HLE.HOS.Tamper; using System;
using System;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public struct HidVibrationValue public struct VibrationValue
{ {
public float AmplitudeLow; public float AmplitudeLow;
public float FrequencyLow; public float FrequencyLow;
@@ -12,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return obj is HidVibrationValue value && return obj is VibrationValue value &&
AmplitudeLow == value.AmplitudeLow && AmplitudeLow == value.AmplitudeLow &&
AmplitudeHigh == value.AmplitudeHigh; AmplitudeHigh == value.AmplitudeHigh;
} }

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types; using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -26,9 +27,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private bool _isFirmwareUpdateAvailableForSixAxisSensor; private bool _isFirmwareUpdateAvailableForSixAxisSensor;
private bool _isSixAxisSensorUnalteredPassthroughEnabled; private bool _isSixAxisSensorUnalteredPassthroughEnabled;
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode; private NpadHandheldActivationMode _npadHandheldActivationMode;
private HidNpadHandheldActivationMode _npadHandheldActivationMode; private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
private long _npadCommunicationMode; private long _npadCommunicationMode;
private uint _accelerometerPlayMode; private uint _accelerometerPlayMode;
@@ -37,22 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
#pragma warning restore CS0649 #pragma warning restore CS0649
private float _sevenSixAxisSensorFusionStrength; private float _sevenSixAxisSensorFusionStrength;
private HidSensorFusionParameters _sensorFusionParams; private SensorFusionParameters _sensorFusionParams;
private HidAccelerometerParameters _accelerometerParams; private AccelerometerParameters _accelerometerParams;
public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer) public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
{ {
_xpadIdEvent = new KEvent(context.Device.System.KernelContext); _xpadIdEvent = new KEvent(context.Device.System.KernelContext);
_palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext); _palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext);
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; _npadHandheldActivationMode = NpadHandheldActivationMode.Dual;
_npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual; _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
_isFirmwareUpdateAvailableForSixAxisSensor = false; _isFirmwareUpdateAvailableForSixAxisSensor = false;
_sensorFusionParams = new HidSensorFusionParameters(); _sensorFusionParams = new SensorFusionParameters();
_accelerometerParams = new HidAccelerometerParameters(); _accelerometerParams = new AccelerometerParameters();
// TODO: signal event at right place // TODO: signal event at right place
_xpadIdEvent.ReadableEvent.Signal(); _xpadIdEvent.ReadableEvent.Signal();
@@ -393,7 +392,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32(); int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding context.RequestData.BaseStream.Position += 4; // Padding
_sensorFusionParams = new HidSensorFusionParameters _sensorFusionParams = new SensorFusionParameters
{ {
RevisePower = context.RequestData.ReadInt32(), RevisePower = context.RequestData.ReadInt32(),
ReviseRange = context.RequestData.ReadInt32() ReviseRange = context.RequestData.ReadInt32()
@@ -445,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
int sixAxisSensorHandle = context.RequestData.ReadInt32(); int sixAxisSensorHandle = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; // Padding context.RequestData.BaseStream.Position += 4; // Padding
_accelerometerParams = new HidAccelerometerParameters _accelerometerParams = new AccelerometerParameters
{ {
X = context.RequestData.ReadInt32(), X = context.RequestData.ReadInt32(),
Y = context.RequestData.ReadInt32() Y = context.RequestData.ReadInt32()
@@ -539,7 +538,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context) public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
{ {
int sixAxisSensorHandle = context.RequestData.ReadInt32(); int sixAxisSensorHandle = context.RequestData.ReadInt32();
_gyroscopeZeroDriftMode = (HidGyroscopeZeroDriftMode)context.RequestData.ReadInt32(); _gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@@ -570,7 +569,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.RequestData.BaseStream.Position += 4; // Padding context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
@@ -909,54 +908,63 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
{ {
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; if (HidUtils.IsValidNpadIdType(npadIdType))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
}
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(123)] [CommandHipc(123)]
// SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType) // SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType)
public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
{ {
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); context.RequestData.BaseStream.Position += 4; // Padding
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; if (HidUtils.IsValidNpadIdType(npadIdType))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, hidNpadJoyDeviceType, _npadJoyAssignmentMode }); SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _);
}
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(124)] [CommandHipc(124)]
// SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId) // SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
{ {
PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32()); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; if (HidUtils.IsValidNpadIdType(npadIdType))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, hidControllerId, _npadJoyAssignmentMode }); context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
}
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(125)] [CommandHipc(125)]
// MergeSingleJoyAsDualJoy(uint SingleJoyId0, uint SingleJoyId1, nn::applet::AppletResourceUserId) // MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId)
public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context) public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context)
{ {
long singleJoyId0 = context.RequestData.ReadInt32(); NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32();
long singleJoyId1 = context.RequestData.ReadInt32(); NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, singleJoyId0, singleJoyId1 }); if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1))
{
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 });
}
return ResultCode.Success; return ResultCode.Success;
} }
@@ -988,7 +996,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetNpadHandheldActivationMode(ServiceCtx context) public ResultCode SetNpadHandheldActivationMode(ServiceCtx context)
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
_npadHandheldActivationMode = (HidNpadHandheldActivationMode)context.RequestData.ReadInt64(); _npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
@@ -1049,37 +1057,47 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
[CommandHipc(133)] // 5.0.0+ [CommandHipc(133)] // 5.0.0+
// SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1 // SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet
public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
{ {
PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32(); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; if (HidUtils.IsValidNpadIdType(npadIdType))
{
SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet);
context.ResponseData.Write(0); //Unknown0 if (npadIdTypeIsSet)
context.ResponseData.Write(0); //Unknown1 {
context.ResponseData.Write(npadIdTypeIsSet);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { context.ResponseData.Write((uint)npadIdTypeSet);
appletResourceUserId, }
hidControllerId, }
hidNpadJoyDeviceType,
_npadJoyAssignmentMode,
Unknown0 = 0,
Unknown1 = 0
});
return ResultCode.Success; return ResultCode.Success;
} }
private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet)
{
npadIdTypeSet = default;
npadIdTypeIsSet = false;
context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
// TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual.
// If one is found, it returns the npadIdType of the other Npad and a bool.
// If not, it returns nothing.
}
[CommandHipc(200)] [CommandHipc(200)]
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
public ResultCode GetVibrationDeviceInfo(ServiceCtx context) public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
{ {
HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<HidVibrationDeviceHandle>(); VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>();
NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{ {
@@ -1093,28 +1111,28 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return ResultCode.InvalidDeviceIndex; return ResultCode.InvalidDeviceIndex;
} }
HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None; VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None;
if (Enum.IsDefined<NpadStyleIndex>(deviceType)) if (Enum.IsDefined(deviceType))
{ {
vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator; vibrationDeviceType = VibrationDeviceType.LinearResonantActuator;
} }
else if ((uint)deviceType == 8) else if ((uint)deviceType == 8)
{ {
vibrationDeviceType = HidVibrationDeviceType.GcErm; vibrationDeviceType = VibrationDeviceType.GcErm;
} }
HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None; VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None;
if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator) if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator)
{ {
if (deviceHandle.Position == 0) if (deviceHandle.Position == 0)
{ {
vibrationDevicePosition = HidVibrationDevicePosition.Left; vibrationDevicePosition = VibrationDevicePosition.Left;
} }
else if (deviceHandle.Position == 1) else if (deviceHandle.Position == 1)
{ {
vibrationDevicePosition = HidVibrationDevicePosition.Right; vibrationDevicePosition = VibrationDevicePosition.Right;
} }
else else
{ {
@@ -1122,7 +1140,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
} }
HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue VibrationDeviceValue deviceInfo = new VibrationDeviceValue
{ {
DeviceType = vibrationDeviceType, DeviceType = vibrationDeviceType,
Position = vibrationDevicePosition Position = vibrationDevicePosition
@@ -1140,7 +1158,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
public ResultCode SendVibrationValue(ServiceCtx context) public ResultCode SendVibrationValue(ServiceCtx context)
{ {
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{ {
DeviceType = context.RequestData.ReadByte(), DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(), PlayerId = context.RequestData.ReadByte(),
@@ -1148,7 +1166,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
Reserved = context.RequestData.ReadByte() Reserved = context.RequestData.ReadByte()
}; };
HidVibrationValue vibrationValue = new HidVibrationValue VibrationValue vibrationValue = new VibrationValue
{ {
AmplitudeLow = context.RequestData.ReadSingle(), AmplitudeLow = context.RequestData.ReadSingle(),
FrequencyLow = context.RequestData.ReadSingle(), FrequencyLow = context.RequestData.ReadSingle(),
@@ -1158,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>(); Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
dualVibrationValues[deviceHandle.Position] = vibrationValue; dualVibrationValues[deviceHandle.Position] = vibrationValue;
@@ -1171,7 +1189,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
public ResultCode GetActualVibrationValue(ServiceCtx context) public ResultCode GetActualVibrationValue(ServiceCtx context)
{ {
HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
{ {
DeviceType = context.RequestData.ReadByte(), DeviceType = context.RequestData.ReadByte(),
PlayerId = context.RequestData.ReadByte(), PlayerId = context.RequestData.ReadByte(),
@@ -1181,7 +1199,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
context.ResponseData.Write(vibrationValue.AmplitudeLow); context.ResponseData.Write(vibrationValue.AmplitudeLow);
context.ResponseData.Write(vibrationValue.FrequencyLow); context.ResponseData.Write(vibrationValue.FrequencyLow);
@@ -1234,12 +1252,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
Span<HidVibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, HidVibrationDeviceHandle>(vibrationDeviceHandleBuffer); Span<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer);
Span<HidVibrationValue> vibrationValues = MemoryMarshal.Cast<byte, HidVibrationValue>(vibrationValueBuffer); Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer);
if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
{ {
Dictionary<byte, HidVibrationValue> dualVibrationValues = new Dictionary<byte, HidVibrationValue>(); Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId; PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId;
for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++) for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++)
@@ -1250,7 +1268,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
if (index != currentIndex || dualVibrationValues.Count == 2) if (index != currentIndex || dualVibrationValues.Count == 2)
{ {
context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
dualVibrationValues = new Dictionary<byte, HidVibrationValue>(); dualVibrationValues = new Dictionary<byte, VibrationValue>();
} }
dualVibrationValues[position] = vibrationValues[deviceCounter]; dualVibrationValues[position] = vibrationValues[deviceCounter];

View File

@@ -579,6 +579,15 @@ namespace Ryujinx.HLE.HOS.Services.Ro
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(10)]
// LoadNrr2(u64, u64, u64, pid)
public ResultCode LoadNrr2(ServiceCtx context)
{
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return LoadNrr(context);
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
if (isDisposing) if (isDisposing)

View File

@@ -4,7 +4,6 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Ipc;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Sm;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;

View File

@@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(60)] [CommandHipc(60)]
// IsUserSystemClockAutomaticCorrectionEnabled() -> bool // IsUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{ {
@@ -234,6 +234,17 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(62)]
// GetDebugModeFlag() -> bool
public ResultCode GetDebugModeFlag(ServiceCtx context)
{
context.ResponseData.Write(false);
Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
[CommandHipc(77)] [CommandHipc(77)]
// GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16> // GetDeviceNickName() -> buffer<nn::settings::system::DeviceNickName, 0x16>
public ResultCode GetDeviceNickName(ServiceCtx context) public ResultCode GetDeviceNickName(ServiceCtx context)

View File

@@ -1,11 +1,9 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Ipc; using Ryujinx.HLE.HOS.Kernel.Ipc;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -17,21 +15,19 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{ {
private static Dictionary<string, Type> _services; private static Dictionary<string, Type> _services;
private static readonly ConcurrentDictionary<string, KPort> _registeredServices; private readonly SmRegistry _registry;
private readonly ServerBase _commonServer; private readonly ServerBase _commonServer;
private bool _isInitialized; private bool _isInitialized;
public IUserInterface(KernelContext context) public IUserInterface(KernelContext context, SmRegistry registry)
{ {
_commonServer = new ServerBase(context, "CommonServer"); _commonServer = new ServerBase(context, "CommonServer");
_registry = registry;
} }
static IUserInterface() static IUserInterface()
{ {
_registeredServices = new ConcurrentDictionary<string, KPort>();
_services = Assembly.GetExecutingAssembly().GetTypes() _services = Assembly.GetExecutingAssembly().GetTypes()
.SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true)
.Select(service => (((ServiceAttribute)service).Name, type))) .Select(service => (((ServiceAttribute)service).Name, type)))
@@ -74,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KSession session = new KSession(context.Device.System.KernelContext); KSession session = new KSession(context.Device.System.KernelContext);
if (_registeredServices.TryGetValue(name, out KPort port)) if (_registry.TryGetService(name, out KPort port))
{ {
KernelResult result = port.EnqueueIncomingSession(session.ServerSession); KernelResult result = port.EnqueueIncomingSession(session.ServerSession);
@@ -82,6 +78,15 @@ namespace Ryujinx.HLE.HOS.Services.Sm
{ {
throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\".");
} }
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
} }
else else
{ {
@@ -107,18 +112,18 @@ namespace Ryujinx.HLE.HOS.Services.Sm
throw new NotImplementedException(name); throw new NotImplementedException(name);
} }
} }
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
} }
if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
session.ServerSession.DecrementReferenceCount();
session.ClientSession.DecrementReferenceCount();
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
return ResultCode.Success; return ResultCode.Success;
} }
@@ -179,7 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0); KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, 0);
if (!_registeredServices.TryAdd(name, port)) if (!_registry.TryRegister(name, port))
{ {
return ResultCode.AlreadyRegistered; return ResultCode.AlreadyRegistered;
} }
@@ -219,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
return ResultCode.InvalidName; return ResultCode.InvalidName;
} }
if (!_registeredServices.TryRemove(name, out _)) if (!_registry.Unregister(name))
{ {
return ResultCode.NotRegistered; return ResultCode.NotRegistered;
} }

View File

@@ -0,0 +1,49 @@
using Ryujinx.HLE.HOS.Kernel.Ipc;
using System.Collections.Concurrent;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Sm
{
class SmRegistry
{
private readonly ConcurrentDictionary<string, KPort> _registeredServices;
private readonly AutoResetEvent _serviceRegistrationEvent;
public SmRegistry()
{
_registeredServices = new ConcurrentDictionary<string, KPort>();
_serviceRegistrationEvent = new AutoResetEvent(false);
}
public bool TryGetService(string name, out KPort port)
{
return _registeredServices.TryGetValue(name, out port);
}
public bool TryRegister(string name, KPort port)
{
if (_registeredServices.TryAdd(name, port))
{
_serviceRegistrationEvent.Set();
return true;
}
return false;
}
public bool Unregister(string name)
{
return _registeredServices.TryRemove(name, out _);
}
public bool IsServiceRegistered(string name)
{
return _registeredServices.TryGetValue(name, out _);
}
public void WaitForServiceRegistration()
{
_serviceRegistrationEvent.WaitOne();
}
}
}

View File

@@ -164,6 +164,7 @@ namespace Ryujinx.Headless.SDL2
Device.Gpu.Renderer.RunLoop(() => Device.Gpu.Renderer.RunLoop(() =>
{ {
Device.Gpu.SetGpuThread();
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set(); Translator.IsReadyForTranslation.Set();

View File

@@ -543,14 +543,14 @@ namespace Ryujinx.Input.HLE
Dispose(true); Dispose(true);
} }
public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue) public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue)
{ {
if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue)) if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
{ {
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
{ {
HidVibrationValue leftVibrationValue = dualVibrationValue.Item1; VibrationValue leftVibrationValue = dualVibrationValue.Item1;
HidVibrationValue rightVibrationValue = dualVibrationValue.Item2; VibrationValue rightVibrationValue = dualVibrationValue.Item2;
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));