Compare commits

...

4 Commits

Author SHA1 Message Date
Willi Schinmeyer 090ac1a703
Merge ede3f0f5b8 into 73f985d27c 2024-09-19 18:36:57 +07:00
jhorv 73f985d27c
Replace passing by IMemoryOwner<byte> with passing by concrete MemoryOwner<byte> (#7171)
* refactor(perf): pass MemoryOwner<byte> around as itself rather than IMemoryOwner<byte>

* fix(perf): get span via MemoryOwner<byte>.Span property instead of through Memory property

* fix: incorrect comment change
2024-09-18 23:00:54 -03:00
Willi Schinmeyer ede3f0f5b8 Correct raw screenshot tooltip
Watermarks are actually applied by the OS, not the game,
as the tooltip previously wrongly stated.
However, per #5952, games can also react to screenshots,
which also is not supported.
2024-09-06 21:11:04 +02:00
Willi Schinmeyer 30d7253e26 Add raw screenshot gamepad button
This triggers the same raw screenshot as the keyboard hotkey,
i.e. the game is not informed of the intent to screenshot,
so it cannot add watermarks etc.

The button can be configured using both the regular UI and GTK.

Includes English and German translations.

An alternate approach is proposed in draft PR #4503:
Allow triggering all hotkeys using the gamepad.

Re: #5952
2024-09-06 13:06:39 +02:00
32 changed files with 275 additions and 81 deletions

View File

@ -3,6 +3,7 @@ namespace Ryujinx.Common.Configuration.Hid
public class LeftJoyconCommonConfig<TButton>
{
public TButton ButtonMinus { get; set; }
public TButton ButtonScreenshot { get; set; }
public TButton ButtonL { get; set; }
public TButton ButtonZl { get; set; }
public TButton ButtonSl { get; set; }

View File

@ -1,4 +1,4 @@
using System.Buffers;
using Ryujinx.Common.Memory;
namespace Ryujinx.Graphics.GAL
{
@ -18,30 +18,30 @@ namespace Ryujinx.Graphics.GAL
PinnedSpan<byte> GetData(int layer, int level);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
void SetData(IMemoryOwner<byte> data);
void SetData(MemoryOwner<byte> data);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
void SetData(IMemoryOwner<byte> data, int layer, int level);
void SetData(MemoryOwner<byte> data, int layer, int level);
/// <summary>
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
/// the operation completes.
/// </summary>
/// <param name="data">Texture data bytes</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param>
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
void SetStorage(BufferRange buffer);

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
public readonly CommandType CommandType => CommandType.TextureSetData;
private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data;
private TableRef<MemoryOwner<byte>> _data;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data)
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data)
{
_texture = texture;
_data = data;

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data;
private TableRef<MemoryOwner<byte>> _data;
private int _layer;
private int _level;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level)
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level)
{
_texture = texture;
_data = data;

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
{
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
private TableRef<ThreadedTexture> _texture;
private TableRef<IMemoryOwner<byte>> _data;
private TableRef<MemoryOwner<byte>> _data;
private int _layer;
private int _level;
private Rectangle<int> _region;
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
{
_texture = texture;
_data = data;

View File

@ -1,6 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using System.Buffers;
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
{
@ -111,21 +111,21 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
_renderer.QueueCommand();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
_renderer.QueueCommand();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
_renderer.QueueCommand();

View File

@ -1,10 +1,10 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -353,7 +353,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
if (target != null)
{
IMemoryOwner<byte> data;
MemoryOwner<byte> data;
if (srcLinear)
{
data = LayoutConverter.ConvertLinearStridedToLinear(

View File

@ -7,7 +7,6 @@ using Ryujinx.Graphics.Texture.Astc;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -662,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
IMemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
MemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
if (ScaleFactor != 1f && AllowScaledSetData())
{
@ -685,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Uploads new texture data to the host GPU.
/// </summary>
/// <param name="data">New data</param>
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
BlacklistScale();
@ -704,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="data">New data</param>
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
BlacklistScale();
@ -722,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="layer">Target layer</param>
/// <param name="level">Target level</param>
/// <param name="region">Target sub-region of the texture to update</param>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
BlacklistScale();
@ -740,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="level">Mip level to convert</param>
/// <param name="single">True to convert a single slice</param>
/// <returns>Converted data</returns>
public IMemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
public MemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
{
int width = Info.Width;
int height = Info.Height;
@ -755,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int sliceDepth = single ? 1 : depth;
IMemoryOwner<byte> linear;
MemoryOwner<byte> linear;
if (Info.IsLinear)
{
@ -788,7 +787,7 @@ namespace Ryujinx.Graphics.Gpu.Image
data);
}
IMemoryOwner<byte> result = linear;
MemoryOwner<byte> result = linear;
// Handle compressed cases not supported by the host:
// - ASTC is usually not supported on desktop cards.
@ -832,19 +831,19 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Etc2RgbaUnorm:
using (result)
{
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
return ETC2Decoder.DecodeRgba(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbPtaSrgb:
case Format.Etc2RgbPtaUnorm:
using (result)
{
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
return ETC2Decoder.DecodePta(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Etc2RgbSrgb:
case Format.Etc2RgbUnorm:
using (result)
{
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
return ETC2Decoder.DecodeRgb(result.Span, width, height, sliceDepth, levels, layers);
}
}
}
@ -856,43 +855,43 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.Bc1RgbaUnorm:
using (result)
{
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
return BCnDecoder.DecodeBC1(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc2Srgb:
case Format.Bc2Unorm:
using (result)
{
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
return BCnDecoder.DecodeBC2(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc3Srgb:
case Format.Bc3Unorm:
using (result)
{
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
return BCnDecoder.DecodeBC3(result.Span, width, height, sliceDepth, levels, layers);
}
case Format.Bc4Snorm:
case Format.Bc4Unorm:
using (result)
{
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
return BCnDecoder.DecodeBC4(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
}
case Format.Bc5Snorm:
case Format.Bc5Unorm:
using (result)
{
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
return BCnDecoder.DecodeBC5(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
}
case Format.Bc6HSfloat:
case Format.Bc6HUfloat:
using (result)
{
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
return BCnDecoder.DecodeBC6(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
}
case Format.Bc7Srgb:
case Format.Bc7Unorm:
using (result)
{
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
return BCnDecoder.DecodeBC7(result.Span, width, height, sliceDepth, levels, layers);
}
}
}
@ -900,7 +899,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (result)
{
var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width);
var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Span, width);
if (_context.Capabilities.SupportsR4G4B4A4Format)
{
@ -910,7 +909,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (converted)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width);
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Span, width);
}
}
}
@ -921,7 +920,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
using (result)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
}
}
}
@ -933,24 +932,24 @@ namespace Ryujinx.Graphics.Gpu.Image
case Format.R5G6B5Unorm:
using (result)
{
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Span, width);
}
case Format.B5G5R5A1Unorm:
case Format.R5G5B5X1Unorm:
case Format.R5G5B5A1Unorm:
using (result)
{
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Span, width, Format == Format.R5G5B5X1Unorm);
}
case Format.A1B5G5R5Unorm:
using (result)
{
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Span, width);
}
case Format.R4G4B4A4Unorm:
using (result)
{
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
}
}
}

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Texture;
@ -5,7 +6,6 @@ using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
MemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
}

View File

@ -1,7 +1,7 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
using System.Buffers;
namespace Ryujinx.Graphics.OpenGL.Image
{
@ -55,9 +55,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
var dataSpan = data.Memory.Span;
var dataSpan = data.Span;
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
@ -65,13 +65,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
throw new NotSupportedException();
}

View File

@ -1,8 +1,8 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using System;
using System.Buffers;
using System.Diagnostics;
namespace Ryujinx.Graphics.OpenGL.Image
@ -448,13 +448,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
using (data = EnsureDataFormat(data))
{
unsafe
{
var dataSpan = data.Memory.Span;
var dataSpan = data.Span;
fixed (byte* ptr = dataSpan)
{
ReadFrom((IntPtr)ptr, dataSpan.Length);
@ -463,13 +463,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
using (data = EnsureDataFormat(data))
{
unsafe
{
fixed (byte* ptr = data.Memory.Span)
fixed (byte* ptr = data.Span)
{
int width = Math.Max(Info.Width >> level, 1);
int height = Math.Max(Info.Height >> level, 1);
@ -480,7 +480,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
using (data = EnsureDataFormat(data))
{
@ -489,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
unsafe
{
fixed (byte* ptr = data.Memory.Span)
fixed (byte* ptr = data.Span)
{
ReadFrom2D(
(IntPtr)ptr,
@ -522,13 +522,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
}
private IMemoryOwner<byte> EnsureDataFormat(IMemoryOwner<byte> data)
private MemoryOwner<byte> EnsureDataFormat(MemoryOwner<byte> data)
{
if (Format == Format.S8UintD24Unorm)
{
using (data)
{
return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span);
return FormatConverter.ConvertS8D24ToD24S8(data.Span);
}
}

View File

@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
using System.Collections.Generic;
using Format = Ryujinx.Graphics.GAL.Format;
using VkFormat = Silk.NET.Vulkan.Format;
@ -84,20 +84,20 @@ namespace Ryujinx.Graphics.Vulkan
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
_gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
_gd.SetBufferData(_bufferHandle, _offset, data.Span);
data.Dispose();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
throw new NotSupportedException();
}

View File

@ -1,7 +1,7 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -746,23 +746,23 @@ namespace Ryujinx.Graphics.Vulkan
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data)
public void SetData(MemoryOwner<byte> data)
{
SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
SetData(data.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
data.Dispose();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level)
public void SetData(MemoryOwner<byte> data, int layer, int level)
{
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true);
SetData(data.Span, layer, level, 1, 1, singleSlice: true);
data.Dispose();
}
/// <inheritdoc/>
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
{
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region);
SetData(data.Span, layer, level, 1, 1, singleSlice: true, region);
data.Dispose();
}

View File

@ -166,6 +166,7 @@ namespace Ryujinx.Input.GTK3
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Misc1, (Key)_configuration.LeftJoycon.ButtonScreenshot));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));

View File

@ -15,6 +15,7 @@ using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Widgets;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
@ -73,11 +74,14 @@ namespace Ryujinx.UI
private HideCursorMode _hideCursorMode;
private readonly InputManager _inputManager;
private readonly IKeyboard _keyboardInterface;
private readonly List<IGamepad> _gamepadInterfaces;
private readonly GraphicsDebugLevel _glLogLevel;
private string _gpuBackendName;
private string _gpuDriverName;
private bool _isMouseInClient;
private int _gamepadsChanged; // use atomically via Interlocked
public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel)
{
var mouseDriver = new GTK3MouseDriver(this);
@ -88,6 +92,13 @@ namespace Ryujinx.UI
TouchScreenManager = _inputManager.CreateTouchScreenManager();
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
_gamepadInterfaces = new List<IGamepad>();
_inputManager.GamepadDriver.OnGamepadConnected += GamepadConnected;
_inputManager.GamepadDriver.OnGamepadDisconnected += GamepadDisconnected;
RefreshGamepads();
WaitEvent = new ManualResetEvent(false);
_glLogLevel = glLogLevel;
@ -649,6 +660,11 @@ namespace Ryujinx.UI
});
}
if (Interlocked.Exchange(ref _gamepadsChanged, 0) == 1)
{
RefreshGamepads();
}
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if ((Toplevel as MainWindow).IsFocused)
@ -767,7 +783,8 @@ namespace Ryujinx.UI
state |= KeyboardHotkeyState.ToggleVSync;
}
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot) ||
ButtonPressedOnAnyGamepad(GamepadButtonInputId.Misc1))
{
state |= KeyboardHotkeyState.Screenshot;
}
@ -809,5 +826,37 @@ namespace Ryujinx.UI
return state;
}
private void GamepadConnected(string id)
{
Interlocked.Exchange(ref _gamepadsChanged, 1);
}
private void GamepadDisconnected(string id)
{
Interlocked.Exchange(ref _gamepadsChanged, 1);
}
private void RefreshGamepads()
{
_gamepadInterfaces.Clear();
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
_gamepadInterfaces.Add(_inputManager.GamepadDriver.GetGamepad(id));
}
}
private bool ButtonPressedOnAnyGamepad(GamepadButtonInputId button)
{
foreach (IGamepad gamepad in _gamepadInterfaces)
{
if (gamepad.IsPressed(button))
{
return true;
}
}
return false;
}
}
}

