Merge branch 'master' into datetimeLoading

This commit is contained in:
Nicolas Abram 2024-05-01 14:16:11 -03:00 committed by GitHub
commit 9c41583a4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
76 changed files with 1532 additions and 440 deletions

View file

@ -20,7 +20,7 @@ jobs:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
fail-fast: false
steps:
@ -41,12 +41,12 @@ jobs:
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Change config filename for macOS
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
@ -61,15 +61,15 @@ jobs:
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit
run: |
@ -83,21 +83,21 @@ jobs:
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4
with:
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
build_macos:
name: macOS Universal (${{ matrix.configuration }})

View file

@ -42,7 +42,7 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.8" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Threading;
@ -87,7 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return;
}
byte[] samples = new byte[frameCount * _bytesPerFrame];
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span;
_ringBuffer.Read(samples, 0, samples.Length);

View file

@ -1,8 +1,10 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Backends.SoundIo.Native;
using Ryujinx.Audio.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
@ -37,7 +39,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
_outputStream.WriteCallback += Update;
_outputStream.Volume = requestedVolume;
// TODO: Setup other callbacks (errors, ect).
// TODO: Setup other callbacks (errors, etc.)
_outputStream.Open();
}
@ -120,7 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length;
byte[] samples = new byte[frameCount * bytesPerFrame];
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span;
_ringBuffer.Read(samples, 0, samples.Length);

View file

@ -212,7 +212,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP.
/// </summary>
/// <returns>True if if the audio renderer should fix it.</returns>
/// <returns>True if the audio renderer should fix it.</returns>
public bool IsAdpcmLoopContextBugFixed()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2);

View file

@ -44,7 +44,7 @@ namespace Ryujinx.Common.Extensions
/// <remarks>
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
/// To discourage use, it is recommended to to call this method like the following:
/// To discourage use, it is recommended to call this method like the following:
/// <c>
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
/// </c>

View file

@ -85,6 +85,70 @@ namespace Ryujinx.Cpu.Jit
_addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors);
}
public override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false)
{
if (size == 0)
{
return ReadOnlySequence<byte>.Empty;
}
try
{
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, false);
}
else
{
AssertValidAddressAndSize(va, (ulong)size);
}
ulong endVa = va + (ulong)size;
int offset = 0;
BytesReadOnlySequenceSegment first = null, last = null;
while (va < endVa)
{
(MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(size - offset));
Memory<byte> physicalMemory = memory.GetMemory(rangeOffset, (int)copySize);
if (first is null)
{
first = last = new BytesReadOnlySequenceSegment(physicalMemory);
}
else
{
if (last.IsContiguousWith(physicalMemory, out nuint contiguousStart, out int contiguousSize))
{
Memory<byte> contiguousPhysicalMemory = new NativeMemoryManager<byte>(contiguousStart, contiguousSize).Memory;
last.Replace(contiguousPhysicalMemory);
}
else
{
last = last.Append(physicalMemory);
}
}
va += copySize;
offset += (int)copySize;
}
return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex));
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return ReadOnlySequence<byte>.Empty;
}
}
/// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{

View file

@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsMismatchingViewFormat;
public readonly bool SupportsCubemapView;
public readonly bool SupportsNonConstantTextureOffset;
public readonly bool SupportsSeparateSampler;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsMismatchingViewFormat,
bool supportsCubemapView,
bool supportsNonConstantTextureOffset,
bool supportsSeparateSampler,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
@ -144,6 +146,7 @@ namespace Ryujinx.Graphics.GAL
SupportsMismatchingViewFormat = supportsMismatchingViewFormat;
SupportsCubemapView = supportsCubemapView;
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
SupportsSeparateSampler = supportsSeparateSampler;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;

View file

@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex;
GpuChannelPoolState poolState = new(
texturePoolGpuVa,
_state.State.SetTexHeaderPoolCMaximumIndex,
@ -139,7 +141,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
@ -184,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
}

View file

@ -157,6 +157,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
}
else if (operation == SyncpointbOperation.Incr)
{
// "Unbind" render targets since a syncpoint increment might indicate future CPU access for the textures.
_parent.TextureManager.RefreshModifiedTextures();
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
_context.Synchronization.IncrementSyncpoint(syncpointId);
}

View file

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Dma;
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Twod;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using System;
using System.Runtime.CompilerServices;
@ -28,6 +29,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
/// </summary>
public MemoryManager MemoryManager => _channel.MemoryManager;
/// <summary>
/// Channel texture manager.
/// </summary>
public TextureManager TextureManager => _channel.TextureManager;
/// <summary>
/// 3D Engine.
/// </summary>

View file

@ -1429,7 +1429,18 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
addressesSpan[index] = baseAddress + shader.Offset;
}
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, ref _pipeline, _channel, ref _currentSpecState.GetPoolState(), ref _currentSpecState.GetGraphicsState(), addresses);
int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex
? _state.State.TexturePoolState.MaximumId
: _state.State.SamplerPoolState.MaximumId;
CachedShaderProgram gs = shaderCache.GetGraphicsShader(
ref _state.State,
ref _pipeline,
_channel,
samplerPoolMaximumId,
ref _currentSpecState.GetPoolState(),
ref _currentSpecState.GetGraphicsState(),
addresses);
// Consume the modified flag for spec state so that it isn't checked again.
_currentSpecState.SetShader(gs);

View file

@ -62,8 +62,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="channel">GPU channel that the texture pool cache belongs to</param>
/// <param name="address">Start address of the texture pool</param>
/// <param name="maximumId">Maximum ID of the texture pool</param>
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
/// <returns>The found or newly created texture pool</returns>
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache)
{
// Remove old entries from the cache, if possible.
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
@ -73,6 +74,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_pools.RemoveFirst();
oldestPool.Dispose();
oldestPool.CacheNode = null;
bindingsArrayCache.RemoveAllWithPool(oldestPool);
}
T pool;
@ -87,8 +89,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (pool.CacheNode != _pools.Last)
{
_pools.Remove(pool.CacheNode);
pool.CacheNode = _pools.AddLast(pool);
_pools.AddLast(pool.CacheNode);
}
pool.CacheTimestamp = _currentTimestamp;

View file

@ -44,6 +44,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public TextureUsageFlags Flags { get; }
/// <summary>
/// Indicates that the binding is for a sampler.
/// </summary>
public bool IsSamplerOnly { get; }
/// <summary>
/// Constructs the texture binding information structure.
/// </summary>
@ -74,8 +79,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
public TextureBindingInfo(Target target, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, arrayLength, cbufSlot, handle, flags)
/// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
public TextureBindingInfo(
Target target,
int binding,
int arrayLength,
int cbufSlot,
int handle,
TextureUsageFlags flags,
bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags)
{
IsSamplerOnly = isSamplerOnly;
}
}
}

View file

