mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-27 16:00:06 +02:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db2c4a49e4 | ||
|
|
95ab195207 | ||
|
|
07d35ab0e3 | ||
|
|
ab1afccb03 | ||
|
|
02b2ad6162 | ||
|
|
a8b5501253 | ||
|
|
4315f4795e | ||
|
|
d18abffa65 | ||
|
|
551f29517d | ||
|
|
a0ad6f2246 | ||
|
|
a412999eeb | ||
|
|
87605ebce2 | ||
|
|
b67e8df788 |
@@ -413,6 +413,10 @@ namespace ARMeilleure.Translation
|
||||
|
||||
context.CurrOp = opCode;
|
||||
|
||||
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||
Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
|
||||
context.Store(dispAddressAddr, Const(opCode.Address));
|
||||
|
||||
bool isLastOp = opcIndex == block.OpCodes.Count - 1;
|
||||
|
||||
if (isLastOp)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class Decoder<T> where T : IInstEmit
|
||||
{
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb)
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock, bool isThumb)
|
||||
{
|
||||
List<Block> blocks = [];
|
||||
List<ulong> branchTargets = [];
|
||||
@@ -24,7 +24,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
|
||||
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr, bool isThumb)
|
||||
{
|
||||
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
|
||||
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true, isThumb);
|
||||
|
||||
Dictionary<ulong, int> targets = new();
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, IAddressTable<ulong> funcTable, nint dispatchStubPtr)
|
||||
{
|
||||
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address);
|
||||
MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address, singleBlock: true);
|
||||
|
||||
Dictionary<ulong, int> targets = new();
|
||||
List<PendingBranch> pendingBranches = [];
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
private const uint NzcvFlags = 0xfu << 28;
|
||||
private const uint CFlag = 0x1u << 29;
|
||||
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address)
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool singleBlock)
|
||||
{
|
||||
List<Block> blocks = [];
|
||||
List<ulong> branchTargets = [];
|
||||
@@ -35,7 +35,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
if (block.IsTruncated || (singleBlock && block.EndsWithBranch) || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsGeometryShader;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsTransformFeedback;
|
||||
public readonly bool SupportsImageBufferPixelAlignment;
|
||||
public readonly bool SupportsImageLoadFormatted;
|
||||
public readonly bool SupportsLayerVertexTessellation;
|
||||
public readonly bool SupportsMismatchingViewFormat;
|
||||
@@ -43,6 +44,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsShaderNonUniformIndexing;
|
||||
public readonly bool SupportsTextureBufferPixelAlignment;
|
||||
public readonly bool SupportsTextureGatherOffsets;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsVertexStoreAndAtomics;
|
||||
@@ -101,6 +103,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsGeometryShader,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsTransformFeedback,
|
||||
bool supportsImageBufferPixelAlignment,
|
||||
bool supportsImageLoadFormatted,
|
||||
bool supportsLayerVertexTessellation,
|
||||
bool supportsMismatchingViewFormat,
|
||||
@@ -112,6 +115,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsShaderNonUniformIndexing,
|
||||
bool supportsTextureBufferPixelAlignment,
|
||||
bool supportsTextureGatherOffsets,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsVertexStoreAndAtomics,
|
||||
@@ -164,6 +168,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsGeometryShader = supportsGeometryShader;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsTransformFeedback = supportsTransformFeedback;
|
||||
SupportsImageBufferPixelAlignment = supportsImageBufferPixelAlignment;
|
||||
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
||||
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
|
||||
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
|
||||
@@ -175,6 +180,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
|
||||
SupportsTextureBufferPixelAlignment = supportsTextureBufferPixelAlignment;
|
||||
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
|
||||
|
||||
@@ -466,6 +466,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
|
||||
|
||||
_channel.TextureManager.SignalRenderTargetsModifiable();
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
int textureId = _state.State.DrawTextureTextureId;
|
||||
@@ -803,7 +804,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int index = (argument >> 6) & 0xf;
|
||||
int layer = (argument >> 10) & 0x3ff;
|
||||
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor;
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor | RenderTargetUpdateFlags.ForClear;
|
||||
|
||||
if (layer != 0 || layerCount > 1)
|
||||
{
|
||||
|
||||
@@ -38,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
DiscardClip = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the render target will be used for a clear operation.
|
||||
/// </summary>
|
||||
ForClear = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Default update flags for draw.
|
||||
/// </summary>
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
private bool _fsReadsFragCoord;
|
||||
private bool _fsAlwaysDiscards;
|
||||
private bool _vsUsesDrawParameters;
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
@@ -491,6 +492,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
Span<RtColorState> rtColorStateSpan = _state.State.RtColorState.AsSpan();
|
||||
|
||||
bool rtModifiable = updateFlags.HasFlag(RenderTargetUpdateFlags.ForClear) || !_fsAlwaysDiscards;
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
|
||||
@@ -499,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse))
|
||||
{
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null, rtModifiable);
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -518,7 +521,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
samplesInY,
|
||||
sizeHint);
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color, rtModifiable);
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
@@ -572,7 +575,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
}
|
||||
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
|
||||
changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil, rtModifiable);
|
||||
|
||||
if (changedScale)
|
||||
{
|
||||
@@ -774,6 +777,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
float width = scaleX * 2;
|
||||
float height = scaleY * 2;
|
||||
|
||||
if (yNegate)
|
||||
{
|
||||
y += _state.State.ScreenScissorState.Height - MathF.Abs(height);
|
||||
}
|
||||
|
||||
float scale = _channel.TextureManager.RenderTargetScale;
|
||||
if (scale != 1f)
|
||||
{
|
||||
@@ -1517,20 +1525,38 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
_currentProgramInfo[stageIndex] = info;
|
||||
}
|
||||
|
||||
if (gs.Shaders[5]?.Info.UsesFragCoord == true)
|
||||
{
|
||||
// Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
|
||||
_fsReadsFragCoord = false;
|
||||
|
||||
if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0)
|
||||
ShaderProgramInfo fragmentShaderInfo = gs.Shaders[5]?.Info;
|
||||
|
||||
if (fragmentShaderInfo != null)
|
||||
{
|
||||
if (fragmentShaderInfo.UsesFragCoord)
|
||||
{
|
||||
UpdateSupportBufferViewportSize();
|
||||
// Make sure we update the viewport size on the support buffer if it will be consumed on the new shader.
|
||||
|
||||
if (!_fsReadsFragCoord && (_state.State.YControl & YControl.NegateY) != 0)
|
||||
{
|
||||
UpdateSupportBufferViewportSize();
|
||||
}
|
||||
|
||||
_fsReadsFragCoord = true;
|
||||
}
|
||||
|
||||
_fsReadsFragCoord = true;
|
||||
if (_fsAlwaysDiscards != fragmentShaderInfo.HasUnconditionalDiscard)
|
||||
{
|
||||
_fsAlwaysDiscards = fragmentShaderInfo.HasUnconditionalDiscard;
|
||||
|
||||
if (!_fsAlwaysDiscards)
|
||||
{
|
||||
_channel.TextureManager.RefreshModifiedTextures();
|
||||
_channel.TextureManager.SignalRenderTargetsModifiable();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_fsReadsFragCoord = false;
|
||||
_fsAlwaysDiscards = false;
|
||||
}
|
||||
|
||||
if (gs.VertexAsCompute != null)
|
||||
|
||||
@@ -1572,11 +1572,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
|
||||
public void SignalModifying(bool bound)
|
||||
{
|
||||
if (bound)
|
||||
{
|
||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||
}
|
||||
|
||||
if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer)
|
||||
{
|
||||
_modifiedStale = false;
|
||||
@@ -1584,9 +1579,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
_physicalMemory.TextureCache.Lift(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals that a render target texture has been either bound or unbound.
|
||||
/// </summary>
|
||||
/// <param name="bound">True if the texture has been bound, false if it has been unbound</param>
|
||||
public void SignalBindingChange(bool bound)
|
||||
{
|
||||
if (bound)
|
||||
{
|
||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||
|
||||
IncrementReferenceCount();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -67,6 +68,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
private int _lastFragmentTotal;
|
||||
|
||||
private readonly int _bufferTextureAlignment;
|
||||
private readonly int _bufferImageAlignment;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture bindings manager.
|
||||
/// </summary>
|
||||
@@ -108,6 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
|
||||
_textureCounts = [];
|
||||
|
||||
int alignment = context.Capabilities.TextureBufferOffsetAlignment;
|
||||
_bufferTextureAlignment = context.Capabilities.SupportsTextureBufferPixelAlignment ? 0 : alignment;
|
||||
_bufferImageAlignment = context.Capabilities.SupportsImageBufferPixelAlignment ? 0 : alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -521,10 +529,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, false);
|
||||
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, false);
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -659,7 +669,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Buffers are frequently re-created to accommodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, true);
|
||||
MultiRange range = GetAlignedBufferTextureRange(texture, index, stageIndex, true);
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, range, bindingInfo, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
@@ -692,6 +704,61 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned address of the texture according to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to have its address aligned</param>
|
||||
/// <param name="index">Index of the texture in the shader</param>
|
||||
/// <param name="stageIndex">Index of the shader stage</param>
|
||||
/// <param name="isImage">True if the texture is bound as image, false for sampled textures</param>
|
||||
/// <returns>Aligned address and size of the buffer texture</returns>
|
||||
private MultiRange GetAlignedBufferTextureRange(Texture texture, int index, int stageIndex, bool isImage)
|
||||
{
|
||||
MultiRange range = texture.Range;
|
||||
int alignment = isImage ? _bufferImageAlignment : _bufferTextureAlignment;
|
||||
|
||||
if (alignment != 0)
|
||||
{
|
||||
MemoryRange firstRange = range.GetSubRange(0);
|
||||
ulong misalign = firstRange.Address & ((ulong)alignment - 1);
|
||||
int offset = misalign != 0 ? (int)misalign / texture.Info.FormatInfo.BytesPerPixel : 0;
|
||||
|
||||
if (misalign != 0)
|
||||
{
|
||||
firstRange = new MemoryRange(firstRange.Address - misalign, firstRange.Size + misalign);
|
||||
|
||||
if (range.Count > 1)
|
||||
{
|
||||
MemoryRange[] ranges = new MemoryRange[range.Count];
|
||||
|
||||
ranges[0] = firstRange;
|
||||
|
||||
for (int i = 1; i < range.Count; i++)
|
||||
{
|
||||
ranges[i] = range.GetSubRange(i);
|
||||
}
|
||||
|
||||
range = new MultiRange(ranges);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = new MultiRange(firstRange.Address, firstRange.Size);
|
||||
}
|
||||
}
|
||||
|
||||
if (isImage)
|
||||
{
|
||||
_context.SupportBufferUpdater.UpdateBufferImageOffset(stageIndex, index, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.SupportBufferUpdater.UpdateBufferTextureOffset(stageIndex, index, offset);
|
||||
}
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture handle.
|
||||
/// </summary>
|
||||
|
||||
@@ -19,12 +19,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
private readonly SamplerPoolCache _samplerPoolCache;
|
||||
|
||||
/// <summary>
|
||||
/// Bound render target texture modification report state.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
private enum BindState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Render target texture has not been signalled for modification, and can't be modified on the next render operations.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture has been signalled for modification.
|
||||
/// </summary>
|
||||
Bound = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture might be modified on the next render operations.
|
||||
/// </summary>
|
||||
Modified = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Render target texture has been signalled for modification and might be modified on the next render operations.
|
||||
/// </summary>
|
||||
BoundModified = Bound | Modified,
|
||||
}
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
private readonly bool[] _rtColorsBound;
|
||||
private readonly BindState[] _rtColorsBound;
|
||||
private Texture _rtDepthStencil;
|
||||
private ITexture _rtHostDs;
|
||||
private bool _rtDsBound;
|
||||
private BindState _rtDsBound;
|
||||
|
||||
public int ClipRegionWidth { get; private set; }
|
||||
public int ClipRegionHeight { get; private set; }
|
||||
@@ -55,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
_rtColorsBound = new bool[Constants.TotalRenderTargets];
|
||||
_rtColorsBound = new BindState[Constants.TotalRenderTargets];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,27 +178,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the color buffer to set (up to 8)</param>
|
||||
/// <param name="color">The color buffer texture</param>
|
||||
/// <param name="modified">Indicates if the following render operations will modidify <paramref name="color"/> contents</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetColor(int index, Texture color)
|
||||
public bool SetRenderTargetColor(int index, Texture color, bool modified)
|
||||
{
|
||||
bool hasValue = color != null;
|
||||
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
||||
|
||||
if (_rtColors[index] != color)
|
||||
{
|
||||
if (_rtColorsBound[index])
|
||||
Texture oldColor = _rtColors[index];
|
||||
|
||||
if (oldColor != null)
|
||||
{
|
||||
_rtColors[index]?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtColorsBound[index] = true;
|
||||
if (_rtColorsBound[index].HasFlag(BindState.Bound))
|
||||
{
|
||||
oldColor.SignalModifying(false);
|
||||
}
|
||||
|
||||
oldColor.SignalBindingChange(false);
|
||||
}
|
||||
|
||||
_rtColorsBound[index] = modified ? BindState.BoundModified : BindState.None;
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
color.SynchronizeMemory();
|
||||
color.SignalModifying(true);
|
||||
|
||||
if (modified)
|
||||
{
|
||||
color.SignalModifying(true);
|
||||
}
|
||||
|
||||
color.SignalBindingChange(true);
|
||||
}
|
||||
|
||||
_rtColors[index] = color;
|
||||
@@ -184,27 +223,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Sets the render target depth-stencil buffer.
|
||||
/// </summary>
|
||||
/// <param name="depthStencil">The depth-stencil buffer texture</param>
|
||||
/// <param name="modified">Indicates if the following render operations will modidify <paramref name="depthStencil"/> contents</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil)
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil, bool modified)
|
||||
{
|
||||
bool hasValue = depthStencil != null;
|
||||
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
||||
|
||||
if (_rtDepthStencil != depthStencil)
|
||||
{
|
||||
if (_rtDsBound)
|
||||
Texture oldDepthStencil = _rtDepthStencil;
|
||||
|
||||
if (oldDepthStencil != null)
|
||||
{
|
||||
_rtDepthStencil?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtDsBound = true;
|
||||
if (_rtDsBound.HasFlag(BindState.Bound))
|
||||
{
|
||||
oldDepthStencil.SignalModifying(false);
|
||||
}
|
||||
|
||||
oldDepthStencil.SignalBindingChange(false);
|
||||
}
|
||||
|
||||
_rtDsBound = modified ? BindState.BoundModified : BindState.None;
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
depthStencil.SynchronizeMemory();
|
||||
depthStencil.SignalModifying(true);
|
||||
|
||||
if (modified)
|
||||
{
|
||||
depthStencil.SignalModifying(true);
|
||||
}
|
||||
|
||||
depthStencil.SignalBindingChange(true);
|
||||
}
|
||||
|
||||
_rtDepthStencil = depthStencil;
|
||||
@@ -443,10 +494,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
hostDsTexture = dsTexture.HostTexture;
|
||||
|
||||
if (!_rtDsBound)
|
||||
if (_rtDsBound == BindState.Modified)
|
||||
{
|
||||
dsTexture.SignalModifying(true);
|
||||
_rtDsBound = true;
|
||||
_rtDsBound |= BindState.Bound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,10 +521,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
hostTexture = texture.HostTexture;
|
||||
|
||||
if (!_rtColorsBound[index])
|
||||
if (_rtColorsBound[index] == BindState.Modified)
|
||||
{
|
||||
texture.SignalModifying(true);
|
||||
_rtColorsBound[index] = true;
|
||||
_rtColorsBound[index] |= BindState.Bound;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,24 +569,37 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
Texture dsTexture = _rtDepthStencil;
|
||||
|
||||
if (dsTexture != null && _rtDsBound)
|
||||
if (dsTexture != null && _rtDsBound.HasFlag(BindState.Bound))
|
||||
{
|
||||
dsTexture.SignalModifying(false);
|
||||
_rtDsBound = false;
|
||||
_rtDsBound &= ~BindState.Bound;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _rtColors.Length; index++)
|
||||
{
|
||||
Texture texture = _rtColors[index];
|
||||
|
||||
if (texture != null && _rtColorsBound[index])
|
||||
if (texture != null && _rtColorsBound[index].HasFlag(BindState.Bound))
|
||||
{
|
||||
texture.SignalModifying(false);
|
||||
_rtColorsBound[index] = false;
|
||||
_rtColorsBound[index] &= ~BindState.Bound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the currently bound render targets might be modified, if they are used on the next render operation.
|
||||
/// </summary>
|
||||
public void SignalRenderTargetsModifiable()
|
||||
{
|
||||
_rtDsBound |= BindState.Modified;
|
||||
|
||||
for (int index = 0; index < _rtColorsBound.Length; index++)
|
||||
{
|
||||
_rtColorsBound[index] |= BindState.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
|
||||
/// </summary>
|
||||
@@ -572,19 +636,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
for (int i = 0; i < _rtColors.Length; i++)
|
||||
{
|
||||
if (_rtColorsBound[i])
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
_rtColors[i] = null;
|
||||
}
|
||||
|
||||
if (_rtDsBound)
|
||||
{
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
_rtDepthStencil = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,38 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the offset used for accessing buffer textures that are not aligned to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage where the texture is used</param>
|
||||
/// <param name="bindingIndex">Index of the texture binding in the shader</param>
|
||||
/// <param name="offset">Offset for the misaligned part of the address</param>
|
||||
public void UpdateBufferTextureOffset(int stageIndex, int bindingIndex, int offset)
|
||||
{
|
||||
if (_data.BufferTextureOffset[stageIndex][bindingIndex].X != offset)
|
||||
{
|
||||
_data.BufferTextureOffset[stageIndex][bindingIndex].X = offset;
|
||||
int index = stageIndex * SupportBuffer.TextureCount + bindingIndex;
|
||||
MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4, sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the offset used for accessing buffer images that are not aligned to the host requirements.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage where the image is used</param>
|
||||
/// <param name="bindingIndex">Index of the image binding in the shader</param>
|
||||
/// <param name="offset">Offset for the misaligned part of the address</param>
|
||||
public void UpdateBufferImageOffset(int stageIndex, int bindingIndex, int offset)
|
||||
{
|
||||
if (_data.BufferTextureOffset[stageIndex][bindingIndex].Y != offset)
|
||||
{
|
||||
_data.BufferTextureOffset[stageIndex][bindingIndex].Y = offset;
|
||||
int index = stageIndex * SupportBuffer.TextureCount + bindingIndex;
|
||||
MarkDirty(SupportBuffer.BufferTextureOffsetOffset + index * sizeof(int) * 4 + sizeof(int), sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the format of a given render target is a BGRA format.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 7354;
|
||||
private const uint CodeGenVersion = 6371;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@@ -170,6 +170,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// </summary>
|
||||
public bool UsesRtLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the fragment shader always discards the fragment, not producing any output for the bound render targets.
|
||||
/// </summary>
|
||||
public bool HasUnconditionalDiscard;
|
||||
|
||||
/// <summary>
|
||||
/// Bit mask with the clip distances written on the vertex stage.
|
||||
/// </summary>
|
||||
@@ -806,6 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesDrawParameters,
|
||||
dataInfo.UsesRtLayer,
|
||||
dataInfo.HasUnconditionalDiscard,
|
||||
dataInfo.ClipDistancesWritten,
|
||||
dataInfo.FragmentOutputMap);
|
||||
}
|
||||
@@ -836,6 +842,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
UsesInstanceId = info.UsesInstanceId,
|
||||
UsesDrawParameters = info.UsesDrawParameters,
|
||||
UsesRtLayer = info.UsesRtLayer,
|
||||
HasUnconditionalDiscard = info.HasUnconditionalDiscard,
|
||||
ClipDistancesWritten = info.ClipDistancesWritten,
|
||||
FragmentOutputMap = info.FragmentOutputMap,
|
||||
};
|
||||
|
||||
@@ -207,6 +207,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat;
|
||||
|
||||
public bool QueryHostSupportsBufferImagePixelAlignment() => _context.Capabilities.SupportsImageBufferPixelAlignment;
|
||||
|
||||
public bool QueryHostSupportsBufferTexturePixelAlignment() => _context.Capabilities.SupportsTextureBufferPixelAlignment;
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock;
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||
|
||||
@@ -430,7 +430,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage;
|
||||
|
||||
program = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
(program, ShaderProgramInfo vacInfo) = lastInVertexPipeline.GenerateVertexPassthroughForCompute();
|
||||
infoBuilder.AddStageInfoVac(vacInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -535,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled)
|
||||
{
|
||||
ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled);
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, context.GetVertexAsComputeInfo(), tfEnabled);
|
||||
|
||||
return new(_context.Renderer.CreateProgram([source], info), program.Info, context.GetResourceReservations());
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
|
||||
{
|
||||
AddDescriptor(stages, type, setIndex, start, count);
|
||||
AddUsage(stages, type, setIndex, start, count, write);
|
||||
// AddUsage(stages, type, setIndex, start, count, write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -159,6 +159,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
public void AddStageInfoVac(ShaderProgramInfo info)
|
||||
{
|
||||
ResourceStages stages = info.Stage switch
|
||||
{
|
||||
ShaderStage.Compute => ResourceStages.Compute,
|
||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
|
||||
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
|
||||
ShaderStage.Geometry => ResourceStages.Geometry,
|
||||
ShaderStage.Fragment => ResourceStages.Fragment,
|
||||
_ => ResourceStages.None,
|
||||
};
|
||||
|
||||
AddUsage(info.CBuffers, stages, isStorage: false);
|
||||
AddUsage(info.SBuffers, stages, isStorage: true);
|
||||
AddUsage(info.Textures, stages, isImage: false);
|
||||
AddUsage(info.Images, stages, isImage: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource descriptor to the list of descriptors.
|
||||
/// </summary>
|
||||
@@ -422,10 +441,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param>
|
||||
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false)
|
||||
public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, ShaderProgramInfo info2, bool tfEnabled, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true);
|
||||
|
||||
builder.AddStageInfoVac(info2);
|
||||
builder.AddStageInfo(info, vertexAsCompute: true);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsGeometryShader: true,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsTransformFeedback: true,
|
||||
supportsImageBufferPixelAlignment: false,
|
||||
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
||||
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||
supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat,
|
||||
@@ -185,6 +186,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||
supportsShaderFloat64: true,
|
||||
supportsShaderNonUniformIndexing: false,
|
||||
supportsTextureBufferPixelAlignment: false,
|
||||
supportsTextureGatherOffsets: true,
|
||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||
supportsVertexStoreAndAtomics: true,
|
||||
|
||||
@@ -522,6 +522,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
||||
|
||||
enum PMode
|
||||
{
|
||||
Idx = 0,
|
||||
F4e = 1,
|
||||
B4e = 2,
|
||||
Rc8 = 3,
|
||||
|
||||
@@ -234,6 +234,24 @@ namespace Ryujinx.Graphics.Shader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for buffer image access with the buffer offset aligned to a single pixel rather than a fixed alignment.
|
||||
/// </summary>
|
||||
/// <returns>True if the host supports buffer image access with pixel alignment, false otherwise</returns>
|
||||
bool QueryHostSupportsBufferImagePixelAlignment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for buffer texture access with the buffer offset aligned to a single pixel rather than a fixed alignment.
|
||||
/// </summary>
|
||||
/// <returns>True if the host supports buffer texture access with pixel alignment, false otherwise</returns>
|
||||
bool QueryHostSupportsBufferTexturePixelAlignment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering critical sections on the shader code.
|
||||
/// </summary>
|
||||
|
||||
@@ -215,34 +215,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pret is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtR(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtR>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtI(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtI>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtC(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtC>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtRc(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtRc>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
|
||||
}
|
||||
|
||||
public static void R2b(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstR2b>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Graphics.Shader.Decoders;
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
using System;
|
||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
@@ -37,6 +37,38 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(GetDest(op.Dest), GetSrcImm(context, op.Imm32));
|
||||
}
|
||||
|
||||
public static void PrmtR(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtR>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtI(EmitterContext context)
|
||||
{
|
||||
InstPrmtI op = context.GetOp<InstPrmtI>();
|
||||
|
||||
Operand op1 = GetSrcReg(context, op.SrcA);
|
||||
Operand control = GetSrcImm(context, op.Imm20);
|
||||
Operand op2 = GetSrcReg(context, op.SrcC);
|
||||
|
||||
EmitPrmt(context, op1, control, op2, op.PMode, op.Dest);
|
||||
}
|
||||
|
||||
public static void PrmtC(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtC>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
|
||||
}
|
||||
|
||||
public static void PrmtRc(EmitterContext context)
|
||||
{
|
||||
context.GetOp<InstPrmtRc>();
|
||||
|
||||
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
|
||||
}
|
||||
|
||||
public static void R2pR(EmitterContext context)
|
||||
{
|
||||
InstR2pR op = context.GetOp<InstR2pR>();
|
||||
@@ -169,6 +201,39 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(GetDest(op.Dest), src);
|
||||
}
|
||||
|
||||
public static void SelR(EmitterContext context)
|
||||
{
|
||||
InstSelR op = context.GetOp<InstSelR>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcReg(context, op.SrcB);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelI(EmitterContext context)
|
||||
{
|
||||
InstSelI op = context.GetOp<InstSelI>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelC(EmitterContext context)
|
||||
{
|
||||
InstSelC op = context.GetOp<InstSelC>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
private static Operand EmitLoadSubgroupLaneId(EmitterContext context)
|
||||
{
|
||||
if (context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize() <= 32)
|
||||
@@ -215,37 +280,34 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void SelR(EmitterContext context)
|
||||
private static void EmitPrmt(EmitterContext context, Operand op1, Operand control, Operand op2, PMode pMode, int rd)
|
||||
{
|
||||
InstSelR op = context.GetOp<InstSelR>();
|
||||
if (pMode == PMode.Idx)
|
||||
{
|
||||
Operand res = Const(0);
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcReg(context, op.SrcB);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
Operand sel = context.ShiftRightU32(control, Const(b * 4));
|
||||
Operand byteSel = context.BitwiseAnd(sel, Const(7));
|
||||
Operand copySign = context.BitwiseAnd(sel, Const(8));
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
Operand srcOp = context.ConditionalSelect(context.BitwiseAnd(byteSel, Const(4)), op2, op1);
|
||||
Operand srcValue = context.ShiftRightU32(srcOp, context.ShiftLeft(context.BitwiseAnd(byteSel, Const(3)), Const(3)));
|
||||
Operand srcSign = context.ShiftRightS32(context.ShiftLeft(srcValue, Const(24)), Const(31));
|
||||
|
||||
public static void SelI(EmitterContext context)
|
||||
{
|
||||
InstSelI op = context.GetOp<InstSelI>();
|
||||
srcValue = context.ConditionalSelect(copySign, srcSign, srcValue);
|
||||
srcValue = context.BitwiseAnd(srcValue, Const(0xff));
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20));
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
res = context.BitfieldInsert(res, srcValue, Const(b * 8), Const(8));
|
||||
}
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
}
|
||||
|
||||
public static void SelC(EmitterContext context)
|
||||
{
|
||||
InstSelC op = context.GetOp<InstSelC>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||
Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv);
|
||||
|
||||
EmitSel(context, srcA, srcB, srcPred, op.Dest);
|
||||
context.Copy(GetDest(rd), res);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(pMode.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitR2p(EmitterContext context, Operand value, Operand mask, ByteSel byteSel, bool ccpr)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
set => _branch = AddSuccessor(_branch, value);
|
||||
}
|
||||
|
||||
public bool HasSuccessor => _branch != null || _next != null;
|
||||
public bool HasBranch => _branch != null;
|
||||
public bool Reachable => Index == 0 || Predecessors.Count != 0;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
public bool UsesInstanceId { get; }
|
||||
public bool UsesDrawParameters { get; }
|
||||
public bool UsesRtLayer { get; }
|
||||
public bool HasUnconditionalDiscard { get; }
|
||||
public byte ClipDistancesWritten { get; }
|
||||
public int FragmentOutputMap { get; }
|
||||
|
||||
@@ -34,6 +35,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
bool usesInstanceId,
|
||||
bool usesDrawParameters,
|
||||
bool usesRtLayer,
|
||||
bool hasUnconditionalDiscard,
|
||||
byte clipDistancesWritten,
|
||||
int fragmentOutputMap)
|
||||
{
|
||||
@@ -50,6 +52,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
UsesInstanceId = usesInstanceId;
|
||||
UsesDrawParameters = usesDrawParameters;
|
||||
UsesRtLayer = usesRtLayer;
|
||||
HasUnconditionalDiscard = hasUnconditionalDiscard;
|
||||
ClipDistancesWritten = clipDistancesWritten;
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
RenderScale,
|
||||
TfeOffset,
|
||||
TfeVertexCount,
|
||||
BufferTextureOffset,
|
||||
}
|
||||
|
||||
public struct SupportBuffer
|
||||
@@ -42,10 +43,13 @@ namespace Ryujinx.Graphics.Shader
|
||||
public static readonly int ComputeRenderScaleOffset;
|
||||
public static readonly int TfeOffsetOffset;
|
||||
public static readonly int TfeVertexCountOffset;
|
||||
public static readonly int BufferTextureOffsetOffset;
|
||||
|
||||
public const int FragmentIsBgraCount = 8;
|
||||
public const int TextureCount = 64;
|
||||
|
||||
// One for the render target, 64 for the textures, and 8 for the images.
|
||||
public const int RenderScaleMaxCount = 1 + 64 + 8;
|
||||
public const int RenderScaleMaxCount = 1 + TextureCount + 8;
|
||||
|
||||
private static int OffsetOf<T>(ref SupportBuffer storage, ref T target)
|
||||
{
|
||||
@@ -68,6 +72,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||
TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset);
|
||||
TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount);
|
||||
BufferTextureOffsetOffset = OffsetOf(ref instance, ref instance.BufferTextureOffset);
|
||||
}
|
||||
|
||||
internal static StructureType GetStructureType()
|
||||
@@ -80,7 +85,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
new StructureField(AggregateType.S32, "frag_scale_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count")
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.Vector2 | AggregateType.S32, "buffer_texture_offset")
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -95,5 +101,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public Vector4<int> TfeOffset;
|
||||
public Vector4<int> TfeVertexCount;
|
||||
|
||||
public Array5<Array64<Vector4<int>>> BufferTextureOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
SharedMemory = 1 << 11,
|
||||
Store = 1 << 12,
|
||||
VtgAsCompute = 1 << 13,
|
||||
UnconditionalDiscard = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class FeatureIdentification
|
||||
{
|
||||
public static void RunPass(BasicBlock[] blocks, ShaderStage stage, ref FeatureFlags usedFeatures)
|
||||
{
|
||||
if (stage == ShaderStage.Fragment)
|
||||
{
|
||||
bool endsWithDiscardOnly = true;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < blocks.Length; blockIndex++)
|
||||
{
|
||||
BasicBlock block = blocks[blockIndex];
|
||||
|
||||
if (block.HasSuccessor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block.Operations.Count == 0 ||
|
||||
block.Operations.Last.Value is not Operation operation ||
|
||||
operation.Inst != Instruction.Discard)
|
||||
{
|
||||
endsWithDiscardOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endsWithDiscardOnly)
|
||||
{
|
||||
usedFeatures |= FeatureFlags.UnconditionalDiscard;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
||||
|
||||
private readonly List<BufferDefinition> _vacConstantBuffers;
|
||||
private readonly List<BufferDefinition> _vacStorageBuffers;
|
||||
private readonly List<TextureDefinition> _vacTextures;
|
||||
private readonly List<TextureDefinition> _vacImages;
|
||||
|
||||
public int LocalMemoryId { get; private set; }
|
||||
public int SharedMemoryId { get; private set; }
|
||||
|
||||
@@ -78,6 +83,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_usedTextures = new();
|
||||
_usedImages = new();
|
||||
|
||||
_vacConstantBuffers = new();
|
||||
_vacStorageBuffers = new();
|
||||
_vacTextures = new();
|
||||
_vacImages = new();
|
||||
|
||||
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType()));
|
||||
|
||||
LocalMemoryId = -1;
|
||||
@@ -563,6 +573,76 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return descriptors.ToArray();
|
||||
}
|
||||
|
||||
public ShaderProgramInfo GetVertexAsComputeInfo(bool isVertex = false)
|
||||
{
|
||||
var cbDescriptors = new BufferDescriptor[_vacConstantBuffers.Count];
|
||||
int cbDescriptorIndex = 0;
|
||||
|
||||
foreach (BufferDefinition definition in _vacConstantBuffers)
|
||||
{
|
||||
cbDescriptors[cbDescriptorIndex++] = new BufferDescriptor(definition.Set, definition.Binding, 0, 0, 0, BufferUsageFlags.None);
|
||||
}
|
||||
|
||||
var sbDescriptors = new BufferDescriptor[_vacStorageBuffers.Count];
|
||||
int sbDescriptorIndex = 0;
|
||||
|
||||
foreach (BufferDefinition definition in _vacStorageBuffers)
|
||||
{
|
||||
sbDescriptors[sbDescriptorIndex++] = new BufferDescriptor(definition.Set, definition.Binding, 0, 0, 0, BufferUsageFlags.Write);
|
||||
}
|
||||
|
||||
var tDescriptors = new TextureDescriptor[_vacTextures.Count];
|
||||
int tDescriptorIndex = 0;
|
||||
|
||||
foreach (TextureDefinition definition in _vacTextures)
|
||||
{
|
||||
tDescriptors[tDescriptorIndex++] = new TextureDescriptor(
|
||||
definition.Set,
|
||||
definition.Binding,
|
||||
definition.Type,
|
||||
definition.Format,
|
||||
0,
|
||||
0,
|
||||
definition.ArrayLength,
|
||||
definition.Separate,
|
||||
definition.Flags);
|
||||
}
|
||||
|
||||
var iDescriptors = new TextureDescriptor[_vacImages.Count];
|
||||
int iDescriptorIndex = 0;
|
||||
|
||||
foreach (TextureDefinition definition in _vacImages)
|
||||
{
|
||||
iDescriptors[iDescriptorIndex++] = new TextureDescriptor(
|
||||
definition.Set,
|
||||
definition.Binding,
|
||||
definition.Type,
|
||||
definition.Format,
|
||||
0,
|
||||
0,
|
||||
definition.ArrayLength,
|
||||
definition.Separate,
|
||||
definition.Flags);
|
||||
}
|
||||
|
||||
return new ShaderProgramInfo(
|
||||
cbDescriptors,
|
||||
sbDescriptors,
|
||||
tDescriptors,
|
||||
iDescriptors,
|
||||
isVertex ? ShaderStage.Vertex : ShaderStage.Compute,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle)
|
||||
{
|
||||
foreach ((TextureInfo info, TextureMeta meta) in _usedTextures)
|
||||
@@ -627,6 +707,30 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type));
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeConstantBuffer(BufferDefinition definition)
|
||||
{
|
||||
_vacConstantBuffers.Add(definition);
|
||||
Properties.AddOrUpdateConstantBuffer(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeStorageBuffer(BufferDefinition definition)
|
||||
{
|
||||
_vacStorageBuffers.Add(definition);
|
||||
Properties.AddOrUpdateStorageBuffer(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeTexture(TextureDefinition definition)
|
||||
{
|
||||
_vacTextures.Add(definition);
|
||||
Properties.AddOrUpdateTexture(definition);
|
||||
}
|
||||
|
||||
public void AddVertexAsComputeImage(TextureDefinition definition)
|
||||
{
|
||||
_vacImages.Add(definition);
|
||||
Properties.AddOrUpdateImage(definition);
|
||||
}
|
||||
|
||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||
{
|
||||
uint index = (uint)stage;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
@@ -27,7 +28,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||
|
||||
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
||||
{
|
||||
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
|
||||
if (!context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
|
||||
{
|
||||
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
|
||||
}
|
||||
|
||||
if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment())
|
||||
{
|
||||
node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: false);
|
||||
}
|
||||
}
|
||||
else if (!context.GpuAccessor.QueryHostSupportsBufferTexturePixelAlignment() && texOp.Inst.IsImage())
|
||||
{
|
||||
node = InsertCoordOffset(node, context.ResourceManager, context.Stage, isImage: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,6 +291,87 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertCoordOffset(LinkedListNode<INode> node, ResourceManager resourceManager, ShaderStage stage, bool isImage)
|
||||
{
|
||||
// Some GPUs have fixed alignment requirements for buffer textures.
|
||||
// For those cases, we bind the aligned buffer offset, and apply the remaining offset on the shader.
|
||||
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
if ((texOp.Type & SamplerType.Mask) != SamplerType.TextureBuffer)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[texOp.SourcesCount];
|
||||
|
||||
for (int i = 0; i < texOp.SourcesCount; i++)
|
||||
{
|
||||
sources[i] = texOp.GetSource(i);
|
||||
}
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage);
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
Operand[] dests = new Operand[texOp.DestsCount];
|
||||
|
||||
for (int i = 0; i < texOp.DestsCount; i++)
|
||||
{
|
||||
dests[i] = texOp.GetDest(i);
|
||||
}
|
||||
|
||||
LinkedListNode<INode> oldNode = node;
|
||||
|
||||
Operand source = sources[coordsIndex];
|
||||
Operand offset = Local();
|
||||
Operand coordPlusOffset = Local();
|
||||
|
||||
int stageIndex = stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
ShaderStage.TessellationEvaluation => 2,
|
||||
ShaderStage.Geometry => 3,
|
||||
ShaderStage.Fragment => 4,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
int bindingIndex = isImage
|
||||
? resourceManager.FindImageDescriptorIndex(texOp.Binding)
|
||||
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
|
||||
|
||||
node.List.AddBefore(node, new Operation(
|
||||
Instruction.Load,
|
||||
StorageKind.ConstantBuffer,
|
||||
offset,
|
||||
Const(SupportBuffer.Binding),
|
||||
Const((int)SupportBufferField.BufferTextureOffset),
|
||||
Const(stageIndex * SupportBuffer.TextureCount + bindingIndex),
|
||||
Const(isImage ? 1 : 0)));
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offset));
|
||||
|
||||
sources[coordsIndex] = coordPlusOffset;
|
||||
|
||||
TextureOperation newTexOp = new(
|
||||
texOp.Inst,
|
||||
texOp.Type,
|
||||
texOp.Format,
|
||||
texOp.Flags,
|
||||
texOp.Set,
|
||||
texOp.Binding,
|
||||
texOp.Index,
|
||||
dests,
|
||||
sources);
|
||||
|
||||
node = node.List.AddBefore(node, newTexOp);
|
||||
|
||||
Utils.DeleteNode(oldNode, texOp);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage)
|
||||
{
|
||||
// Non-constant texture offsets are not allowed (according to the spec),
|
||||
|
||||
@@ -301,6 +301,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
Optimizer.RunPass(context);
|
||||
TransformPasses.RunPass(context);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
FeatureIdentification.RunPass(cfg.Blocks, Definitions.Stage, ref usedFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||
@@ -353,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
usedFeatures.HasFlag(FeatureFlags.InstanceId),
|
||||
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
|
||||
usedFeatures.HasFlag(FeatureFlags.RtLayer),
|
||||
usedFeatures.HasFlag(FeatureFlags.UnconditionalDiscard),
|
||||
clipDistancesWritten,
|
||||
originalDefinitions.OmapTargets);
|
||||
|
||||
@@ -393,7 +399,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
int binding = resourceManager.Reservations.GetTfeBufferStorageBufferBinding(i);
|
||||
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(tfeDataBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +407,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding;
|
||||
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||
resourceManager.AddVertexAsComputeConstantBuffer(vertexInfoBuffer);
|
||||
|
||||
StructureType vertexOutputStruct = new([
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0)
|
||||
@@ -409,13 +415,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int vertexOutputSbBinding = resourceManager.Reservations.VertexOutputStorageBufferBinding;
|
||||
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexOutputSbBinding, "vertex_output", vertexOutputStruct);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(vertexOutputBuffer);
|
||||
|
||||
if (Stage == ShaderStage.Vertex)
|
||||
{
|
||||
SetBindingPair ibSetAndBinding = resourceManager.Reservations.GetIndexBufferTextureSetAndBinding();
|
||||
TextureDefinition indexBuffer = new(ibSetAndBinding.SetIndex, ibSetAndBinding.Binding, "ib_data", SamplerType.TextureBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(indexBuffer);
|
||||
|
||||
int inputMap = _program.AttributeUsage.UsedInputAttributes;
|
||||
|
||||
@@ -424,7 +430,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
int location = BitOperations.TrailingZeroCount(inputMap);
|
||||
SetBindingPair setAndBinding = resourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
|
||||
TextureDefinition vaBuffer = new(setAndBinding.SetIndex, setAndBinding.Binding, $"vb_data{location}", SamplerType.TextureBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(vaBuffer);
|
||||
|
||||
inputMap &= ~(1 << location);
|
||||
}
|
||||
@@ -433,11 +439,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
SetBindingPair trbSetAndBinding = resourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding();
|
||||
TextureDefinition remapBuffer = new(trbSetAndBinding.SetIndex, trbSetAndBinding.Binding, "trb_data", SamplerType.TextureBuffer);
|
||||
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
|
||||
resourceManager.AddVertexAsComputeTexture(remapBuffer);
|
||||
|
||||
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
|
||||
BufferDefinition geometryVbOutputBuffer = new(BufferLayout.Std430, 1, geometryVbOutputSbBinding, "geometry_vb_output", vertexOutputStruct);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryVbOutputBuffer);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(geometryVbOutputBuffer);
|
||||
|
||||
StructureType geometryIbOutputStruct = new([
|
||||
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
||||
@@ -445,7 +451,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int geometryIbOutputSbBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding;
|
||||
BufferDefinition geometryIbOutputBuffer = new(BufferLayout.Std430, 1, geometryIbOutputSbBinding, "geometry_ib_output", geometryIbOutputStruct);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(geometryIbOutputBuffer);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(geometryIbOutputBuffer);
|
||||
}
|
||||
|
||||
resourceManager.SetVertexAsComputeLocalMemories(Definitions.Stage, Definitions.InputTopology);
|
||||
@@ -478,12 +484,17 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage);
|
||||
}
|
||||
|
||||
public ShaderProgramInfo GetVertexAsComputeInfo()
|
||||
{
|
||||
return CreateResourceManager(true).GetVertexAsComputeInfo();
|
||||
}
|
||||
|
||||
public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext)
|
||||
{
|
||||
_vertexOutput = vertexContext._program.GetIoUsage();
|
||||
}
|
||||
|
||||
public ShaderProgram GenerateVertexPassthroughForCompute()
|
||||
public (ShaderProgram, ShaderProgramInfo) GenerateVertexPassthroughForCompute()
|
||||
{
|
||||
AttributeUsage attributeUsage = new(GpuAccessor);
|
||||
ResourceManager resourceManager = new(ShaderStage.Vertex, GpuAccessor);
|
||||
@@ -495,7 +506,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
if (Stage == ShaderStage.Vertex)
|
||||
{
|
||||
BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType());
|
||||
resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer);
|
||||
resourceManager.AddVertexAsComputeConstantBuffer(vertexInfoBuffer);
|
||||
}
|
||||
|
||||
StructureType vertexInputStruct = new([
|
||||
@@ -504,7 +515,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int vertexDataSbBinding = reservations.VertexOutputStorageBufferBinding;
|
||||
BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexDataSbBinding, "vb_input", vertexInputStruct);
|
||||
resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer);
|
||||
resourceManager.AddVertexAsComputeStorageBuffer(vertexOutputBuffer);
|
||||
|
||||
EmitterContext context = new();
|
||||
|
||||
@@ -562,14 +573,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
LastInVertexPipeline = true
|
||||
};
|
||||
|
||||
return Generate(
|
||||
return (Generate(
|
||||
[function],
|
||||
attributeUsage,
|
||||
definitions,
|
||||
definitions,
|
||||
resourceManager,
|
||||
FeatureFlags.None,
|
||||
0);
|
||||
0), resourceManager.GetVertexAsComputeInfo(isVertex: true));
|
||||
}
|
||||
|
||||
public ShaderProgram GenerateGeometryPassthrough()
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
queueLock,
|
||||
_gd.QueueFamilyIndex,
|
||||
_gd.IsQualcommProprietary,
|
||||
_gd.IsTurnip,
|
||||
isLight: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Queue _queue;
|
||||
private readonly Lock _queueLock;
|
||||
private readonly bool _concurrentFenceWaitUnsupported;
|
||||
private readonly bool _fenceAlwaysWaits;
|
||||
private readonly CommandPool _pool;
|
||||
private readonly Thread _owner;
|
||||
|
||||
@@ -66,6 +67,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Lock queueLock,
|
||||
uint queueFamilyIndex,
|
||||
bool concurrentFenceWaitUnsupported,
|
||||
bool fenceAlwaysWaits,
|
||||
bool isLight = false)
|
||||
{
|
||||
_api = api;
|
||||
@@ -73,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_queue = queue;
|
||||
_queueLock = queueLock;
|
||||
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||
_fenceAlwaysWaits = fenceAlwaysWaits;
|
||||
_owner = Thread.CurrentThread;
|
||||
|
||||
CommandPoolCreateInfo commandPoolCreateInfo = new()
|
||||
@@ -207,7 +210,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
ref ReservedCommandBuffer entry = ref _commandBuffers[index];
|
||||
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
|
||||
if (wait || !entry.InConsumption || entry.Fence.IsSignaledLazy())
|
||||
{
|
||||
WaitAndDecrementRef(index);
|
||||
|
||||
@@ -349,7 +352,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (refreshFence)
|
||||
{
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported, _fenceAlwaysWaits);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -12,13 +12,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _referenceCount;
|
||||
private int _lock;
|
||||
private readonly bool _concurrentWaitUnsupported;
|
||||
private readonly bool _alwaysWaits;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported, bool alwaysWaits)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||
_alwaysWaits = alwaysWaits;
|
||||
|
||||
FenceCreateInfo fenceCreateInfo = new()
|
||||
{
|
||||
@@ -123,6 +125,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSignaledLazy()
|
||||
{
|
||||
if (_alwaysWaits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsSignaled();
|
||||
}
|
||||
|
||||
public bool IsSignaled()
|
||||
{
|
||||
if (_concurrentWaitUnsupported)
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private ProgramPipelineState _state;
|
||||
private DisposableRenderPass _dummyRenderPass;
|
||||
private readonly Task _compileTask;
|
||||
private ShaderCompilationRequest _compileRequest;
|
||||
private bool _firstBackgroundUse;
|
||||
|
||||
public ShaderCollection(
|
||||
@@ -139,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||
|
||||
_compileTask = Task.CompletedTask;
|
||||
_compileRequest = new ShaderCompilationRequest(Task.CompletedTask);
|
||||
_firstBackgroundUse = false;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_state = state;
|
||||
|
||||
_compileTask = BackgroundCompilation();
|
||||
_compileRequest = gd.ShaderCompilationQueue != null
|
||||
? gd.ShaderCompilationQueue.Add(BackgroundCompilation)
|
||||
: new ShaderCompilationRequest(BackgroundCompilationAsync());
|
||||
_firstBackgroundUse = !fromCache;
|
||||
}
|
||||
|
||||
@@ -458,10 +460,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return (buffer, texture);
|
||||
}
|
||||
|
||||
private async Task BackgroundCompilation()
|
||||
private async Task BackgroundCompilationAsync()
|
||||
{
|
||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||
|
||||
BackgroundCompilationImpl();
|
||||
}
|
||||
|
||||
private void BackgroundCompilation()
|
||||
{
|
||||
foreach (var shader in _shaders)
|
||||
{
|
||||
shader.CompileTask.Wait();
|
||||
}
|
||||
|
||||
BackgroundCompilationImpl();
|
||||
}
|
||||
|
||||
private void BackgroundCompilationImpl()
|
||||
{
|
||||
if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure))
|
||||
{
|
||||
LinkStatus = ProgramLinkStatus.Failure;
|
||||
@@ -604,11 +621,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
if (!_compileTask.IsCompleted)
|
||||
if (!_compileRequest.IsCompleted)
|
||||
{
|
||||
if (blocking)
|
||||
{
|
||||
_compileTask.Wait();
|
||||
_compileRequest.Wait();
|
||||
|
||||
if (LinkStatus == ProgramLinkStatus.Failure)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class ShaderCompilationQueue
|
||||
{
|
||||
private const int MaxParallelCompilations = 8;
|
||||
private const int MaxThreadStackSize = 2 * 1024 * 1024; // MB
|
||||
|
||||
private struct Request
|
||||
{
|
||||
public readonly ulong Id;
|
||||
public readonly Action Callback;
|
||||
|
||||
public Request(ulong id, Action callback)
|
||||
{
|
||||
Id = id;
|
||||
Callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Thread[] _workerThreads;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private readonly BlockingCollection<Request>[] _queues;
|
||||
private readonly ulong[] _finishedIds;
|
||||
private ulong _currentId;
|
||||
private int _currentQueueIndex;
|
||||
|
||||
public ShaderCompilationQueue()
|
||||
{
|
||||
_workerThreads = new Thread[MaxParallelCompilations];
|
||||
_queues = new BlockingCollection<Request>[MaxParallelCompilations];
|
||||
_finishedIds = new ulong[MaxParallelCompilations];
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_queues[i] = new BlockingCollection<Request>();
|
||||
|
||||
Thread thread = new Thread(DoWork, MaxThreadStackSize) { Name = $"BackgroundShaderCompiler.{i}" };
|
||||
thread.IsBackground = true;
|
||||
thread.Start(i);
|
||||
|
||||
_workerThreads[i] = thread;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWork(object threadId)
|
||||
{
|
||||
int queueIndex = (int)threadId;
|
||||
|
||||
try
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
foreach (var request in queue.GetConsumingEnumerable(_cts.Token))
|
||||
{
|
||||
request.Callback();
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
_finishedIds[queueIndex] = request.Id;
|
||||
|
||||
Monitor.PulseAll(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest Add(Action callback)
|
||||
{
|
||||
ulong newId = Interlocked.Increment(ref _currentId);
|
||||
|
||||
// Let's keep rotating between the queues to increase the chances
|
||||
// that the selected queue thread is currently idle.
|
||||
int queueIndex = Interlocked.Increment(ref _currentQueueIndex) % MaxParallelCompilations;
|
||||
|
||||
_queues[queueIndex].Add(new Request(newId, callback));
|
||||
|
||||
return new ShaderCompilationRequest(this, queueIndex, newId);
|
||||
}
|
||||
|
||||
public void Wait(int queueIndex, ulong id)
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
while (_finishedIds[queueIndex] < id)
|
||||
{
|
||||
Monitor.Wait(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted(int queueIndex, ulong id)
|
||||
{
|
||||
var queue = _queues[queueIndex];
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
return _finishedIds[queueIndex] >= id;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_queues[i].CompleteAdding();
|
||||
}
|
||||
|
||||
_cts.Cancel();
|
||||
|
||||
for (int i = 0; i < MaxParallelCompilations; i++)
|
||||
{
|
||||
_workerThreads[i].Join();
|
||||
|
||||
_queues[i].Dispose();
|
||||
}
|
||||
|
||||
_cts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
struct ShaderCompilationRequest
|
||||
{
|
||||
private readonly Task _task;
|
||||
private readonly ShaderCompilationQueue _queue;
|
||||
private readonly int _queueIndex;
|
||||
private readonly ulong _requestId;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
return _task.IsCompleted;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _queue.IsCompleted(_queueIndex, _requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest(Task task)
|
||||
{
|
||||
_task = task;
|
||||
_queue = null;
|
||||
_queueIndex = 0;
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
public ShaderCompilationRequest(ShaderCompilationQueue queue, int queueIndex, ulong requestId)
|
||||
{
|
||||
_task = null;
|
||||
_queue = queue;
|
||||
_queueIndex = queueIndex;
|
||||
_requestId = requestId;
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_task != null)
|
||||
{
|
||||
_task.Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
_queue.Wait(_queueIndex, _requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public void FreeCompleted()
|
||||
{
|
||||
FenceHolder signalledFence = null;
|
||||
while (_pendingCopies.TryPeek(out PendingCopy pc) && pc.Fence != null && (pc.Fence == signalledFence || pc.Fence.IsSignaled()))
|
||||
while (_pendingCopies.TryPeek(out PendingCopy pc) && pc.Fence != null && (pc.Fence == signalledFence || pc.Fence.IsSignaledLazy()))
|
||||
{
|
||||
signalledFence = pc.Fence; // Already checked - don't need to do it again.
|
||||
PendingCopy dequeued = _pendingCopies.Dequeue();
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal CommandBufferPool CommandBufferPool { get; private set; }
|
||||
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
|
||||
internal BackgroundResources BackgroundResources { get; private set; }
|
||||
internal ShaderCompilationQueue ShaderCompilationQueue { get; private set; }
|
||||
internal Action<Action> InterruptAction { get; private set; }
|
||||
internal SyncManager SyncManager { get; private set; }
|
||||
|
||||
@@ -96,6 +97,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsIntelArc { get; private set; }
|
||||
internal bool IsQualcommProprietary { get; private set; }
|
||||
internal bool IsTurnip { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool SupportsMTL31 { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
@@ -127,6 +129,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
|
||||
IsMoltenVk = true;
|
||||
|
||||
// The default thread stack size on MacOS is low, and can cause stack overflow
|
||||
// on SPIR-V Cross during shader compilation.
|
||||
// As a workaround, we use this custom queue which allows us to specify the stack
|
||||
// size of the threads used for compilation.
|
||||
ShaderCompilationQueue = new ShaderCompilationQueue();
|
||||
}
|
||||
|
||||
SupportsMTL31 = OperatingSystem.IsMacOSVersionAtLeast(14);
|
||||
@@ -396,6 +404,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
IsFeedbackLoopDevice = IsAmdRdna3;
|
||||
|
||||
IsTurnip = GpuRenderer.StartsWith("Turnip");
|
||||
|
||||
if (Vendor == Vendor.Nvidia)
|
||||
{
|
||||
Match match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
|
||||
@@ -476,7 +486,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
|
||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary, IsTurnip);
|
||||
|
||||
PipelineLayoutCache = new PipelineLayoutCache();
|
||||
|
||||
@@ -777,6 +787,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsGeometryShader: Capabilities.SupportsGeometryShader,
|
||||
supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsTransformFeedback: Capabilities.SupportsTransformFeedback,
|
||||
supportsImageBufferPixelAlignment: false,
|
||||
supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat,
|
||||
supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer,
|
||||
supportsMismatchingViewFormat: true,
|
||||
@@ -790,6 +801,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
supportsShaderNonUniformIndexing:
|
||||
featuresVk12.ShaderSampledImageArrayNonUniformIndexing &&
|
||||
featuresVk12.ShaderStorageImageArrayNonUniformIndexing,
|
||||
supportsTextureBufferPixelAlignment: false,
|
||||
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended,
|
||||
supportsTextureShadowLod: false,
|
||||
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
|
||||
@@ -1112,6 +1124,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
|
||||
ShaderCompilationQueue?.Dispose();
|
||||
|
||||
Api.DestroyDevice(_device, null);
|
||||
|
||||
_debugMessenger.Dispose();
|
||||
|
||||
@@ -166,13 +166,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
||||
|
||||
string[] buttons = GetButtonsText(module, description, "DlgBtn");
|
||||
|
||||
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
|
||||
(uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
|
||||
|
||||
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
|
||||
if (showDetails)
|
||||
{
|
||||
message = GetMessageText(module, description, "FlvMsg");
|
||||
buttons = GetButtonsText(module, description, "FlvBtn");
|
||||
|
||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
|
||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,19 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
_normalSession = normalSession;
|
||||
_interactiveSession = interactiveSession;
|
||||
|
||||
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
||||
_normalSession.Push(BuildResponse());
|
||||
|
||||
UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog();
|
||||
if (selected == null)
|
||||
{
|
||||
_normalSession.Push(BuildResponse());
|
||||
}
|
||||
else if (selected.UserId == new UserId("00000000000000000000000000000080"))
|
||||
{
|
||||
_normalSession.Push(BuildGuestResponse());
|
||||
}
|
||||
else
|
||||
{
|
||||
_normalSession.Push(BuildResponse(selected));
|
||||
}
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
|
||||
_system.ReturnFocus();
|
||||
@@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private byte[] BuildResponse()
|
||||
private byte[] BuildResponse(UserProfile selectedUser)
|
||||
{
|
||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
||||
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
writer.Write((ulong)PlayerSelectResult.Success);
|
||||
|
||||
currentUser.UserId.Write(writer);
|
||||
selectedUser.UserId.Write(writer);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private byte[] BuildGuestResponse()
|
||||
{
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
writer.Write(new byte());
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private byte[] BuildResponse()
|
||||
{
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
writer.Write((ulong)PlayerSelectResult.Failure);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
@@ -59,6 +59,7 @@
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
||||
<EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
|
||||
<EmbeddedResource Include="HOS\Services\Account\Acc\GuestUserImage.jpg" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||
|
||||
namespace Ryujinx.HLE.UI
|
||||
@@ -48,7 +49,8 @@ namespace Ryujinx.HLE.UI
|
||||
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
|
||||
/// </summary>
|
||||
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
|
||||
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
|
||||
// ReSharper disable once UnusedParameter.Global
|
||||
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a handler to process keyboard inputs into text strings.
|
||||
@@ -65,5 +67,10 @@ namespace Ryujinx.HLE.UI
|
||||
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
|
||||
/// </summary>
|
||||
void TakeScreenshot();
|
||||
|
||||
/// <summary>
|
||||
/// Displays the player select dialog and returns the selected profile.
|
||||
/// </summary>
|
||||
UserProfile ShowPlayerSelectDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Ryujinx.ShaderTools
|
||||
|
||||
if (options.VertexPassthrough)
|
||||
{
|
||||
program = translatorContext.GenerateVertexPassthroughForCompute();
|
||||
(program, _) = translatorContext.GenerateVertexPassthroughForCompute();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using Ryujinx.Graphics.GAL.Multithreading;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.HLE.UI;
|
||||
@@ -28,6 +29,7 @@ using static SDL.SDL3;
|
||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||
using Switch = Ryujinx.HLE.Switch;
|
||||
using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
|
||||
namespace Ryujinx.Headless
|
||||
{
|
||||
@@ -531,7 +533,7 @@ namespace Ryujinx.Headless
|
||||
Exit();
|
||||
}
|
||||
|
||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
|
||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
|
||||
{
|
||||
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
|
||||
|
||||
@@ -590,5 +592,10 @@ namespace Ryujinx.Headless
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public UserProfile ShowPlayerSelectDialog()
|
||||
{
|
||||
return AccountSaveDataManager.GetLastUsedUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,4 +169,10 @@
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="UI\Applet\UserSelectorDialog.axaml.cs">
|
||||
<DependentUpon>UserSelectorDialog.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||
using Ryujinx.HLE.UI;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Applet
|
||||
@@ -215,7 +221,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
_parent.ViewModel.AppHost?.Stop();
|
||||
}
|
||||
|
||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
|
||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons, (uint Module, uint Description)? errorCode = null)
|
||||
{
|
||||
ManualResetEvent dialogCloseEvent = new(false);
|
||||
|
||||
@@ -256,11 +262,61 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
return showDetails;
|
||||
}
|
||||
|
||||
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
|
||||
{
|
||||
return new AvaloniaDynamicTextInputHandler(_parent);
|
||||
}
|
||||
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
|
||||
|
||||
public UserProfile ShowPlayerSelectDialog()
|
||||
{
|
||||
UserId selected = UserId.Null;
|
||||
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
|
||||
UserProfile guest = new UserProfile(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
|
||||
|
||||
ManualResetEvent dialogCloseEvent = new(false);
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
ObservableCollection<BaseModel> profiles = [];
|
||||
NavigationDialogHost nav = new();
|
||||
|
||||
_parent.AccountManager.GetAllUsers()
|
||||
.OrderBy(x => x.Name)
|
||||
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
|
||||
|
||||
profiles.Add(new Models.UserProfile(guest, nav));
|
||||
ProfileSelectorDialogViewModel viewModel = new()
|
||||
{
|
||||
Profiles = profiles,
|
||||
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
|
||||
};
|
||||
(selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
|
||||
|
||||
dialogCloseEvent.Set();
|
||||
});
|
||||
|
||||
dialogCloseEvent.WaitOne();
|
||||
|
||||
UserProfile profile = _parent.AccountManager.LastOpenedUser;
|
||||
if (selected == guest.UserId)
|
||||
{
|
||||
profile = guest;
|
||||
}
|
||||
else if (selected == UserId.Null)
|
||||
{
|
||||
profile = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (UserProfile p in _parent.AccountManager.GetAllUsers())
|
||||
{
|
||||
if (p.UserId == selected)
|
||||
{
|
||||
profile = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
public void TakeScreenshot()
|
||||
{
|
||||
_parent.ViewModel.AppHost.ScreenshotRequested = true;
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Applet.ProfileSelectorDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True"
|
||||
x:DataType="viewModels:ProfileSelectorDialogViewModel">
|
||||
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Design.DataContext>
|
||||
<viewModels:ProfileSelectorDialogViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border
|
||||
CornerRadius="5"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1">
|
||||
|
||||
<ListBox
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectionChanged="ProfilesList_SelectionChanged">
|
||||
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Margin" Value="5 5 0 5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
<Style Selector="Rectangle#SelectionIndicator">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate
|
||||
DataType="models:UserProfile">
|
||||
<Grid
|
||||
PointerEntered="Grid_PointerEntered"
|
||||
PointerExited="Grid_OnPointerExited">
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
Background="{Binding BackgroundColor}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Image
|
||||
Width="96"
|
||||
Height="96"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
MaxWidth="90"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
MaxLines="2"
|
||||
Margin="5" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate
|
||||
DataType="viewModels:BaseModel">
|
||||
<Panel
|
||||
Height="118"
|
||||
Width="96">
|
||||
<Panel.Styles>
|
||||
<Style Selector="Panel">
|
||||
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0 24 0 0"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,125 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Applet
|
||||
{
|
||||
public partial class ProfileSelectorDialog : UserControl
|
||||
{
|
||||
public ProfileSelectorDialogViewModel ViewModel { get; set; }
|
||||
|
||||
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
|
||||
{
|
||||
DataContext = ViewModel = viewModel;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Grid_PointerEntered(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Grid { DataContext: UserProfile profile })
|
||||
{
|
||||
profile.IsPointerOver = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Grid_OnPointerExited(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Grid { DataContext: UserProfile profile })
|
||||
{
|
||||
profile.IsPointerOver = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
int selectedIndex = listBox.SelectedIndex;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||
{
|
||||
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
|
||||
{
|
||||
ViewModel.SelectedUserId = userProfile.UserId;
|
||||
Logger.Info?.Print(LogClass.UI, $"Selected: {userProfile.UserId}", "ProfileSelector");
|
||||
|
||||
ObservableCollection<BaseModel> newProfiles = [];
|
||||
|
||||
foreach (BaseModel item in ViewModel.Profiles)
|
||||
{
|
||||
if (item is UserProfile originalItem)
|
||||
{
|
||||
UserProfileSft profile = new(originalItem.UserId, originalItem.Name, originalItem.Image);
|
||||
|
||||
if (profile.UserId == ViewModel.SelectedUserId)
|
||||
{
|
||||
profile.AccountState = AccountState.Open;
|
||||
}
|
||||
|
||||
newProfiles.Add(new UserProfile(profile, new NavigationDialogHost()));
|
||||
}
|
||||
}
|
||||
|
||||
ViewModel.Profiles = newProfiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
|
||||
{
|
||||
|
||||
if (ConfigurationState.Instance.System.SkipUserProfilesManager)
|
||||
{
|
||||
UserId defaultId = viewModel.SelectedUserId;
|
||||
return (defaultId, true);
|
||||
}
|
||||
|
||||
FAContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
|
||||
SecondaryButtonText = string.Empty,
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
|
||||
Content = new ProfileSelectorDialog(viewModel),
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
UserId result = UserId.Null;
|
||||
bool input = false;
|
||||
|
||||
contentDialog.Closed += Handler;
|
||||
|
||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||
|
||||
return (result, input);
|
||||
|
||||
void Handler(FAContentDialog sender, FAContentDialogClosedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Result == FAContentDialogResult.Primary)
|
||||
{
|
||||
result = viewModel.SelectedUserId;
|
||||
input = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = UserId.Null;
|
||||
input = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class ProfileSelectorDialogViewModel : BaseModel
|
||||
{
|
||||
|
||||
[ObservableProperty] private UserId _selectedUserId;
|
||||
|
||||
[ObservableProperty] private ObservableCollection<BaseModel> _profiles = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user