View File

@ -84,6 +84,7 @@ namespace Ryujinx.UI.Windows
[GUI] ToggleButton _dpadLeft;
[GUI] ToggleButton _dpadRight;
[GUI] ToggleButton _minus;
[GUI] ToggleButton _screenshot;
[GUI] ToggleButton _l;
[GUI] ToggleButton _zL;
[GUI] ToggleButton _rStick;
@ -165,6 +166,7 @@ namespace Ryujinx.UI.Windows
_dpadLeft.Clicked += Button_Pressed;
_dpadRight.Clicked += Button_Pressed;
_minus.Clicked += Button_Pressed;
_screenshot.Clicked += Button_Pressed;
_l.Clicked += Button_Pressed;
_zL.Clicked += Button_Pressed;
_lSl.Clicked += Button_Pressed;
@ -400,6 +402,7 @@ namespace Ryujinx.UI.Windows
_dpadLeft.Label = "Unbound";
_dpadRight.Label = "Unbound";
_minus.Label = "Unbound";
_screenshot.Label = "Unbound";
_l.Label = "Unbound";
_zL.Label = "Unbound";
_lSl.Label = "Unbound";
@ -460,6 +463,7 @@ namespace Ryujinx.UI.Windows
_dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString();
_dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString();
_minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString();
_screenshot.Label = keyboardConfig.LeftJoycon.ButtonScreenshot.ToString();
_l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString();
_zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString();
_lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString();
@ -498,6 +502,7 @@ namespace Ryujinx.UI.Windows
_dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString();
_dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString();
_minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString();
_screenshot.Label = controllerConfig.LeftJoycon.ButtonScreenshot.ToString();
_l.Label = controllerConfig.LeftJoycon.ButtonL.ToString();
_zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString();
_lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString();
@ -566,6 +571,7 @@ namespace Ryujinx.UI.Windows
Enum.TryParse(_dpadLeft.Label, out Key lDPadLeft);
Enum.TryParse(_dpadRight.Label, out Key lDPadRight);
Enum.TryParse(_minus.Label, out Key lButtonMinus);
Enum.TryParse(_screenshot.Label, out Key lButtonScreenshot);
Enum.TryParse(_l.Label, out Key lButtonL);
Enum.TryParse(_zL.Label, out Key lButtonZl);
Enum.TryParse(_lSl.Label, out Key lButtonSl);
@ -597,6 +603,7 @@ namespace Ryujinx.UI.Windows
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
ButtonMinus = lButtonMinus,
ButtonScreenshot = lButtonScreenshot,
ButtonL = lButtonL,
ButtonZl = lButtonZl,
ButtonSl = lButtonSl,
@ -643,6 +650,7 @@ namespace Ryujinx.UI.Windows
Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick);
Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton);
Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus);
Enum.TryParse(_screenshot.Label, out ConfigGamepadInputId lButtonScreenshot);
Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL);
Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl);
Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl);
@ -710,6 +718,7 @@ namespace Ryujinx.UI.Windows
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonMinus = lButtonMinus,
ButtonScreenshot = lButtonScreenshot,
ButtonL = lButtonL,
ButtonZl = lButtonZl,
ButtonSl = lButtonSl,
@ -997,6 +1006,7 @@ namespace Ryujinx.UI.Windows
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonScreenshot = Key.Unbound, // keyboard already has a default screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
@ -1057,6 +1067,7 @@ namespace Ryujinx.UI.Windows
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonScreenshot = ConfigGamepadInputId.Misc1,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,