@ -21,12 +21,98 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly GpuContext _context;
private readonly GpuChannel _channel;
private readonly bool _isCompute;
/// <summary>
/// Array cache entry key.
/// </summary>
private readonly struct CacheEntryKey : IEquatable<CacheEntryKey>
private readonly struct CacheEntryFromPoolKey : IEquatable<CacheEntryFromPoolKey>
{
/// <summary>
/// Whether the entry is for an image.
/// </summary>
public readonly bool IsImage;
/// <summary>
/// Whether the entry is for a sampler.
/// </summary>
public readonly bool IsSampler;
/// <summary>
/// Texture or image target type.
/// </summary>
public readonly Target Target;
/// <summary>
/// Number of entries of the array.
/// </summary>
public readonly int ArrayLength;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="isImage">Whether the entry is for an image</param>
/// <param name="bindingInfo">Binding information for the array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool)
{
IsImage = isImage;
IsSampler = bindingInfo.IsSamplerOnly;
Target = bindingInfo.Target;
ArrayLength = bindingInfo.ArrayLength;
_texturePool = texturePool;
_samplerPool = samplerPool;
}
/// <summary>
/// Checks if the pool matches the cached pool.
/// </summary>
/// <param name="texturePool">Texture or sampler pool instance</param>
/// <returns>True if the pool matches, false otherwise</returns>
public bool MatchesPool<T>(IPool<T> pool)
{
return _texturePool == pool || _samplerPool == pool;
}
/// <summary>
/// Checks if the texture and sampler pools matches the cached pools.
/// </summary>
/// <param name="texturePool">Texture pool instance</param>
/// <param name="samplerPool">Sampler pool instance</param>
/// <returns>True if the pools match, false otherwise</returns>
private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool)
{
return _texturePool == texturePool && _samplerPool == samplerPool;
}
public bool Equals(CacheEntryFromPoolKey other)
{
return IsImage == other.IsImage &&
IsSampler == other.IsSampler &&
Target == other.Target &&
ArrayLength == other.ArrayLength &&
MatchesPools(other._texturePool, other._samplerPool);
}
public override bool Equals(object obj)
{
return obj is CacheEntryFromBufferKey other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_texturePool, _samplerPool, IsSampler);
}
}
/// <summary>
/// Array cache entry key.
/// </summary>
private readonly struct CacheEntryFromBufferKey : IEquatable<CacheEntryFromBufferKey>
{
/// <summary>
/// Whether the entry is for an image.
@ -61,7 +147,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
public CacheEntryKey(
public CacheEntryFromBufferKey(
bool isImage,
TextureBindingInfo bindingInfo,
TexturePool texturePool,
@ -100,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Image
return _textureBufferBounds.Equals(textureBufferBounds);
}
public bool Equals(CacheEntryKey other)
public bool Equals(CacheEntryFromBufferKey other)
{
return IsImage == other.IsImage &&
Target == other.Target &&
@ -112,7 +198,7 @@ namespace Ryujinx.Graphics.Gpu.Image
public override bool Equals(object obj)
{
return obj is CacheEntryKey other && Equals(other);
return obj is CacheEntryFromBufferKey other && Equals(other);
}
public override int GetHashCode()
@ -122,40 +208,15 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// Array cache entry.
/// Array cache entry from pool.
/// </summary>
private class CacheEntry
{
/// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryKey Key;
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntry> CacheNode;
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
/// <summary>
/// All cached textures, along with their invalidated sequence number as value.
/// </summary>
public readonly Dictionary<Texture, int> Textures;
/// <summary>
/// All pool texture IDs along with their textures.
/// </summary>
public readonly Dictionary<int, Texture> TextureIds;
/// <summary>
/// All pool sampler IDs along with their samplers.
/// </summary>
public readonly Dictionary<int, Sampler> SamplerIds;
/// <summary>
/// Backend texture array if the entry is for a texture, otherwise null.
/// </summary>
@ -166,44 +227,39 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public readonly IImageArray ImageArray;
private readonly TexturePool _texturePool;
private readonly SamplerPool _samplerPool;
/// <summary>
/// Texture pool where the array textures are located.
/// </summary>
protected readonly TexturePool TexturePool;
/// <summary>
/// Sampler pool where the array samplers are located.
/// </summary>
protected readonly SamplerPool SamplerPool;
private int _texturePoolSequence;
private int _samplerPoolSequence;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
private CacheEntry(ref CacheEntryKey key, TexturePool texturePool, SamplerPool samplerPool)
private CacheEntry(TexturePool texturePool, SamplerPool samplerPool)
{
Key = key;
Textures = new Dictionary<Texture, int>();
TextureIds = new Dictionary<int, Texture>();
SamplerIds = new Dictionary<int, Sampler>();
_texturePool = texturePool;
_samplerPool = samplerPool;
_lastSequenceNumber = -1;
TexturePool = texturePool;
SamplerPool = samplerPool;
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
{
TextureArray = array;
}
@ -211,11 +267,10 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntry(ref CacheEntryKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(ref key, texturePool, samplerPool)
public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool)
{
ImageArray = array;
}
@ -248,23 +303,9 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Clears all cached texture instances.
/// </summary>
public void Reset()
public virtual void Reset()
{
Textures.Clear();
TextureIds.Clear();
SamplerIds.Clear();
}
/// <summary>
/// Updates the cached constant buffer data.
/// </summary>
/// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
/// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
{
_cachedTextureBuffer = cachedTextureBuffer.ToArray();
_cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
}
/// <summary>
@ -287,39 +328,105 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
public bool PoolsModified()
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool TexturePoolModified()
{
bool texturePoolModified = _texturePool.WasModified(ref _texturePoolSequence);
bool samplerPoolModified = _samplerPool.WasModified(ref _samplerPoolSequence);
return TexturePool.WasModified(ref _texturePoolSequence);
}
// If both pools were not modified since the last check, we have nothing else to check.
if (!texturePoolModified && !samplerPoolModified)
{
return false;
}
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pool might have been modified, false otherwise</returns>
public bool SamplerPoolModified()
{
return SamplerPool.WasModified(ref _samplerPoolSequence);
}
}
// If the pools were modified, let's check if any of the entries we care about changed.
/// <summary>
/// Array cache entry from constant buffer.
/// </summary>
private class CacheEntryFromBuffer : CacheEntry
{
/// <summary>
/// Key for this entry on the cache.
/// </summary>
public readonly CacheEntryFromBufferKey Key;
// Check if any of our cached textures changed on the pool.
foreach ((int textureId, Texture texture) in TextureIds)
{
if (_texturePool.GetCachedItem(textureId) != texture)
{
return true;
}
}
/// <summary>
/// Linked list node used on the texture bindings array cache.
/// </summary>
public LinkedListNode<CacheEntryFromBuffer> CacheNode;
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, Sampler sampler) in SamplerIds)
{
if (_samplerPool.GetCachedItem(samplerId) != sampler)
{
return true;
}
}
/// <summary>
/// Timestamp set on the last use of the array by the cache.
/// </summary>
public int CacheTimestamp;
return false;
/// <summary>
/// All pool texture IDs along with their textures.
/// </summary>
public readonly Dictionary<int, (Texture, TextureDescriptor)> TextureIds;
/// <summary>
/// All pool sampler IDs along with their samplers.
/// </summary>
public readonly Dictionary<int, (Sampler, SamplerDescriptor)> SamplerIds;
private int[] _cachedTextureBuffer;
private int[] _cachedSamplerBuffer;
private int _lastSequenceNumber;
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend texture array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
{
Key = key;
_lastSequenceNumber = -1;
TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
}
/// <summary>
/// Creates a new array cache entry.
/// </summary>
/// <param name="key">Key for this entry on the cache</param>
/// <param name="array">Backend image array</param>
/// <param name="texturePool">Texture pool where the array textures are located</param>
/// <param name="samplerPool">Sampler pool where the array samplers are located</param>
public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool)
{
Key = key;
_lastSequenceNumber = -1;
TextureIds = new Dictionary<int, (Texture, TextureDescriptor)>();
SamplerIds = new Dictionary<int, (Sampler, SamplerDescriptor)>();
}
/// <inheritdoc/>
public override void Reset()
{
base.Reset();
TextureIds.Clear();
SamplerIds.Clear();
}
/// <summary>
/// Updates the cached constant buffer data.
/// </summary>
/// <param name="cachedTextureBuffer">Constant buffer data with the texture handles (and sampler handles, if they are combined)</param>
/// <param name="cachedSamplerBuffer">Constant buffer data with the sampler handles</param>
/// <param name="separateSamplerBuffer">Whether <paramref name="cachedTextureBuffer"/> and <paramref name="cachedSamplerBuffer"/> comes from different buffers</param>
public void UpdateData(ReadOnlySpan<int> cachedTextureBuffer, ReadOnlySpan<int> cachedSamplerBuffer, bool separateSamplerBuffer)
{
_cachedTextureBuffer = cachedTextureBuffer.ToArray();
_cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer;
}
/// <summary>
@ -380,10 +487,51 @@ namespace Ryujinx.Graphics.Gpu.Image
return true;
}
/// <summary>
/// Checks if the cached texture or sampler pool has been modified since the last call to this method.
/// </summary>
/// <returns>True if any used entries of the pools might have been modified, false otherwise</returns>
public bool PoolsModified()
{
bool texturePoolModified = TexturePoolModified();
bool samplerPoolModified = SamplerPoolModified();
// If both pools were not modified since the last check, we have nothing else to check.
if (!texturePoolModified && !samplerPoolModified)
{
return false;
}
// If the pools were modified, let's check if any of the entries we care about changed.
// Check if any of our cached textures changed on the pool.
foreach ((int textureId, (Texture texture, TextureDescriptor descriptor)) in TextureIds)
{
if (TexturePool.GetCachedItem(textureId) != texture ||
(texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor)))
{
return true;
}
}
// Check if any of our cached samplers changed on the pool.
foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds)
{
if (SamplerPool.GetCachedItem(samplerId) != sampler ||
(sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor)))
{
return true;
}
}
return false;
}
}
private readonly Dictionary<CacheEntryKey, CacheEntry> _cache;
private readonly LinkedList<CacheEntry> _lruCache;
private readonly Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer> _cacheFromBuffer;
private readonly Dictionary<CacheEntryFromPoolKey, CacheEntry> _cacheFromPool;
private readonly LinkedList<CacheEntryFromBuffer> _lruCache;
private int _currentTimestamp;
@ -392,14 +540,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">GPU context</param>
/// <param name="channel">GPU channel</param>
/// <param name="isCompute">Whether the bindings will be used for compute or graphics pipelines</param>
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel, bool isCompute)
public TextureBindingsArrayCache(GpuContext context, GpuChannel channel)
{
_context = context;
_channel = channel;
_isCompute = isCompute;
_cache = new Dictionary<CacheEntryKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntry>();
_cacheFromBuffer = new Dictionary<CacheEntryFromBufferKey, CacheEntryFromBuffer>();
_cacheFromPool = new Dictionary<CacheEntryFromPoolKey, CacheEntry>();
_lruCache = new LinkedList<CacheEntryFromBuffer>();
}
/// <summary>
@ -457,15 +604,180 @@ namespace Ryujinx.Graphics.Gpu.Image
bool isImage,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
if (IsDirectHandleType(bindingInfo.Handle))
{
UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo);
}
else
{
UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo);
}
}
/// <summary>
/// Updates a texture or image array bindings and textures from a texture or sampler pool.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="bindingInfo">Array binding information</param>
private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo)
{
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
bool isSampler = bindingInfo.IsSamplerOnly;
bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified();
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported);
if (!poolModified && !isNewEntry && entry.ValidateTextures())
{
entry.SynchronizeMemory(isStore, resScaleUnsupported);
if (isImage)
{
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
return;
}
if (!isNewEntry)
{
entry.Reset();
}
int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1;
length = Math.Min(length, bindingInfo.ArrayLength);
Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null;
ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength];
ITexture[] textures = new ITexture[bindingInfo.ArrayLength];
for (int index = 0; index < length; index++)
{
Texture texture = null;
Sampler sampler = null;
if (isSampler)
{
sampler = samplerPool?.Get(index);
}
else
{
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture);
if (texture != null)
{
entry.Textures[texture] = texture.InvalidatedSequence;
if (isStore)
{
texture.SignalModified();
}
if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted)
{
// Scaling textures used on arrays is currently not supported.
texture.BlacklistScale();
}
}
}
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
Format format = bindingInfo.Format;
if (hostTexture != null && texture.Target == Target.TextureBuffer)
{
// 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.
if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
_channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
_channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
{
if (format == 0 && texture != null)
{
format = texture.Format;
}
formats[index] = format;
textures[index] = hostTexture;
}
else
{
samplers[index] = hostSampler;
textures[index] = hostTexture;
}
}
if (isImage)
{
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
}
}
/// <summary>
/// Updates a texture or image array bindings and textures from constant buffer handles.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="stage">Shader stage where the array is used</param>
/// <param name="stageIndex">Shader stage index where the array is used</param>
/// <param name="textureBufferIndex">Texture constant buffer index</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="samplerIndex">Sampler handles source</param>
/// <param name="bindingInfo">Array binding information</param>
private void UpdateFromBuffer(
TexturePool texturePool,
SamplerPool samplerPool,
ShaderStage stage,
int stageIndex,
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
TextureBindingInfo bindingInfo)
{
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex;
bool isCompute = stage == ShaderStage.Compute;
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex);
ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex);
CacheEntry entry = GetOrAddEntry(
CacheEntryFromBuffer entry = GetOrAddEntry(
texturePool,
samplerPool,
bindingInfo,
@ -589,8 +901,8 @@ namespace Ryujinx.Graphics.Gpu.Image
Sampler sampler = samplerPool?.Get(samplerId);
entry.TextureIds[textureId] = texture;
entry.SamplerIds[samplerId] = sampler;
entry.TextureIds[textureId] = (texture, descriptor);
entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default);
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
ISampler hostSampler = sampler?.GetHostSampler(texture);
@ -650,13 +962,12 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
/// Gets a cached texture entry, or creates a new one if not found.
/// Gets a cached texture entry from pool, or creates a new one if not found.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
/// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
/// <returns>Cache entry</returns>
private CacheEntry GetOrAddEntry(
@ -664,17 +975,11 @@ namespace Ryujinx.Graphics.Gpu.Image
SamplerPool samplerPool,
TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
{
CacheEntryKey key = new CacheEntryKey(
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool);
isNew = !_cache.TryGetValue(key, out CacheEntry entry);
isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry);
if (isNew)
{
@ -684,13 +989,61 @@ namespace Ryujinx.Graphics.Gpu.Image
{
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
_cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
}
else
{
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cache.Add(key, entry = new CacheEntry(ref key, array, texturePool, samplerPool));
_cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool));
}
}
return entry;
}
/// <summary>
/// Gets a cached texture entry from constant buffer, or creates a new one if not found.
/// </summary>
/// <param name="texturePool">Texture pool</param>
/// <param name="samplerPool">Sampler pool</param>
/// <param name="bindingInfo">Array binding information</param>
/// <param name="isImage">Whether the array is a image or texture array</param>
/// <param name="textureBufferBounds">Constant buffer bounds with the texture handles</param>
/// <param name="isNew">Whether a new entry was created, or an existing one was returned</param>
/// <returns>Cache entry</returns>
private CacheEntryFromBuffer GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
{
CacheEntryFromBufferKey key = new CacheEntryFromBufferKey(
isImage,
bindingInfo,
texturePool,
samplerPool,
ref textureBufferBounds);
isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry);
if (isNew)
{
int arrayLength = bindingInfo.ArrayLength;
if (isImage)
{
IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
}
else
{
ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer);
_cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool));
}
}
@ -716,15 +1069,52 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
private void RemoveLeastUsedEntries()
{
LinkedListNode<CacheEntry> nextNode = _lruCache.First;
LinkedListNode<CacheEntryFromBuffer> nextNode = _lruCache.First;
while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval)
{
LinkedListNode<CacheEntry> toRemove = nextNode;
LinkedListNode<CacheEntryFromBuffer> toRemove = nextNode;
nextNode = nextNode.Next;
_cache.Remove(toRemove.Value.Key);
_cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove);
}
}
/// <summary>
/// Removes all cached texture arrays matching the specified texture pool.
/// </summary>
/// <param name="pool">Texture pool</param>
public void RemoveAllWithPool<T>(IPool<T> pool)
{
List<CacheEntryFromPoolKey> keysToRemove = null;
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
{
if (key.MatchesPool(pool))
{
(keysToRemove ??= new()).Add(key);
}
}
if (keysToRemove != null)
{
foreach (CacheEntryFromPoolKey key in keysToRemove)
{
_cacheFromPool.Remove(key);
}
}
}
/// <summary>
/// Checks if a handle indicates the binding should have all its textures sourced directly from a pool.
/// </summary>
/// <param name="handle">Handle to check</param>
/// <returns>True if the handle represents direct pool access, false otherwise</returns>
private static bool IsDirectHandleType(int handle)
{
(_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle);
return type == TextureHandleType.Direct;
}
}
}

View file

@ -34,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
private readonly TextureBindingsArrayCache _arrayBindingsCache;
private readonly TextureBindingsArrayCache _bindingsArrayCache;
private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool;
@ -72,12 +72,14 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
/// <param name="context">The GPU context that the texture bindings manager belongs to</param>
/// <param name="channel">The GPU channel that the texture bindings manager belongs to</param>
/// <param name="bindingsArrayCache">Cache of texture array bindings</param>
/// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param>
/// <param name="samplerPoolCache">Sampler pools cache used to get sampler pools from</param>
/// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
public TextureBindingsManager(
GpuContext context,
GpuChannel channel,
TextureBindingsArrayCache bindingsArrayCache,
TexturePoolCache texturePoolCache,
SamplerPoolCache samplerPoolCache,
bool isCompute)
@ -89,7 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_isCompute = isCompute;
_arrayBindingsCache = new TextureBindingsArrayCache(context, channel, isCompute);
_bindingsArrayCache = bindingsArrayCache;
int stages = isCompute ? 1 : Constants.ShaderStages;
@ -456,7 +458,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
_bindingsArrayCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo);
continue;
}
@ -594,7 +596,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (bindingInfo.ArrayLength > 1)
{
_arrayBindingsCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
_bindingsArrayCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo);
continue;
}
@ -732,7 +734,7 @@ namespace Ryujinx.Graphics.Gpu.Image
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
TextureDescriptor descriptor;
@ -770,7 +772,7 @@ namespace Ryujinx.Graphics.Gpu.Image
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
int handle = textureBufferAddress != 0
int handle = textureBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
: 0;
@ -790,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
samplerHandle = samplerBufferAddress != 0
samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
: 0;
}
@ -828,7 +830,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped)
{
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId);
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache);
_texturePool = texturePool;
}
}
@ -839,7 +841,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (poolAddress != MemoryManager.PteUnmapped)
{
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId);
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache);
_samplerPool = samplerPool;
}
}

View file

@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly TextureBindingsManager _cpBindingsManager;
private readonly TextureBindingsManager _gpBindingsManager;
private readonly TextureBindingsArrayCache _bindingsArrayCache;
private readonly TexturePoolCache _texturePoolCache;
private readonly SamplerPoolCache _samplerPoolCache;
@ -46,8 +47,9 @@ namespace Ryujinx.Graphics.Gpu.Image
TexturePoolCache texturePoolCache = new(context);
SamplerPoolCache samplerPoolCache = new(context);
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false);
_bindingsArrayCache = new TextureBindingsArrayCache(context, channel);
_cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: false);
_texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache;
@ -384,7 +386,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache);
return texturePool;
}

View file

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
TextureBindings[i] = stage.Info.Textures.Select(descriptor =>
{
Target target = ShaderTexture.GetTarget(descriptor.Type);
Target target = descriptor.Type != SamplerType.None ? ShaderTexture.GetTarget(descriptor.Type) : default;
var result = new TextureBindingInfo(
target,
@ -66,7 +66,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
descriptor.ArrayLength,
descriptor.CbufSlot,
descriptor.HandleIndex,
descriptor.Flags);
descriptor.Flags,
descriptor.Type == SamplerType.None);
if (descriptor.ArrayLength <= 1)
{

View file

@ -109,6 +109,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QuerySamplerArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: true);
}
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
@ -117,6 +124,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Constant buffer derived length is not available on the cache</exception>
public int QueryTextureArrayLengthFromBuffer(int slot)
{
if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot))
@ -130,6 +138,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return arrayLength;
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
public int QueryTextureArrayLengthFromPool()
{
return QueryArrayLengthFromPool(isSampler: false);
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -170,6 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
/// <inheritdoc/>
/// <exception cref="DiskCacheLoadException">Texture information is not available on the cache</exception>
public void RegisterTexture(int handle, int cbufSlot)
{
if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
@ -182,5 +198,24 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
_newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized);
}
/// <summary>
/// Gets the cached texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True to get sampler pool length, false for texture pool length</param>
/// <returns>Pool length</returns>
/// <exception cref="DiskCacheLoadException">Pool length is not available on the cache</exception>
private int QueryArrayLengthFromPool(bool isSampler)
{
if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler))
{
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength);
}
int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler);
_newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength);
return arrayLength;
}
}
}

View file

@ -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 = 6489;
private const uint CodeGenVersion = 6577;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View file