View File

@ -475,6 +475,31 @@
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="width_request">80</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Screenshot</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="_screenshot">
<property name="label" translatable="yes"> </property>
<property name="width_request">70</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
private readonly MemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => _rawDataOwner.Memory.Span;
private Span<byte> Raw => _rawDataOwner.Span;
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];

View File

@ -161,6 +161,7 @@ namespace Ryujinx.Headless.SDL2
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonScreenshot = Key.Unbound, // keyboard already has a default screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
@ -221,6 +222,7 @@ namespace Ryujinx.Headless.SDL2
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonScreenshot = ConfigGamepadInputId.Misc1,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,

View File

@ -244,6 +244,7 @@ namespace Ryujinx.Input.SDL2
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Misc1, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonScreenshot));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));

View File

@ -372,6 +372,7 @@ namespace Ryujinx.Input.SDL2
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Misc1, (Key)_configuration.LeftJoycon.ButtonScreenshot));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));

View File

@ -1,5 +1,5 @@
using Ryujinx.Common.Memory;
using System;
using System.Buffers;
namespace Ryujinx.Memory
{
@ -7,7 +7,7 @@ namespace Ryujinx.Memory
{
private readonly IWritableBlock _block;
private readonly ulong _va;
private readonly IMemoryOwner<byte> _memoryOwner;
private readonly MemoryOwner<byte> _memoryOwner;
private readonly bool _tracked;
private bool NeedsWriteback => _block != null;
@ -22,7 +22,7 @@ namespace Ryujinx.Memory
Memory = memory;
}
public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner<byte> memoryOwner, bool tracked = false)
public WritableRegion(IWritableBlock block, ulong va, MemoryOwner<byte> memoryOwner, bool tracked = false)
: this(block, va, memoryOwner.Memory, tracked)
{
_memoryOwner = memoryOwner;

View File

@ -888,6 +888,7 @@ namespace Ryujinx.UI.Common.Configuration
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonScreenshot = Key.Unbound, // keyboard already has a default screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
@ -1117,6 +1118,7 @@ namespace Ryujinx.UI.Common.Configuration
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonScreenshot = Key.Unbound, // keyboard already has a default screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,

View File

@ -82,6 +82,7 @@ namespace Ryujinx.Ava
private readonly MainWindowViewModel _viewModel;
private readonly IKeyboard _keyboardInterface;
private readonly List<IGamepad> _gamepadInterfaces;
private readonly TopLevel _topLevel;
public RendererHost RendererHost;
@ -89,6 +90,8 @@ namespace Ryujinx.Ava
private float _newVolume;
private KeyboardHotkeyState _prevHotkeyState;
private int _gamepadsChanged; // use atomically via Interlocked
private long _lastCursorMoveTime;
private bool _isCursorInRenderer = true;
private bool _ignoreCursorState = false;
@ -160,6 +163,13 @@ namespace Ryujinx.Ava
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
_gamepadInterfaces = new List<IGamepad>();
_inputManager.GamepadDriver.OnGamepadConnected += GamepadConnected;
_inputManager.GamepadDriver.OnGamepadDisconnected += GamepadDisconnected;
RefreshGamepads();
NpadManager = _inputManager.CreateNpadManager();
TouchScreenManager = _inputManager.CreateTouchScreenManager();
ApplicationPath = applicationPath;
@ -1103,6 +1113,11 @@ namespace Ryujinx.Ava
return false;
}
if (Interlocked.Exchange(ref _gamepadsChanged, 0) == 1)
{
RefreshGamepads();
}
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_viewModel.IsActive)
@ -1241,7 +1256,8 @@ namespace Ryujinx.Ava
{
state = KeyboardHotkeyState.ToggleVSync;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot) ||
ButtonPressedOnAnyGamepad(GamepadButtonInputId.Misc1))
{
state = KeyboardHotkeyState.Screenshot;
}
@ -1276,5 +1292,37 @@ namespace Ryujinx.Ava
return state;
}
private void GamepadConnected(string id)
{
Interlocked.Exchange(ref _gamepadsChanged, 1);
}
private void GamepadDisconnected(string id)
{
Interlocked.Exchange(ref _gamepadsChanged, 1);
}
private void RefreshGamepads()
{
_gamepadInterfaces.Clear();
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
_gamepadInterfaces.Add(_inputManager.GamepadDriver.GetGamepad(id));
}
}
private bool ButtonPressedOnAnyGamepad(GamepadButtonInputId button)
{
foreach (IGamepad gamepad in _gamepadInterfaces)
{
if (gamepad.IsPressed(button))
{
return true;
}
}
return false;
}
}
}