@ -120,6 +120,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public int QuerySamplerArrayLengthFromPool()
{
int length = _state.SamplerPoolMaximumId + 1;
_state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length);
return length;
}
/// <inheritdoc/>
public SamplerType QuerySamplerType(int handle, int cbufSlot)
{
@ -141,6 +150,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
return arrayLength;
}
/// <inheritdoc/>
public int QueryTextureArrayLengthFromPool()
{
int length = _state.PoolState.TexturePoolMaximumId + 1;
_state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length);
return length;
}
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{

View file

@ -213,6 +213,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats;
public bool QueryHostSupportsSeparateSampler() => _context.Capabilities.SupportsSeparateSampler;
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence;

View file

@ -5,6 +5,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
class GpuAccessorState
{
/// <summary>
/// Maximum ID that a sampler pool entry may have.
/// </summary>
public readonly int SamplerPoolMaximumId;
/// <summary>
/// GPU texture pool state.
/// </summary>
@ -38,18 +43,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Creates a new GPU accessor state.
/// </summary>
/// <param name="samplerPoolMaximumId">Maximum ID that a sampler pool entry may have</param>
/// <param name="poolState">GPU texture pool state</param>
/// <param name="computeState">GPU compute state, for compute shaders</param>
/// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param>
/// <param name="specializationState">Shader specialization state (shared by all stages)</param>
/// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param>
public GpuAccessorState(
int samplerPoolMaximumId,
GpuChannelPoolState poolState,
GpuChannelComputeState computeState,
GpuChannelGraphicsState graphicsState,
ShaderSpecializationState specializationState,
TransformFeedbackDescriptor[] transformFeedbackDescriptors = null)
{
SamplerPoolMaximumId = samplerPoolMaximumId;
PoolState = poolState;
GraphicsState = graphicsState;
ComputeState = computeState;

View file

@ -2,7 +2,6 @@ using System;
namespace Ryujinx.Graphics.Gpu.Shader
{
#pragma warning disable CS0659 // Class overrides Object.Equals(object o) but does not override Object.GetHashCode()
/// <summary>
/// State used by the <see cref="GpuAccessor"/>.
/// </summary>
@ -52,6 +51,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
return obj is GpuChannelPoolState state && Equals(state);
}
public override int GetHashCode()
{
return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex);
}
}
#pragma warning restore CS0659
}

View file

@ -192,12 +192,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// This automatically translates, compiles and adds the code to the cache if not present.
/// </remarks>
/// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="computeState">Compute engine state</param>
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
/// <returns>Compiled compute shader code</returns>
public CachedShaderProgram GetComputeShader(
GpuChannel channel,
int samplerPoolMaximumId,
GpuChannelPoolState poolState,
GpuChannelComputeState computeState,
ulong gpuVa)
@ -214,7 +216,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
ShaderSpecializationState specState = new(ref computeState);
GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState);
GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState);
GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState);
gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false);
@ -291,6 +293,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="state">GPU state</param>
/// <param name="pipeline">Pipeline state</param>
/// <param name="channel">GPU channel</param>
/// <param name="samplerPoolMaximumId">Maximum ID that an entry in the sampler pool may have</param>
/// <param name="poolState">Texture pool state</param>
/// <param name="graphicsState">3D engine state</param>
/// <param name="addresses">Addresses of the shaders for each stage</param>
@ -299,6 +302,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ref ThreedClassState state,
ref ProgramPipelineState pipeline,
GpuChannel channel,
int samplerPoolMaximumId,
ref GpuChannelPoolState poolState,
ref GpuChannelGraphicsState graphicsState,
ShaderAddresses addresses)
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel);
ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new(poolState, default, graphicsState, specState, transformFeedbackDescriptors);
GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors);
ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan();

View file

@ -185,11 +185,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
if (texture.ArrayLength > 1)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
ResourceType type = GetTextureResourceType(texture, isImage);
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
@ -242,16 +238,38 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
foreach (TextureDescriptor texture in textures)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
ResourceType type = isBuffer
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
ResourceType type = GetTextureResourceType(texture, isImage);
_resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
}
}
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
if (isBuffer)
{
return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture;
}
else if (isImage)
{
return ResourceType.Image;
}
else if (texture.Type == SamplerType.None)
{
return ResourceType.Sampler;
}
else if (texture.Separate)
{
return ResourceType.Texture;
}
else
{
return ResourceType.TextureAndSampler;
}
}
/// <summary>
/// Creates a new shader information structure from the added information.
/// </summary>

View file

@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
PrimitiveTopology = 1 << 1,
TransformFeedback = 1 << 3,
TextureArrayFromBuffer = 1 << 4,
TextureArrayFromPool = 1 << 5,
}
private QueriedStateFlags _queriedState;
@ -154,7 +155,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
private readonly Dictionary<TextureKey, int> _textureArraySpecialization;
private readonly Dictionary<TextureKey, int> _textureArrayFromBufferSpecialization;
private readonly Dictionary<bool, int> _textureArrayFromPoolSpecialization;
private KeyValuePair<TextureKey, Box<TextureSpecializationState>>[] _allTextures;
private Box<TextureSpecializationState>[][] _textureByBinding;
private Box<TextureSpecializationState>[][] _imageByBinding;
@ -165,7 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private ShaderSpecializationState()
{
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
_textureArraySpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromBufferSpecialization = new Dictionary<TextureKey, int>();
_textureArrayFromPoolSpecialization = new Dictionary<bool, int>();
}
/// <summary>
@ -327,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
/// Registers the length of a texture array calculated from a constant buffer size.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
@ -335,10 +338,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length)
{
_textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromBuffer;
}
/// <summary>
/// Registers the length of a texture array calculated from a texture or sampler pool capacity.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <param name="length">Number of elements in the texture array</param>
public void RegisterTextureArrayLengthFromPool(bool isSampler, int length)
{
_textureArrayFromPoolSpecialization[isSampler] = length;
_queriedState |= QueriedStateFlags.TextureArrayFromPool;
}
/// <summary>
/// Indicates that the format of a given texture was used during the shader translation process.
/// </summary>
@ -385,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Checks if a given texture was registerd on this specialization state.
/// Checks if a given texture was registered on this specialization state.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
@ -396,14 +410,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
/// <summary>
/// Checks if a given texture array (from constant buffer) was registerd on this specialization state.
/// Checks if a given texture array (from constant buffer) was registered on this specialization state.
/// </summary>
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>True if the length for the given buffer and stage exists, false otherwise</returns>
public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot));
}
/// <summary>
/// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state.
/// </summary>
/// <param name="isSampler">True for sampler pool, false for texture pool</param>
/// <returns>True if the length for the given pool, false otherwise</returns>
public bool TextureArrayFromPoolRegistered(bool isSampler)
{
return _textureArrayFromPoolSpecialization.ContainsKey(isSampler);
}
/// <summary>
@ -412,6 +437,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Format and sRGB tuple</returns>
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
{
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
@ -424,6 +450,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture target</returns>
public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
{
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
@ -435,6 +462,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>True if coordinates are normalized, false otherwise</returns>
public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
{
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
@ -446,9 +474,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="stageIndex">Shader stage where the texture is used</param>
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot)
{
return _textureArraySpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)];
}
/// <summary>
/// Gets the recorded length of a given texture array (from a sampler or texture pool).
/// </summary>
/// <param name="isSampler">True to get the sampler pool length, false to get the texture pool length</param>
/// <returns>Texture array length</returns>
public int GetTextureArrayFromPoolLength(bool isSampler)
{
return _textureArrayFromPoolSpecialization[isSampler];
}
/// <summary>
@ -894,7 +933,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArraySpecialization[textureKey] = length;
specState._textureArrayFromBufferSpecialization[textureKey] = length;
}
}
if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
{
dataReader.Read(ref count);
for (int index = 0; index < count; index++)
{
bool textureKey = default;
int length = 0;
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
dataReader.Read(ref length);
specState._textureArrayFromPoolSpecialization[textureKey] = length;
}
}
@ -965,10 +1020,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer))
{
count = (ushort)_textureArraySpecialization.Count;
count = (ushort)_textureArrayFromBufferSpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _textureArraySpecialization)
foreach (var kv in _textureArrayFromBufferSpecialization)
{
var textureKey = kv.Key;
var length = kv.Value;
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
dataWriter.Write(ref length);
}
}
if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool))
{
count = (ushort)_textureArrayFromPoolSpecialization.Count;
dataWriter.Write(ref count);
foreach (var kv in _textureArrayFromPoolSpecialization)
{
var textureKey = kv.Key;
var length = kv.Value;

View file

@ -176,6 +176,7 @@ namespace Ryujinx.Graphics.OpenGL
supportsCubemapView: true,
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
supportsScaledVertexFormats: true,
supportsSeparateSampler: false,
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true,

View file

@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
@ -352,7 +351,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
arrayDecl = "[]";
}
string samplerTypeName = definition.Type.ToGlslSamplerType();
string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
string layout = string.Empty;

View file

@ -639,14 +639,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
TextureDefinition definition = context.Properties.Textures[texOp.Binding];
string name = definition.Name;
TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
string name = textureDefinition.Name;
if (definition.ArrayLength != 1)
if (textureDefinition.ArrayLength != 1)
{
name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
if (texOp.IsSeparate)
{
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1)
{
samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]";
}
name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})";
}
return name;
}

View file

@ -160,37 +160,49 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch
SpvInstruction imageType;
SpvInstruction sampledImageType;
if (sampler.Type != SamplerType.None)
{
SamplerType.Texture1D => Dim.Dim1D,
SamplerType.Texture2D => Dim.Dim2D,
SamplerType.Texture3D => Dim.Dim3D,
SamplerType.TextureCube => Dim.Cube,
SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var dim = (sampler.Type & SamplerType.Mask) switch
{
SamplerType.Texture1D => Dim.Dim1D,
SamplerType.Texture2D => Dim.Dim2D,
SamplerType.Texture3D => Dim.Dim3D,
SamplerType.TextureCube => Dim.Cube,
SamplerType.TextureBuffer => Dim.Buffer,
_ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."),
};
var imageType = context.TypeImage(
context.TypeFP32(),
dim,
sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample),
1,
ImageFormat.Unknown);
imageType = context.TypeImage(
context.TypeFP32(),
dim,
sampler.Type.HasFlag(SamplerType.Shadow),
sampler.Type.HasFlag(SamplerType.Array),
sampler.Type.HasFlag(SamplerType.Multisample),
1,
ImageFormat.Unknown);
var sampledImageType = context.TypeSampledImage(imageType);
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageType);
sampledImageType = context.TypeSampledImage(imageType);
}
else
{
imageType = sampledImageType = context.TypeSampler();
}
var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType;
var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType);
var sampledImageArrayPointerType = sampledImagePointerType;
if (sampler.ArrayLength == 0)
{
var sampledImageArrayType = context.TypeRuntimeArray(sampledImageType);
var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType);
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}
else if (sampler.ArrayLength != 1)
{
var sampledImageArrayType = context.TypeArray(sampledImageType, context.Constant(context.TypeU32(), sampler.ArrayLength));
var sampledImageArrayType = context.TypeArray(sampledOrSeparateImageType, context.Constant(context.TypeU32(), sampler.ArrayLength));
sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType);
}

View file

@ -838,16 +838,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int pCount = texOp.Type.GetDimensions();
@ -1171,16 +1162,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = Src(AggregateType.S32);
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int coordsCount = texOp.Type.GetDimensions();
@ -1449,17 +1431,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image);
SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image);
@ -1471,17 +1447,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
AstTextureOperation texOp = (AstTextureOperation)operation;
int srcIndex = 0;
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
SpvInstruction image = declaration.Image;
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.GetS32(texOp.GetSource(0));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
image = context.Load(declaration.SampledImageType, image);
image = context.Image(declaration.ImageType, image);
if (texOp.Index == 3)
@ -1506,8 +1476,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (hasLod)
{
int lodSrcIndex = declaration.IsIndexed ? 1 : 0;
var lod = context.GetS32(operation.GetSource(lodSrcIndex));
var lod = context.GetS32(operation.GetSource(srcIndex));
result = context.ImageQuerySizeLod(resultType, image, lod);
}
else
@ -1905,6 +1874,43 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex)
{
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
{
SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex);
}
if (texOp.IsSeparate)
{
image = context.Load(declaration.ImageType, image);
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
SpvInstruction sampler = samplerDeclaration.Image;
if (samplerDeclaration.IsIndexed)
{
SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++));
sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex);
}
sampler = context.Load(samplerDeclaration.ImageType, sampler);
image = context.SampledImage(declaration.SampledImageType, image, sampler);
}
else
{
image = context.Load(declaration.SampledImageType, image);
}
return image;
}
private static OperationResult GenerateUnary(
CodeGenContext context,
AstOperation operation,

View file

@ -26,13 +26,6 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -298,6 +291,15 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries host API support for separate textures and samplers.
/// </summary>
/// <returns>True if the API supports samplers and textures to be combined on the shader, false otherwise</returns>
bool QueryHostSupportsSeparateSampler()
{
return true;
}
/// <summary>
/// Queries host GPU shader ballot support.
/// </summary>
@ -388,6 +390,12 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Gets the maximum number of samplers that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of samplers that the pool may have</returns>
int QuerySamplerArrayLengthFromPool();
/// <summary>
/// Queries sampler type information.
/// </summary>
@ -399,6 +407,19 @@ namespace Ryujinx.Graphics.Shader
return SamplerType.Texture2D;
}
/// <summary>
/// Gets the size in bytes of a bound constant buffer for the current shader stage.
/// </summary>
/// <param name="slot">The number of the constant buffer to get the size from</param>
/// <returns>Size in bytes</returns>
int QueryTextureArrayLengthFromBuffer(int slot);
/// <summary>
/// Gets the maximum number of textures that the bound texture pool may have.
/// </summary>
/// <returns>Maximum amount of textures that the pool may have</returns>
int QueryTextureArrayLengthFromPool();
/// <summary>
/// Queries texture coordinate normalization information.
/// </summary>

View file

@ -216,6 +216,11 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
newSources[index] = source;
if (source != null && source.Type == OperandType.LocalVariable)
{
source.UseOps.Add(this);
}
_sources = newSources;
}

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public TextureFlags Flags { get; private set; }
public int Binding { get; private set; }
public int SamplerBinding { get; private set; }
public TextureOperation(
Instruction inst,
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Format = format;
Flags = flags;
Binding = binding;
SamplerBinding = -1;
}
public void TurnIntoArray(int binding)
@ -32,6 +34,13 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Binding = binding;
}
public void TurnIntoArray(int textureBinding, int samplerBinding)
{
TurnIntoArray(textureBinding);
SamplerBinding = samplerBinding;
}
public void SetBinding(int binding)
{
if ((Flags & TextureFlags.Bindless) != 0)

View file

@ -69,6 +69,7 @@ namespace Ryujinx.Graphics.Shader
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.None => "sampler",
SamplerType.Texture1D => "sampler1D",
SamplerType.TextureBuffer => "samplerBuffer",
SamplerType.Texture2D => "sampler2D",
@ -95,6 +96,31 @@ namespace Ryujinx.Graphics.Shader
return typeName;
}
public static string ToGlslTextureType(this SamplerType type)
{
string typeName = (type & SamplerType.Mask) switch
{
SamplerType.Texture1D => "texture1D",
SamplerType.TextureBuffer => "textureBuffer",
SamplerType.Texture2D => "texture2D",
SamplerType.Texture3D => "texture3D",
SamplerType.TextureCube => "textureCube",
_ => throw new ArgumentException($"Invalid texture type \"{type}\"."),
};
if ((type & SamplerType.Multisample) != 0)
{
typeName += "MS";
}
if ((type & SamplerType.Array) != 0)
{
typeName += "Array";
}
return typeName;
}
public static string ToGlslImageType(this SamplerType type, AggregateType componentType)
{
string typeName = (type & SamplerType.Mask) switch

View file

@ -9,6 +9,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public TextureFlags Flags { get; }
public int Binding { get; }
public int SamplerBinding { get; }
public bool IsSeparate => SamplerBinding >= 0;
public AstTextureOperation(
Instruction inst,
@ -16,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
TextureFormat format,
TextureFlags flags,
int binding,
int samplerBinding,
int index,
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
{
@ -23,6 +27,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Format = format;
Flags = flags;
Binding = binding;
SamplerBinding = samplerBinding;
}
}
}

View file

@ -169,7 +169,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
{
return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.Index, sources);
return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources);
}
int componentsCount = BitOperations.PopCount((uint)operation.Index);

View file

@ -5,25 +5,39 @@ namespace Ryujinx.Graphics.Shader
public int Set { get; }
public int Binding { get; }
public int ArrayLength { get; }
public bool Separate { get; }
public string Name { get; }
public SamplerType Type { get; }
public TextureFormat Format { get; }
public TextureUsageFlags Flags { get; }
public TextureDefinition(int set, int binding, int arrayLength, string name, SamplerType type, TextureFormat format, TextureUsageFlags flags)
public TextureDefinition(
int set,
int binding,
int arrayLength,
bool separate,
string name,
SamplerType type,
TextureFormat format,
TextureUsageFlags flags)
{
Set = set;
Binding = binding;
ArrayLength = arrayLength;
Separate = separate;
Name = name;
Type = type;
Format = format;
Flags = flags;
}
public TextureDefinition(int set, int binding, string name, SamplerType type) : this(set, binding, 1, false, name, type, TextureFormat.Unknown, TextureUsageFlags.None)
{
}
public TextureDefinition SetFlag(TextureUsageFlags flag)
{
return new TextureDefinition(Set, Binding, ArrayLength, Name, Type, Format, Flags | flag);
return new TextureDefinition(Set, Binding, ArrayLength, Separate, Name, Type, Format, Flags | flag);
}
}
}

View file

@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.Shader
public readonly int HandleIndex;
public readonly int ArrayLength;
public readonly bool Separate;
public readonly TextureUsageFlags Flags;
public TextureDescriptor(
@ -22,6 +24,7 @@ namespace Ryujinx.Graphics.Shader
int cbufSlot,
int handleIndex,
int arrayLength,
bool separate,
TextureUsageFlags flags)
{
Binding = binding;
@ -30,6 +33,7 @@ namespace Ryujinx.Graphics.Shader
CbufSlot = cbufSlot;
HandleIndex = handleIndex;
ArrayLength = arrayLength;
Separate = separate;
Flags = flags;
}
}

View file

@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader
SeparateSamplerHandle = 1,
SeparateSamplerId = 2,
SeparateConstantSamplerHandle = 3,
Direct = 4,
}
public static class TextureHandle

View file

@ -1,6 +1,7 @@
using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
@ -31,7 +32,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp))
if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp) &&
!GenerateBindlessAccess(block, resourceManager, gpuAccessor, texOp, node))
{
// If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero.
@ -46,6 +48,88 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
private static bool GenerateBindlessAccess(
BasicBlock block,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TextureOperation texOp,
LinkedListNode<INode> node)
{
if (!gpuAccessor.QueryHostSupportsSeparateSampler())
{
// We depend on combining samplers and textures in the shader being supported for this.
return false;
}
Operand nvHandle = texOp.GetSource(0);
if (nvHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load ||
handleOp.StorageKind != StorageKind.Input)
{
// Right now, we only allow bindless access when the handle comes from a shader input.
// This is an artificial limitation to prevent it from being used in cases where it
// would have a large performance impact of loading all textures in the pool.
// It might be removed in the future, if we can mitigate the performance impact.
return false;
}
Operand textureHandle = OperandHelper.Local();
Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local();
block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, textureIndex, textureHandle, OperandHelper.Const(texturePoolLength - 1)));
texOp.SetSource(0, textureIndex);
bool hasSampler = !texOp.Inst.IsImage();
int textureBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
texOp.Flags & ~TextureFlags.Bindless,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
texturePoolLength,
hasSampler);
if (hasSampler)
{
Operand samplerIndex = OperandHelper.Local();
int samplerPoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QuerySamplerArrayLengthFromPool());
block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, samplerIndex, samplerHandle, OperandHelper.Const(samplerPoolLength - 1)));
texOp.InsertSource(1, samplerIndex);
int samplerBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
SamplerType.None,
texOp.Format,
TextureFlags.None,
0,
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
samplerPoolLength);
texOp.TurnIntoArray(textureBinding, samplerBinding);
}
else
{
texOp.TurnIntoArray(textureBinding);
}
return true;
}
private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
{
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())