View File

@ -233,6 +233,8 @@
"ControllerSettingsDPadDown": "Runter",
"ControllerSettingsDPadLeft": "Links",
"ControllerSettingsDPadRight": "Rechts",
"ControllerSettingsRawScreenshot": "Unbearbeiteter Screenshot",
"ControllerSettingsRawScreenshotTooltip": "Macht ein Bildschirmfoto ohne Wasserzeichen etc.",
"ControllerSettingsStickButton": "Button",
"ControllerSettingsStickUp": "Hoch",
"ControllerSettingsStickDown": "Runter",

View File

@ -234,6 +234,8 @@
"ControllerSettingsDPadDown": "Down",
"ControllerSettingsDPadLeft": "Left",
"ControllerSettingsDPadRight": "Right",
"ControllerSettingsRawScreenshot": "Raw Screenshot",
"ControllerSettingsRawScreenshotTooltip": "Takes a screenshot without any watermarks etc.",
"ControllerSettingsStickButton": "Button",
"ControllerSettingsStickUp": "Up",
"ControllerSettingsStickDown": "Down",

View File

@ -128,6 +128,7 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Misc1, (Key)_configuration.LeftJoycon.ButtonScreenshot));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));

View File

@ -178,6 +178,17 @@ namespace Ryujinx.Ava.UI.Models.Input
}
}
private GamepadInputId _buttonScreenshot;
public GamepadInputId ButtonScreenshot
{
get => _buttonScreenshot;
set
{
_buttonScreenshot = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonL;
public GamepadInputId ButtonL
{
@ -440,6 +451,7 @@ namespace Ryujinx.Ava.UI.Models.Input
DpadRight = controllerInput.LeftJoycon.DpadRight;
ButtonL = controllerInput.LeftJoycon.ButtonL;
ButtonMinus = controllerInput.LeftJoycon.ButtonMinus;
ButtonScreenshot = controllerInput.LeftJoycon.ButtonScreenshot;
LeftButtonSl = controllerInput.LeftJoycon.ButtonSl;
LeftButtonSr = controllerInput.LeftJoycon.ButtonSr;
ButtonZl = controllerInput.LeftJoycon.ButtonZl;
@ -502,6 +514,7 @@ namespace Ryujinx.Ava.UI.Models.Input
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonScreenshot = ButtonScreenshot,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr,
ButtonZl = ButtonZl,

View File

@ -381,6 +381,7 @@ namespace Ryujinx.Ava.UI.Models.Input
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonScreenshot = Key.Unbound, // keyboard screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonZl = ButtonZl,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr,

View File

@ -545,6 +545,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonScreenshot = Key.Unbound, // keyboard already has a default screenshot configured via ConfigurationState.Hid.Hotkeys.Screenshot, so no gamepad mapping needed
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
@ -605,6 +606,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonScreenshot = ConfigGamepadInputId.Misc1,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,

View File

@ -311,6 +311,36 @@
</StackPanel>
</StackPanel>
</Border>
<!-- Screenshot -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsLeft}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<!-- Raw Screenshot -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRawScreenshot}"
ToolTip.Tip="{locale:Locale ControllerSettingsRawScreenshotTooltip}"
TextAlignment="Center" />
<ToggleButton Name="ButtonScreenshot">
<TextBlock
Text="{Binding Config.ButtonScreenshot, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Triggers & Side Buttons -->
<StackPanel

View File

@ -84,6 +84,9 @@ namespace Ryujinx.Ava.UI.Views.Input
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<GamepadInputId>();
break;
case "ButtonScreenshot":
viewModel.Config.ButtonScreenshot = buttonValue.AsHidType<GamepadInputId>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<GamepadInputId>();
break;