View file

@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private const int HardcodedArrayLengthOgl = 4;
// 1 and 0 elements are not considered arrays anymore.
private const int MinimumArrayLength = 2;
public const int MinimumArrayLength = 2;
public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager)
{

View file

@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly HashSet<int> _usedConstantBufferBindings;
private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, SamplerType Type, TextureFormat Format);
private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format);
private struct TextureMeta
{
@ -225,7 +225,8 @@ namespace Ryujinx.Graphics.Shader.Translation
TextureFlags flags,
int cbufSlot,
int handle,
int arrayLength = 1)
int arrayLength = 1,
bool separate = false)
{
inst &= Instruction.Mask;
bool isImage = inst.IsImage();
@ -239,7 +240,18 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown;
}
int binding = GetTextureOrImageBinding(cbufSlot, handle, arrayLength, type, format, isImage, intCoords, isWrite, accurateType, coherent);
int binding = GetTextureOrImageBinding(
cbufSlot,
handle,
arrayLength,
type,
format,
isImage,
intCoords,
isWrite,
accurateType,
coherent,
separate);
_gpuAccessor.RegisterTexture(handle, cbufSlot);
@ -256,9 +268,10 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords,
bool write,
bool accurateType,
bool coherent)
bool coherent,
bool separate)
{
var dimensions = type.GetDimensions();
var dimensions = type == SamplerType.None ? 0 : type.GetDimensions();
var dict = isImage ? _usedImages : _usedTextures;
var usageFlags = TextureUsageFlags.None;
@ -290,7 +303,7 @@ namespace Ryujinx.Graphics.Shader.Translation
// For array textures, we also want to use type as key,
// since we may have texture handles stores in the same buffer, but for textures with different types.
var keyType = arrayLength > 1 ? type : SamplerType.None;
var info = new TextureInfo(cbufSlot, handle, arrayLength, keyType, format);
var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format);
var meta = new TextureMeta()
{
AccurateType = accurateType,
@ -332,6 +345,10 @@ namespace Ryujinx.Graphics.Shader.Translation
? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}"
: $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
}
else if (type == SamplerType.None)
{
nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}";
}
else
{
nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}";
@ -341,6 +358,7 @@ namespace Ryujinx.Graphics.Shader.Translation
isImage ? 3 : 2,
binding,
arrayLength,
separate,
$"{_stagePrefix}_{nameSuffix}",
meta.Type,
info.Format,
@ -495,6 +513,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot,
info.Handle,
info.ArrayLength,
info.Separate,
meta.UsageFlags));
}
@ -514,6 +533,7 @@ namespace Ryujinx.Graphics.Shader.Translation
info.CbufSlot,
info.Handle,
info.ArrayLength,
info.Separate,
meta.UsageFlags));
}
}

View file

@ -413,7 +413,7 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex)
{
int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
TextureDefinition indexBuffer = new(2, ibBinding, 1, "ib_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes;
@ -422,7 +422,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
int location = BitOperations.TrailingZeroCount(inputMap);
int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
TextureDefinition vaBuffer = new(2, binding, 1, $"vb_data{location}", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location);
@ -431,7 +431,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else if (Stage == ShaderStage.Geometry)
{
int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
TextureDefinition remapBuffer = new(2, trbBinding, 1, "trb_data", SamplerType.TextureBuffer, TextureFormat.Unknown, TextureUsageFlags.None);
TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;

View file

@ -302,12 +302,12 @@ namespace Ryujinx.Graphics.Vulkan
SubmitInfo sInfo = new()
{
SType = StructureType.SubmitInfo,
WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
WaitSemaphoreCount = !waitSemaphores.IsEmpty ? (uint)waitSemaphores.Length : 0,
PWaitSemaphores = pWaitSemaphores,
PWaitDstStageMask = pWaitDstStageMask,
CommandBufferCount = 1,
PCommandBuffers = &commandBuffer,
SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
SignalSemaphoreCount = !signalSemaphores.IsEmpty ? (uint)signalSemaphores.Length : 0,
PSignalSemaphores = pSignalSemaphores,
};

View file

@ -43,11 +43,11 @@ namespace Ryujinx.Graphics.Vulkan
int binding = segment.Binding;
int count = segment.Count;
if (setIndex == PipelineBase.UniformSetIndex)
if (IsBufferType(segment.Type))
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformBuffer,
DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
@ -56,76 +56,31 @@ namespace Ryujinx.Graphics.Vulkan
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
}
else if (setIndex == PipelineBase.StorageSetIndex)
else if (IsBufferTextureType(segment.Type))
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageBuffer,
DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
else if (setIndex == PipelineBase.TextureSetIndex)
else
{
if (segment.Type != ResourceType.BufferTexture)
entries[seg] = new DescriptorUpdateTemplateEntry()
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.CombinedImageSampler,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
DescriptorType = segment.Type.Convert(),
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
}
else if (setIndex == PipelineBase.ImageSetIndex)
{
if (segment.Type != ResourceType.BufferImage)
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageImage,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
else
{
entries[seg] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.StorageTexelBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<BufferView>()
};
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
}
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
}
}
@ -237,6 +192,16 @@ namespace Ryujinx.Graphics.Vulkan
Template = result;
}
private static bool IsBufferType(ResourceType type)
{
return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer;
}
private static bool IsBufferTextureType(ResourceType type)
{
return type == ResourceType.BufferTexture || type == ResourceType.BufferImage;
}
public unsafe void Dispose()
{
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View file

@ -706,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsCubemapView: !IsAmdGcn,
supportsNonConstantTextureOffset: false,
supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(),
supportsSeparateSampler: true,
supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,

View file

@ -570,7 +570,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
}
else
{
serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetSpan(copySrc, (int)copySize));
serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize));
}
if (clientResult != Result.Success)
@ -858,7 +858,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
}
else
{
clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetSpan(copySrc, (int)copySize));
clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize));
}
}

View file

@ -2,6 +2,7 @@ using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
@ -34,6 +35,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
}
}
/// <inheritdoc/>
protected override ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size)
{
return _cpuMemory.GetReadOnlySequence(va, size);
}
/// <inheritdoc/>
protected override ReadOnlySpan<byte> GetSpan(ulong va, int size)
{
@ -247,6 +254,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
_cpuMemory.SignalMemoryTracking(va, size, write);
}
/// <inheritdoc/>
protected override void Write(ulong va, ReadOnlySequence<byte> data)
{
_cpuMemory.Write(va, data);
}
/// <inheritdoc/>
protected override void Write(ulong va, ReadOnlySpan<byte> data)
{

View file

@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
@ -1568,7 +1569,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
while (size > 0)
{
ulong copySize = 0x100000; // Copy chunck size. Any value will do, moderate sizes are recommended.
ulong copySize = int.MaxValue;
if (copySize > size)
{
@ -1577,11 +1578,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (toServer)
{
currentProcess.CpuMemory.Write(serverAddress, GetSpan(clientAddress, (int)copySize));
currentProcess.CpuMemory.Write(serverAddress, GetReadOnlySequence(clientAddress, (int)copySize));
}
else
{
Write(clientAddress, currentProcess.CpuMemory.GetSpan(serverAddress, (int)copySize));
Write(clientAddress, currentProcess.CpuMemory.GetReadOnlySequence(serverAddress, (int)copySize));
}
serverAddress += copySize;
@ -1911,9 +1912,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue);
ulong copySize = addressRounded <= endAddr ? addressRounded - address : size;
var data = srcPageTable.GetSpan(addressTruncated + unusedSizeBefore, (int)copySize);
var data = srcPageTable.GetReadOnlySequence(addressTruncated + unusedSizeBefore, (int)copySize);
Context.Memory.Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data);
((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data);
firstPageFillAddress += unusedSizeBefore + copySize;
@ -1977,9 +1978,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if (send)
{
ulong copySize = endAddr - endAddrTruncated;
var data = srcPageTable.GetSpan(endAddrTruncated, (int)copySize);
var data = srcPageTable.GetReadOnlySequence(endAddrTruncated, (int)copySize);
Context.Memory.Write(GetDramAddressFromPa(dstLastPagePa), data);
((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstLastPagePa), data);
lastPageFillAddr += copySize;
@ -2943,6 +2944,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <param name="pageList">Page list where the ranges will be added</param>
protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList);
/// <summary>
/// Gets a read-only sequence of data from CPU mapped memory.
/// </summary>
/// <remarks>
/// Allows reading non-contiguous memory without first copying it to a newly allocated single contiguous block.
/// </remarks>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <returns>A read-only sequence of the data</returns>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size);
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
/// </summary>
@ -2952,7 +2965,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// </remarks>
/// <param name="va">Virtual address of the data</param>
/// <param name="size">Size of the data</param>
/// <param name="tracked">True if read tracking is triggered on the span</param>
/// <returns>A read-only span of the data</returns>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract ReadOnlySpan<byte> GetSpan(ulong va, int size);
@ -3060,6 +3072,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
/// <param name="size">Size of the region</param>
protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write);
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
protected abstract void Write(ulong va, ReadOnlySequence<byte> data);
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>

View file

@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
if (cpuContext != null)
if (!cpuContext.IsEmpty)
{
errorReport.AppendLine("CPU Context:");

View file

@ -23,8 +23,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
newState.Buttons = (MouseButton)buttons;
newState.X = mouseX;
newState.Y = mouseY;
newState.DeltaX = mouseX - previousEntry.DeltaX;
newState.DeltaY = mouseY - previousEntry.DeltaY;
newState.DeltaX = mouseX - previousEntry.X;
newState.DeltaY = mouseY - previousEntry.Y;
newState.WheelDeltaX = scrollX;
newState.WheelDeltaY = scrollY;
newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None;

View file

@ -13,10 +13,10 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
ResultCode OnTransact(uint code, uint flags, ReadOnlySpan<byte> inputParcel, Span<byte> outputParcel)
{
Parcel inputParcelReader = new(inputParcel.ToArray());
using Parcel inputParcelReader = new(inputParcel);
// TODO: support objects?
Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
using Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf<ParcelHeader>()), 0);
string inputInterfaceToken = inputParcelReader.ReadInterfaceToken();

View file

@ -1,7 +1,9 @@
using Ryujinx.Common;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -9,13 +11,13 @@ using System.Text;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
class Parcel
sealed class Parcel : IDisposable
{
private readonly byte[] _rawData;
private readonly IMemoryOwner<byte> _rawDataOwner;
private Span<byte> Raw => new(_rawData);
private Span<byte> Raw => _rawDataOwner.Memory.Span;
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(_rawData)[0];
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];
private Span<byte> Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize);
@ -24,9 +26,11 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
private int _payloadPosition;
private int _objectPosition;
public Parcel(byte[] rawData)
private bool _isDisposed;
public Parcel(ReadOnlySpan<byte> data)
{
_rawData = rawData;
_rawDataOwner = ByteMemoryPool.RentCopy(data);
_payloadPosition = 0;
_objectPosition = 0;
@ -36,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
_rawData = new byte[BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)];
_rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4));
Header.PayloadSize = payloadSize;
Header.ObjectsSize = objectsSize;
@ -132,7 +136,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
// TODO: figure out what this value is
WriteInplaceObject(new byte[4] { 0, 0, 0, 0 });
Span<byte> fourBytes = stackalloc byte[4];
WriteInplaceObject(fourBytes);
}
public AndroidStrongPointer<T> ReadStrongPointer<T>() where T : unmanaged, IFlattenable
@ -219,5 +225,15 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf<ParcelHeader>())];
}
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
_rawDataOwner.Dispose();
}
}
}
}

View file

@ -250,7 +250,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
Parcel parcel = new(0x28, 0x4);
using Parcel parcel = new(0x28, 0x4);
parcel.WriteObject(producer, "dispdrv\0");
@ -288,7 +288,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
Parcel parcel = new(0x28, 0x4);
using Parcel parcel = new(0x28, 0x4);
parcel.WriteObject(producer, "dispdrv\0");

View file

@ -143,7 +143,7 @@ namespace Ryujinx.HLE.HOS
try
{
ControllerKeys pressedKeys = (ControllerKeys)Thread.VolatileRead(ref _pressedKeys);
ControllerKeys pressedKeys = (ControllerKeys)Volatile.Read(ref _pressedKeys);
program.Process.TamperedCodeMemory = false;
program.Execute(pressedKeys);
@ -175,14 +175,14 @@ namespace Ryujinx.HLE.HOS
{
if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld)
{
Thread.VolatileWrite(ref _pressedKeys, (long)input.Buttons);
Volatile.Write(ref _pressedKeys, (long)input.Buttons);
return;
}
}
// Clear the input because player one is not conected.
Thread.VolatileWrite(ref _pressedKeys, 0);
Volatile.Write(ref _pressedKeys, 0);
}
}
}

View file

@ -487,38 +487,35 @@ namespace Ryujinx.Input.HLE
return value;
}
public KeyboardInput? GetHLEKeyboardInput()
public static KeyboardInput GetHLEKeyboardInput(IGamepadDriver KeyboardDriver)
{
if (_gamepad is IKeyboard keyboard)
var keyboard = KeyboardDriver.GetGamepad("0") as IKeyboard;
KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot();
KeyboardInput hidKeyboard = new()
{
KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot();
Modifier = 0,
Keys = new ulong[0x4],
};
KeyboardInput hidKeyboard = new()
{
Modifier = 0,
Keys = new ulong[0x4],
};
foreach (HLEKeyboardMappingEntry entry in _keyMapping)
{
ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL;
foreach (HLEKeyboardMappingEntry entry in _keyMapping)
{
ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL;
hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40));
}
foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping)
{
int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0;
hidKeyboard.Modifier |= value << entry.Target;
}
return hidKeyboard;
hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40));
}
return null;
}
foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping)
{
int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0;
hidKeyboard.Modifier |= value << entry.Target;
}
return hidKeyboard;
}
protected virtual void Dispose(bool disposing)
{

View file

@ -231,11 +231,6 @@ namespace Ryujinx.Input.HLE
var altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default;
motionState = (controller.GetHLEMotionState(), altMotionState);
if (_enableKeyboard)
{
hleKeyboardInput = controller.GetHLEKeyboardInput();
}
}
else
{
@ -257,6 +252,11 @@ namespace Ryujinx.Input.HLE
}
}
if (!_blockInputUpdates && _enableKeyboard)
{
hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver);
}
_device.Hid.Npads.Update(hleInputStates);
_device.Hid.Npads.UpdateSixAxis(hleMotionStates);

View file

@ -1,9 +1,25 @@
using System;
using System.Buffers;
namespace Ryujinx.Memory
{
public interface IWritableBlock
{
/// <summary>
/// Writes data to CPU mapped memory, with write tracking.
/// </summary>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySequence<byte> data)
{
foreach (ReadOnlyMemory<byte> segment in data)
{
Write(va, segment.Span);
va += (ulong)segment.Length;
}
}
void Write(ulong va, ReadOnlySpan<byte> data);
void WriteUntracked(ulong va, ReadOnlySpan<byte> data) => Write(va, data);

View file

@ -8,6 +8,11 @@ namespace Ryujinx.Memory
private readonly T* _pointer;
private readonly int _length;
public NativeMemoryManager(nuint pointer, int length)
: this((T*)pointer, length)
{
}
public NativeMemoryManager(T* pointer, int length)
{
_pointer = pointer;

View file

@ -58,10 +58,20 @@ namespace Ryujinx.ShaderTools
return MemoryMarshal.Cast<byte, ulong>(new ReadOnlySpan<byte>(_data)[(int)address..]);
}
public int QuerySamplerArrayLengthFromPool()
{
return DefaultArrayLength;
}
public int QueryTextureArrayLengthFromBuffer(int slot)
{
return DefaultArrayLength;
}
public int QueryTextureArrayLengthFromPool()
{
return DefaultArrayLength;
}
}
private class Options

View file

@ -388,14 +388,14 @@ namespace Ryujinx.Tests.Memory
{
rwLock.AcquireReaderLock();
int originalValue = Thread.VolatileRead(ref value);
int originalValue = Volatile.Read(ref value);
count++;
// Spin a bit.
for (int i = 0; i < 100; i++)
{
if (Thread.VolatileRead(ref readersAllowed) == 0)
if (Volatile.Read(ref readersAllowed) == 0)
{
error = true;
running = false;
@ -403,7 +403,7 @@ namespace Ryujinx.Tests.Memory
}
// Should not change while the lock is held.
if (Thread.VolatileRead(ref value) != originalValue)
if (Volatile.Read(ref value) != originalValue)
{
error = true;
running = false;

View file

@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 50;
public const int CurrentVersion = 51;
/// <summary>
/// Version of the configuration file format
@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public bool ShowConfirmExit { get; set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>
public bool RememberWindowState { get; set; }
/// <summary>
/// Enables hardware-accelerated rendering for Avalonia
/// </summary>

View file

@ -626,6 +626,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public ReactiveObject<bool> ShowConfirmExit { get; private set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>
public ReactiveObject<bool> RememberWindowState { get; private set; }
/// <summary>
/// Enables hardware-accelerated rendering for Avalonia
/// </summary>
@ -647,6 +652,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>();
RememberWindowState = new ReactiveObject<bool>();
EnableHardwareAcceleration = new ReactiveObject<bool>();
HideCursor = new ReactiveObject<HideCursorMode>();
}
@ -684,6 +690,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
ShowConfirmExit = ShowConfirmExit,
RememberWindowState = RememberWindowState,
EnableHardwareAcceleration = EnableHardwareAcceleration,
HideCursor = HideCursor,
EnableVsync = Graphics.EnableVsync,
@ -792,6 +799,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true;
RememberWindowState.Value = true;
EnableHardwareAcceleration.Value = true;
HideCursor.Value = HideCursorMode.OnIdle;
Graphics.EnableVsync.Value = true;
@ -1459,6 +1467,15 @@ namespace Ryujinx.UI.Common.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 51)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 51.");
configurationFileFormat.RememberWindowState = true;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@ -1489,6 +1506,7 @@ namespace Ryujinx.UI.Common.Configuration
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit;
RememberWindowState.Value = configurationFileFormat.RememberWindowState;
EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration;
HideCursor.Value = configurationFileFormat.HideCursor;
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;

View file

@ -94,6 +94,17 @@ namespace Ryujinx.Ava
private long _lastCursorMoveTime;
private bool _isCursorInRenderer = true;
private bool _ignoreCursorState = false;
private enum CursorStates
{
CursorIsHidden,
CursorIsVisible,
ForceChangeCursor
};
private CursorStates _cursorState = !ConfigurationState.Instance.Hid.EnableMouse.Value ?
CursorStates.CursorIsVisible : CursorStates.CursorIsHidden;
private bool _isStopped;
private bool _isActive;
@ -201,23 +212,65 @@ namespace Ryujinx.Ava
private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e)
{
if (!_viewModel.IsActive)
{
_isCursorInRenderer = false;
_ignoreCursorState = false;
return;
}
if (sender is MainWindow window)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
if (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle)
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
var point = e.GetCurrentPoint(window).Position;
var bounds = RendererHost.EmbeddedWindow.Bounds;
var windowYOffset = bounds.Y + window.MenuBarHeight;
var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1;
if (!_viewModel.ShowMenuAndStatusBar)
{
windowYOffset -= window.MenuBarHeight;
windowYLimit += window.StatusBarHeight + 1;
}
_isCursorInRenderer = point.X >= bounds.X &&
point.X <= bounds.Width + bounds.X &&
point.Y >= bounds.Y &&
point.Y <= bounds.Height + bounds.Y;
Math.Ceiling(point.X) <= (int)window.Bounds.Width &&
point.Y >= windowYOffset &&
point.Y <= windowYLimit &&
!_viewModel.IsSubMenuOpen;
_ignoreCursorState = false;
}
}
private void TopLevel_PointerExited(object sender, PointerEventArgs e)
{
_isCursorInRenderer = false;
if (sender is MainWindow window)
{
var point = e.GetCurrentPoint(window).Position;
var bounds = RendererHost.EmbeddedWindow.Bounds;
var windowYOffset = bounds.Y + window.MenuBarHeight;
var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1;
if (!_viewModel.ShowMenuAndStatusBar)
{
windowYOffset -= window.MenuBarHeight;
windowYLimit += window.StatusBarHeight + 1;
}
_ignoreCursorState = (point.X == bounds.X ||
Math.Ceiling(point.X) == (int)window.Bounds.Width) &&
point.Y >= windowYOffset &&
point.Y <= windowYLimit;
}
_cursorState = CursorStates.ForceChangeCursor;
}
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
@ -245,9 +298,14 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows())
{
SetCursor(_defaultCursorWin);
if (_cursorState != CursorStates.CursorIsHidden && !_ignoreCursorState)
{
SetCursor(_defaultCursorWin);
}
}
});
_cursorState = CursorStates.CursorIsVisible;
}
private void HideCursor()
@ -261,6 +319,8 @@ namespace Ryujinx.Ava
SetCursor(_invisibleCursorWin);
}
});
_cursorState = CursorStates.CursorIsHidden;
}
private void SetRendererWindowSize(Size size)
@ -523,6 +583,8 @@ namespace Ryujinx.Ava
{
_lastCursorMoveTime = Stopwatch.GetTimestamp();
}
_cursorState = CursorStates.ForceChangeCursor;
}
public async Task<bool> LoadGuestApplication()
@ -1037,38 +1099,32 @@ namespace Ryujinx.Ava
if (_viewModel.IsActive)
{
if (_isCursorInRenderer)
bool isCursorVisible = true;
if (_isCursorInRenderer && !_viewModel.ShowLoadProgress)
{
if (ConfigurationState.Instance.Hid.EnableMouse)
if (ConfigurationState.Instance.Hid.EnableMouse.Value)
{
HideCursor();
isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never;
}
else
{
switch (ConfigurationState.Instance.HideCursor.Value)
{
case HideCursorMode.Never:
ShowCursor();
break;
case HideCursorMode.OnIdle:
if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency)
{
HideCursor();
}
else
{
ShowCursor();
}
break;
case HideCursorMode.Always:
HideCursor();
break;
}
isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never ||
(ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle &&
Stopwatch.GetTimestamp() - _lastCursorMoveTime < CursorHideIdleTime * Stopwatch.Frequency);
}
}
else
if (_cursorState != (isCursorVisible ? CursorStates.CursorIsVisible : CursorStates.CursorIsHidden))
{
ShowCursor();
if (isCursorVisible)
{
ShowCursor();
}
else
{
HideCursor();
}
}
Dispatcher.UIThread.Post(() =>
@ -1154,7 +1210,7 @@ namespace Ryujinx.Ava
// Touchscreen.
bool hasTouch = false;
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse)
if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value)
{
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
}

View file

@ -30,6 +30,10 @@
"MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarView": "_View",
"MenuBarViewWindow": "Window Size",
"MenuBarViewWindow720": "720p",
"MenuBarViewWindow1080": "1080p",
"MenuBarHelp": "_Help",
"MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About",
@ -92,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Hide Cursor:",
"SettingsTabGeneralHideCursorNever": "Never",
"SettingsTabGeneralHideCursorOnIdle": "On Idle",

View file

@ -19,8 +19,8 @@ namespace Ryujinx.Ava.UI.Helpers
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
{ Key.ControlRight, LocaleKeys.KeyControlRight },
{ Key.AltLeft, LocaleKeys.KeyControlLeft },
{ Key.AltRight, LocaleKeys.KeyControlRight },
{ Key.AltLeft, LocaleKeys.KeyAltLeft },
{ Key.AltRight, LocaleKeys.KeyAltRight },
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
{ Key.WinRight, LocaleKeys.KeyWinRight },
{ Key.Up, LocaleKeys.KeyUp },

View file

@ -111,8 +111,5 @@ namespace Ryujinx.Ava.UI.Helpers
[LibraryImport("user32.dll", SetLastError = true)]
public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value);
[LibraryImport("user32.dll", SetLastError = true)]
public static partial IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, int value);
}
}

View file

@ -157,7 +157,7 @@ namespace Ryujinx.Ava.UI.Renderer
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
style = ClassStyles.CsOwndc,
lpszClassName = Marshal.StringToHGlobalUni(_className),
hCursor = CreateArrowCursor(),
hCursor = CreateArrowCursor()
};
RegisterClassEx(ref wndClassEx);

View file

@ -104,6 +104,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private double _windowHeight;
private bool _isActive;
private bool _isSubMenuOpen;
public ApplicationData ListSelectedApplication;
public ApplicationData GridSelectedApplication;
@ -317,6 +318,17 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool IsSubMenuOpen
{
get => _isSubMenuOpen;
set
{
_isSubMenuOpen = value;
OnPropertyChanged();
}
}
public bool ShowAll
{
get => _showAll;

View file

@ -131,6 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; }
public bool RememberWindowState { get; set; }
public int HideCursor { get; set; }
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
@ -390,6 +391,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
HideCursor = (int)config.HideCursor.Value;
GameDirectories.Clear();
@ -475,6 +477,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.RememberWindowState.Value = RememberWindowState;
config.HideCursor.Value = (HideCursorMode)HideCursor;
if (_directoryChanged)

View file

@ -1,4 +1,4 @@
<UserControl
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -16,7 +16,8 @@
Name="Menu"
Height="35"
Margin="0"
HorizontalAlignment="Left">
HorizontalAlignment="Left"
IsOpen="{Binding IsSubMenuOpen, Mode=OneWayToSource}">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
@ -185,6 +186,12 @@
<MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/>
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarView}">
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarViewWindow}">
<MenuItem Header="{locale:Locale MenuBarViewWindow720}" Tag="720" Click="ChangeWindowSize_Click" />
<MenuItem Header="{locale:Locale MenuBarViewWindow1080}" Tag="1080" Click="ChangeWindowSize_Click" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Name="UpdateMenuItem"

View file

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
@ -211,6 +212,40 @@ namespace Ryujinx.Ava.UI.Views.Main
}
}
private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e)
{
if (sender is MenuItem item)
{
int height;
int width;
switch (item.Tag)
{
case "720":
height = 720;
width = 1280;
break;
case "1080":
height = 1080;
width = 1920;
break;
default:
throw new ArgumentNullException($"Invalid Tag for {item}");
}
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.WindowState = WindowState.Normal;
height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight;
Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height));
});
}
}
public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true))

View file

@ -36,6 +36,9 @@
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>
<CheckBox IsChecked="{Binding RememberWindowState}">
<TextBlock Text="{locale:Locale SettingsTabGeneralRememberWindowState}" />
</CheckBox>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralHideCursor}"

View file

@ -56,6 +56,9 @@ namespace Ryujinx.Ava.UI.Windows
public static bool ShowKeyErrorOnLoad { get; set; }
public ApplicationLibrary ApplicationLibrary { get; set; }
public readonly double StatusBarHeight;
public readonly double MenuBarHeight;
public MainWindow()
{
ViewModel = new MainWindowViewModel();
@ -64,8 +67,6 @@ namespace Ryujinx.Ava.UI.Windows
DataContext = ViewModel;
SetWindowSizePosition();
InitializeComponent();
Load();
@ -74,10 +75,14 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.Title = $"Ryujinx {Program.Version}";
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight;
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
MenuBarHeight = MenuBar.MinHeight;
double barHeight = MenuBarHeight + StatusBarHeight;
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
Width /= Program.WindowScaleFactor;
SetWindowSizePosition();
if (Program.PreviewerDetached)
{
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
@ -319,6 +324,17 @@ namespace Ryujinx.Ava.UI.Windows
private void SetWindowSizePosition()
{
if (!ConfigurationState.Instance.RememberWindowState)
{
ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight) * Program.WindowScaleFactor;
ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor;
WindowState = WindowState.Normal;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
return;
}
PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
@ -353,14 +369,18 @@ namespace Ryujinx.Ava.UI.Windows
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
// Only save rectangle properties if the window is not in a maximized state.
if (WindowState != WindowState.Maximized)
{
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
}
MainWindowViewModel.SaveConfig();
}
@ -472,7 +492,10 @@ namespace Ryujinx.Ava.UI.Windows
return;
}
SaveWindowSizePosition();
if (ConfigurationState.Instance.RememberWindowState)
{
SaveWindowSizePosition();
}
ApplicationLibrary.CancelLoading();
InputManager.Dispose();