Compare commits

...

7 Commits

Author SHA1 Message Date
492a046335 Vulkan: Buffer Mirrors for MacOS performance (#4899)
* Initial implementation of buffer mirrors

Generally slower right now, goal is to reduce render passes in games that do inline updates

Fix support buffer mirrors

Reintroduce vertex buffer mirror

Add storage buffer support

Optimisation part 1

More optimisation

Avoid useless data copies.

Remove unused cbIndex stuff

Properly set write flag for storage buffers.

Fix minor issues

Not sure why this was here.

Fix BufferRangeList

Fix some big issues

Align storage buffers rather than getting full buffer as a range

Improves mirrorability of read-only storage buffers

Increase staging buffer size, as it now contains mirrors

Fix some issues with buffers not updating

Fix buffer SetDataUnchecked offset for one of the paths when using mirrors

Fix buffer mirrors interaction with buffer textures

Fix mirror rebinding

Move GetBuffer calls on indirect draws before BeginRenderPass to avoid draws without render pass

Fix mirrors rebase

Fix rebase 2023

* Fix crash when using stale vertex buffer

Similar to `Get` with a size that's too large, just treat it as a clamp.

* Explicitly set support buffer as mirrorable

* Address feedback

* Remove unused fragment of MVK workaround

* Replace logging for staging buffer OOM

* Address format issues

* Address more format issues

* Mini cleanup

* Address more things

* Rename BufferRangeList

* Support bounding range for ClearMirrors and UploadPendingData

* Add maximum size for vertex buffer mirrors

* Enable index buffer mirrors

Enabled on all platforms for the IbStreamer.

* Feedback

* Remove mystery BufferCache change

Probably macos related?

* Fix mirrors not creating when staging buffer is empty.

* Change log level to debug
2023-08-14 14:18:47 -03:00
550fd4a733 Simplify resolution scale updates (#5541) 2023-08-14 13:57:39 -03:00
33f544fd92 GPU: Track basic buffer copies that modify texture memory (#5554)
This branch changes the buffer copy fast path to notify memory tracking for all resources that aren't buffers. This fixes cases where games would copy buffer data directly into texture memory, which before would only work if the texture did not already exist. I imagine this happens when the guest driver is moving data between allocations or uploading it.

Since this only affects the fast path, cases where the source data has been modified from GPU (fast path copy destination doesn't count) will still fail to notify the texture, though I don't imagine games will do this. This should be resolved in future.

This should fix some texture issues with guest OpenGL games on switch, such as Dragon Quest Builders.

This may also be useful in future for games that move shader data around memory, if we end up using memory tracking for those.
2023-08-14 08:41:11 +02:00
b423197619 Delete ShaderConfig and organize shader resources/definitions better (#5509)
* Move some properties out of ShaderConfig

* Stop using ShaderConfig on backends

* Replace ShaderConfig usages on Translator and passes

* Move remaining properties out of ShaderConfig and delete ShaderConfig

* Remove ResourceManager property from TranslatorContext

* Move Rewriter passes to separate transform pass files

* Fix TransformPasses.RunPass on cases where a node is removed

* Move remaining ClipDistancePrimitivesWritten and UsedFeatures updates to decode stage

* Reduce excessive parameter passing a bit by using structs more

* Remove binding parameter from ShaderProperties methods since it is redundant

* Replace decoder instruction checks with switch statement

* Put GLSL on the same plan as SPIR-V for input/output declaration

* Stop mutating TranslatorContext state when Translate is called

* Pass most of the graphics state using a struct instead of individual query methods

* Auto-format

* Auto-format

* Add backend logging interface

* Auto-format

* Remove unnecessary use of interpolated strings

* Remove more modifications of AttributeUsage after decode

* PR feedback

* gl_Layer is not supported on compute
2023-08-13 22:26:42 -03:00
8edfb2bc7b "static readonly" constants should be "const" instead (#5560)
* "static readonly" constants should be "const" instead

* change fields to PascalCase
2023-08-13 19:07:57 -03:00
ddefb4fff4 Remove animations on listbox items (#5563) 2023-08-13 22:40:40 +02:00
2efd74b9cb Ava UI: Make some settings methods async (#5332)
* Ava: Asynchronously load Vulkan device settings items.

* Sound checks, timezones and network interface async

* Refresh UI items once awaited tasks complete

* Remove unused dep

* Timezone UI update

* Use UIThread dispatcher for thread-unsafe collections + simplify GPU collection.

* Remove empty lines

* Remove unused string

* Dispatch property changes

* format changes

* format 2

* Use Tasks instead of async void

* Make NetworkInterfaceIndex access thread safe.
2023-08-12 22:43:03 +02:00
105 changed files with 4041 additions and 2653 deletions

View File

@ -32,7 +32,7 @@ namespace ARMeilleure.Instructions
15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0, // S
};
public static readonly long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0;
public const long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0;
public static ulong X86GetGf2p8LogicalShiftLeft(int shift)
{

View File

@ -335,28 +335,6 @@
Value="{DynamicResource AppListBackgroundColor}" />
<Setter Property="BorderThickness"
Value="2"/>
<Style.Animations>
<Animation Duration="0:0:0.7">
<KeyFrame Cue="0%">
<Setter Property="MaxHeight"
Value="0" />
<Setter Property="Opacity"
Value="0.0" />
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="MaxHeight"
Value="1000" />
<Setter Property="Opacity"
Value="0.3" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="MaxHeight"
Value="1000" />
<Setter Property="Opacity"
Value="1.0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background"

View File

@ -37,7 +37,7 @@ namespace Ryujinx.Modules
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private static readonly int _connectionCount = 4;
private const int ConnectionCount = 4;
private static string _buildVer;
private static string _platformExt;
@ -344,22 +344,22 @@ namespace Ryujinx.Modules
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
// Multi-Threaded Updater
long chunkSize = _buildSize / _connectionCount;
long remainderChunk = _buildSize % _connectionCount;
long chunkSize = _buildSize / ConnectionCount;
long remainderChunk = _buildSize % ConnectionCount;
int completedRequests = 0;
int totalProgressPercentage = 0;
int[] progressPercentage = new int[_connectionCount];
int[] progressPercentage = new int[ConnectionCount];
List<byte[]> list = new(_connectionCount);
List<WebClient> webClients = new(_connectionCount);
List<byte[]> list = new(ConnectionCount);
List<WebClient> webClients = new(ConnectionCount);
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
list.Add(Array.Empty<byte>());
}
for (int i = 0; i < _connectionCount; i++)
for (int i = 0; i < ConnectionCount; i++)
{
#pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
@ -368,7 +368,7 @@ namespace Ryujinx.Modules
webClients.Add(client);
if (i == _connectionCount - 1)
if (i == ConnectionCount - 1)
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
}
@ -385,7 +385,7 @@ namespace Ryujinx.Modules
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / _connectionCount, TaskDialogProgressState.Normal);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
};
client.DownloadDataCompleted += (_, args) =>
@ -404,10 +404,10 @@ namespace Ryujinx.Modules
list[index] = args.Result;
Interlocked.Increment(ref completedRequests);
if (Equals(completedRequests, _connectionCount))
if (Equals(completedRequests, ConnectionCount))
{
byte[] mergedFileBytes = new byte[_buildSize];
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < _connectionCount; connectionIndex++)
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
{
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
destinationOffset += list[connectionIndex].Length;

View File

@ -1,7 +1,6 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using DynamicData;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
@ -24,6 +23,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
namespace Ryujinx.Ava.UI.ViewModels
@ -44,7 +44,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private float _volume;
private bool _isVulkanAvailable = true;
private bool _directoryChanged;
private List<string> _gpuIds = new();
private readonly List<string> _gpuIds = new();
private KeyboardHotkeys _keyboardHotkeys;
private int _graphicsBackendIndex;
private string _customThemePath;
@ -278,7 +278,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_contentManager = contentManager;
if (Program.PreviewerDetached)
{
LoadTimeZones();
Task.Run(LoadTimeZones);
}
}
@ -290,27 +290,34 @@ namespace Ryujinx.Ava.UI.ViewModels
_validTzRegions = new List<string>();
_networkInterfaces = new Dictionary<string, string>();
CheckSoundBackends();
PopulateNetworkInterfaces();
Task.Run(CheckSoundBackends);
Task.Run(PopulateNetworkInterfaces);
if (Program.PreviewerDetached)
{
LoadAvailableGpus();
Task.Run(LoadAvailableGpus);
LoadCurrentConfiguration();
}
}
public void CheckSoundBackends()
public async Task CheckSoundBackends()
{
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
await Dispatcher.UIThread.InvokeAsync(() =>
{
OnPropertyChanged(nameof(IsOpenAlEnabled));
OnPropertyChanged(nameof(IsSoundIoEnabled));
OnPropertyChanged(nameof(IsSDL2Enabled));
});
}
private void LoadAvailableGpus()
private async Task LoadAvailableGpus()
{
_gpuIds = new List<string>();
List<string> names = new();
AvailableGpus.Clear();
var devices = VulkanRenderer.GetPhysicalDevices();
if (devices.Length == 0)
@ -322,16 +329,23 @@ namespace Ryujinx.Ava.UI.ViewModels
{
foreach (var device in devices)
{
_gpuIds.Add(device.Id);
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
await Dispatcher.UIThread.InvokeAsync(() =>
{
_gpuIds.Add(device.Id);
AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" });
});
}
}
AvailableGpus.Clear();
AvailableGpus.AddRange(names.Select(x => new ComboBoxItem { Content = x }));
// GPU configuration needs to be loaded during the async method or it will always return 0.
PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ?
_gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0;
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex)));
}
public void LoadTimeZones()
public async Task LoadTimeZones()
{
_timeZoneContentManager = new TimeZoneContentManager();
@ -344,21 +358,34 @@ namespace Ryujinx.Ava.UI.ViewModels
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
await Dispatcher.UIThread.InvokeAsync(() =>
{
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
_validTzRegions.Add(location);
_validTzRegions.Add(location);
});
}
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone)));
}
private void PopulateNetworkInterfaces()
private async Task PopulateNetworkInterfaces()
{
_networkInterfaces.Clear();
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
await Dispatcher.UIThread.InvokeAsync(() =>
{
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
});
}
// Network interface index needs to be loaded during the async method or it will always return 0.
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex)));
}
public void ValidateAndSetTimeZone(string location)
@ -416,7 +443,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Graphics
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
PreferredGpuIndex = _gpuIds.Contains(config.Graphics.PreferredGpu) ? _gpuIds.IndexOf(config.Graphics.PreferredGpu) : 0;
// Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus().
EnableShaderCache = config.Graphics.EnableShaderCache;
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
EnableMacroHLE = config.Graphics.EnableMacroHLE;
@ -437,6 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Network
EnableInternetAccess = config.System.EnableInternetAccess;
// LAN interface index is loaded asynchronously in PopulateNetworkInterfaces()
// Logging
EnableFileLog = config.Logger.EnableFileLog;
@ -450,8 +478,6 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(config.Multiplayer.LanInterfaceId.Value);
}
public void SaveSettings()

View File

@ -28,8 +28,8 @@ namespace Ryujinx.Common.SystemInterop
[LibraryImport(X11LibraryName)]
private static partial int XCloseDisplay(IntPtr display);
private static readonly double _standardDpiScale = 96.0;
private static readonly double _maxScaleFactor = 1.25;
private const double StandardDpiScale = 96.0;
private const double MaxScaleFactor = 1.25;
/// <summary>
/// Marks the application as DPI-Aware when running on the Windows operating system.
@ -90,7 +90,7 @@ namespace Ryujinx.Common.SystemInterop
{
double userDpiScale = GetActualScaleFactor();
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
return Math.Min(userDpiScale / StandardDpiScale, MaxScaleFactor);
}
}
}

View File

@ -4,5 +4,6 @@ namespace Ryujinx.Graphics.GAL
{
Default,
FlushPersistent,
Stream
}
}

View File

@ -10,12 +10,14 @@ namespace Ryujinx.Graphics.GAL
public int Offset { get; }
public int Size { get; }
public bool Write { get; }
public BufferRange(BufferHandle handle, int offset, int size)
public BufferRange(BufferHandle handle, int offset, int size, bool write = false)
{
Handle = handle;
Offset = offset;
Size = size;
Write = write;
}
}
}

View File

@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
internal BufferRange MapBufferRange(BufferRange range)
{
return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size);
return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size, range.Write);
}
internal Span<BufferRange> MapBufferRanges(Span<BufferRange> ranges)
@ -131,7 +131,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
result = BufferHandle.Null;
}
range = new BufferRange(result, range.Offset, range.Size);
range = new BufferRange(result, range.Offset, range.Size, range.Write);
}
}
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
result = BufferHandle.Null;
}
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size, range.Write));
}
}
@ -176,7 +176,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
result = BufferHandle.Null;
}
range = new BufferRange(result, range.Offset, range.Size);
range = new BufferRange(result, range.Offset, range.Size, range.Write);
ranges[i] = new VertexBufferDescriptor(range, ranges[i].Stride, ranges[i].Divisor);
}

View File

@ -171,7 +171,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_inlineIndexBuffer == BufferHandle.Null)
{
_inlineIndexBuffer = renderer.CreateBuffer(size);
_inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream);
_inlineIndexBufferSize = size;
}
else if (_inlineIndexBufferSize < size)
@ -179,7 +179,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
BufferHandle oldBuffer = _inlineIndexBuffer;
int oldSize = _inlineIndexBufferSize;
_inlineIndexBuffer = renderer.CreateBuffer(size);
_inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream);
_inlineIndexBufferSize = size;
renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize);

View File

@ -61,8 +61,6 @@ namespace Ryujinx.Graphics.Gpu.Image
private int _textureBufferIndex;
private readonly float[] _scales;
private bool _scaleChanged;
private int _lastFragmentTotal;
/// <summary>
@ -72,14 +70,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="channel">The GPU channel that the texture bindings manager belongs to</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="scales">Array where the scales for the currently bound textures are stored</param>
/// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
public TextureBindingsManager(
GpuContext context,
GpuChannel channel,
TexturePoolCache texturePoolCache,
SamplerPoolCache samplerPoolCache,
float[] scales,
bool isCompute)
{
_context = context;
@ -87,7 +83,6 @@ namespace Ryujinx.Graphics.Gpu.Image
_texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache;
_scales = scales;
_isCompute = isCompute;
int stages = isCompute ? 1 : Constants.ShaderStages;
@ -239,12 +234,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (result != _scales[index])
{
_scaleChanged = true;
_scales[index] = result;
}
_context.SupportBufferUpdater.UpdateRenderScale(index, result);
return changed;
}
@ -290,11 +280,6 @@ namespace Ryujinx.Graphics.Gpu.Image
// - Vertex stage has bindings that require scale.
// - Fragment stage binding count has been updated since last render scale update.
_scaleChanged = true;
}
if (_scaleChanged)
{
if (!_isCompute)
{
total += fragmentTotal; // Add the fragment bindings to the total.
@ -302,9 +287,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_lastFragmentTotal = fragmentTotal;
_context.SupportBufferUpdater.UpdateRenderScale(_scales, total, fragmentTotal);
_scaleChanged = false;
_context.SupportBufferUpdater.UpdateRenderScaleFragmentCount(total, fragmentTotal);
}
}

View File

@ -44,11 +44,8 @@ namespace Ryujinx.Graphics.Gpu.Image
TexturePoolCache texturePoolCache = new(context);
SamplerPoolCache samplerPoolCache = new(context);
float[] scales = new float[64];
new Span<float>(scales).Fill(1f);
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: false);
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false);
_texturePoolCache = texturePoolCache;
_samplerPoolCache = samplerPoolCache;

View File

@ -140,18 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
/// Gets a sub-range from the buffer, from a start address till the end of the buffer.
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
/// </summary>
/// <remarks>
/// This can be used to bind and use sub-ranges of the buffer on the host API.
/// </remarks>
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
/// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
/// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range</returns>
public BufferRange GetRange(ulong address)
public BufferRange GetRangeAligned(ulong address, ulong size, bool write)
{
ulong end = ((address + size + MemoryManager.PageMask) & ~MemoryManager.PageMask) - Address;
ulong offset = address - Address;
return new BufferRange(Handle, (int)offset, (int)(Size - offset));
return new BufferRange(Handle, (int)offset, (int)(end - offset), write);
}
/// <summary>
@ -162,12 +165,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks>
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
/// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
/// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range</returns>
public BufferRange GetRange(ulong address, ulong size)
public BufferRange GetRange(ulong address, ulong size, bool write)
{
int offset = (int)(address - Address);
return new BufferRange(Handle, offset, (int)size);
return new BufferRange(Handle, offset, (int)size, write);
}
/// <summary>

View File

@ -344,7 +344,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
dstBuffer.ClearModified(dstAddress, size);
memoryManager.Physical.WriteUntracked(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size));
memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
}
}
@ -372,15 +372,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
/// Gets a buffer sub-range starting at a given memory address.
/// Gets a buffer sub-range from a start address til a page boundary after the given size.
/// </summary>
/// <param name="address">Start address of the memory range</param>
/// <param name="size">Size in bytes of the memory range</param>
/// <param name="write">Whether the buffer will be written to by this use</param>
/// <returns>The buffer sub-range starting at the given memory address</returns>
public BufferRange GetBufferRangeTillEnd(ulong address, ulong size, bool write = false)
public BufferRange GetBufferRangeAligned(ulong address, ulong size, bool write = false)
{
return GetBuffer(address, size, write).GetRange(address);
return GetBuffer(address, size, write).GetRangeAligned(address, size, write);
}
/// <summary>
@ -392,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <returns>The buffer sub-range for the given range</returns>
public BufferRange GetBufferRange(ulong address, ulong size, bool write = false)
{
return GetBuffer(address, size, write).GetRange(address, size);
return GetBuffer(address, size, write).GetRange(address, size, write);
}
/// <summary>

View File

@ -614,7 +614,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_tfInfoBuffer == BufferHandle.Null)
{
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize);
_tfInfoBuffer = _context.Renderer.CreateBuffer(TfInfoBufferSize, BufferAccess.Stream);
}
buffers[0] = new BufferAssignment(0, new BufferRange(_tfInfoBuffer, 0, TfInfoBufferSize));
@ -727,7 +727,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
@ -764,7 +764,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);

View File

@ -236,6 +236,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
_cpuMemory.WriteUntracked(address, data);
}
/// <summary>
/// Writes data to the application process, triggering a precise memory tracking event.
/// </summary>
/// <param name="address">Address to write into</param>
/// <param name="data">Data to be written</param>
/// <param name="kind">Kind of the resource being written, which will not be signalled as CPU modified</param>
public void WriteTrackedResource(ulong address, ReadOnlySpan<byte> data, ResourceKind kind)
{
_cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true, exemptId: (int)kind);
_cpuMemory.WriteUntracked(address, data);
}
/// <summary>
/// Writes data to the application process.
/// </summary>

View File

@ -136,33 +136,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <summary>
/// Updates the render scales for shader input textures or images.
/// </summary>
/// <param name="scales">Scale values</param>
/// <param name="index">Index of the scale</param>
/// <param name="scale">Scale value</param>
public void UpdateRenderScale(int index, float scale)
{
if (_data.RenderScale[1 + index].X != scale)
{
_data.RenderScale[1 + index].X = scale;
DirtyRenderScale(1 + index, 1);
}
}
/// <summary>
/// Updates the render scales for shader input textures or images.
/// </summary>
/// <param name="totalCount">Total number of scales across all stages</param>
/// <param name="fragmentCount">Total number of scales on the fragment shader stage</param>
public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
public void UpdateRenderScaleFragmentCount(int totalCount, int fragmentCount)
{
bool changed = false;
for (int index = 0; index < totalCount; index++)
{
if (_data.RenderScale[1 + index].X != scales[index])
{
_data.RenderScale[1 + index].X = scales[index];
changed = true;
}
}
// Only update fragment count if there are scales after it for the vertex stage.
if (fragmentCount != totalCount && fragmentCount != _data.FragmentRenderScaleCount.X)
{
_data.FragmentRenderScaleCount.X = fragmentCount;
DirtyFragmentRenderScaleCount();
}
if (changed)
{
DirtyRenderScale(0, 1 + totalCount);
}
}
/// <summary>
@ -172,7 +169,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="isBgra">True if the format is BGRA< false otherwise</param>
public void SetRenderTargetIsBgra(int index, bool isBgra)
{
bool isBgraChanged = (_data.FragmentIsBgra[index].X != 0) != isBgra;
bool isBgraChanged = _data.FragmentIsBgra[index].X != 0 != isBgra;
if (isBgraChanged)
{
@ -231,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (_handle == BufferHandle.Null)
{
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize);
_handle = _renderer.CreateBuffer(SupportBuffer.RequiredSize, BufferAccess.Stream);
_renderer.Pipeline.ClearBuffer(_handle, 0, SupportBuffer.RequiredSize, 0);
var range = new BufferRange(_handle, 0, SupportBuffer.RequiredSize);

View File

@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -44,6 +43,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
_newSpecState = newSpecState;
_stageIndex = stageIndex;
_isVulkan = context.Capabilities.Api == TargetApi.Vulkan;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
newSpecState.RecordPrimitiveTopology();
}
}
/// <inheritdoc/>
@ -69,48 +74,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return MemoryMarshal.Cast<byte, ulong>(_data.Span[(int)address..]);
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _oldSpecState.GraphicsState.AlphaToCoverageEnable && _oldSpecState.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _oldSpecState.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference;
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _oldSpecState.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _oldSpecState.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
@ -133,55 +96,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.ConstantBufferUse[_stageIndex];
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _oldSpecState.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_newSpecState.RecordPrimitiveTopology();
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _oldSpecState.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _oldSpecState.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _oldSpecState.GraphicsState.TessellationMode.UnpackSpacing();
}
/// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -204,12 +130,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _oldSpecState.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@ -228,31 +148,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_newSpecState.RecordEarlyZForce();
return _oldSpecState.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public bool QueryHasUnalignedStorageBuffer()
{
return _oldSpecState.GraphicsState.HasUnalignedStorageBuffer || _oldSpecState.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _oldSpecState.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _oldSpecState.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@ -1,5 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -36,6 +35,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
_channel = channel;
_state = state;
_stageIndex = stageIndex;
if (stageIndex == (int)ShaderStage.Geometry - 1)
{
// Only geometry shaders require the primitive topology.
_state.SpecializationState.RecordPrimitiveTopology();
}
}
/// <summary>
@ -74,58 +79,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _state.GraphicsState.AlphaToCoverageEnable && _state.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public AlphaTestOp QueryAlphaTestCompare()
{
if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable)
{
return AlphaTestOp.Always;
}
return _state.GraphicsState.AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
/// <inheritdoc/>
public float QueryAlphaTestReference()
{
return _state.GraphicsState.AlphaTestReference;
}
/// <inheritdoc/>
public AttributeType QueryAttributeType(int location)
{
return _state.GraphicsState.AttributeTypes[location];
}
/// <inheritdoc/>
public bool QueryEarlyZForce()
{
_state.SpecializationState?.RecordEarlyZForce();
return _state.GraphicsState.EarlyZForce;
}
/// <inheritdoc/>
public AttributeType QueryFragmentOutputType(int location)
{
return _state.GraphicsState.FragmentOutputTypes[location];
}
/// <inheritdoc/>
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
@ -152,6 +105,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return useMask;
}
/// <inheritdoc/>
public GpuGraphicsState QueryGraphicsState()
{
return _state.GraphicsState.CreateShaderGraphicsState(!_isVulkan, _isVulkan || _state.GraphicsState.YNegateEnabled);
}
/// <inheritdoc/>
public bool QueryHasConstantBufferDrawParameters()
{
@ -164,49 +123,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
}
/// <inheritdoc/>
public bool QueryDualSourceBlendEnable()
{
return _state.GraphicsState.DualSourceBlendEnable;
}
/// <inheritdoc/>
public InputTopology QueryPrimitiveTopology()
{
_state.SpecializationState?.RecordPrimitiveTopology();
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
}
/// <inheritdoc/>
public bool QueryProgramPointSize()
{
return _state.GraphicsState.ProgramPointSizeEnable;
}
/// <inheritdoc/>
public float QueryPointSize()
{
return _state.GraphicsState.PointSize;
}
/// <inheritdoc/>
public bool QueryTessCw()
{
return _state.GraphicsState.TessellationMode.UnpackCw();
}
/// <inheritdoc/>
public TessPatchType QueryTessPatchType()
{
return _state.GraphicsState.TessellationMode.UnpackPatchType();
}
/// <inheritdoc/>
public TessSpacing QueryTessSpacing()
{
return _state.GraphicsState.TessellationMode.UnpackSpacing();
}
//// <inheritdoc/>
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
{
@ -258,12 +174,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
/// <inheritdoc/>
public bool QueryTransformDepthMinusOneToOne()
{
return _state.GraphicsState.DepthMode;
}
/// <inheritdoc/>
public bool QueryTransformFeedbackEnabled()
{
@ -282,18 +192,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
}
/// <inheritdoc/>
public bool QueryViewportTransformDisable()
{
return _state.GraphicsState.ViewportTransformDisable;
}
/// <inheritdoc/>
public bool QueryYNegateEnabled()
{
return _state.GraphicsState.YNegateEnabled;
}
/// <inheritdoc/>
public void RegisterTexture(int handle, int cbufSlot)
{

View File

@ -1,6 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@ -232,33 +231,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
#pragma warning restore IDE0055
};
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
protected static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View File

@ -103,64 +103,81 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool YNegateEnabled;
/// <summary>
/// Creates a new GPU graphics state.
/// Creates a new graphics state from this state that can be used for shader generation.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
/// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param>
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates whether Y negate of the fragment coordinates is enabled</param>
public GpuChannelGraphicsState(
bool earlyZForce,
PrimitiveTopology topology,
TessMode tessellationMode,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool viewportTransformDisable,
bool depthMode,
bool programPointSizeEnable,
float pointSize,
bool alphaTestEnable,
CompareOp alphaTestCompare,
float alphaTestReference,
ref Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters,
bool hasUnalignedStorageBuffer,
ref Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled)
/// <param name="hostSupportsAlphaTest">Indicates if the host API supports alpha test operations</param>
/// <returns>GPU graphics state that can be used for shader translation</returns>
public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool originUpperLeft)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
ViewportTransformDisable = viewportTransformDisable;
DepthMode = depthMode;
ProgramPointSizeEnable = programPointSizeEnable;
PointSize = pointSize;
AlphaTestEnable = alphaTestEnable;
AlphaTestCompare = alphaTestCompare;
AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
AlphaTestOp alphaTestOp;
if (hostSupportsAlphaTest || !AlphaTestEnable)
{
alphaTestOp = AlphaTestOp.Always;
}
else
{
alphaTestOp = AlphaTestCompare switch
{
CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never,
CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less,
CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal,
CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual,
CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater,
CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual,
CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual,
_ => AlphaTestOp.Always,
};
}
return new GpuGraphicsState(
EarlyZForce,
ConvertToInputTopology(Topology, TessellationMode),
TessellationMode.UnpackCw(),
TessellationMode.UnpackPatchType(),
TessellationMode.UnpackSpacing(),
AlphaToCoverageEnable,
AlphaToCoverageDitherEnable,
ViewportTransformDisable,
DepthMode,
ProgramPointSizeEnable,
PointSize,
alphaTestOp,
AlphaTestReference,
in AttributeTypes,
HasConstantBufferDrawParameters,
in FragmentOutputTypes,
DualSourceBlendEnable,
YNegateEnabled,
originUpperLeft);
}
/// <summary>
/// Converts the Maxwell primitive topology to the shader translator topology.
/// </summary>
/// <param name="topology">Maxwell primitive topology</param>
/// <param name="tessellationMode">Maxwell tessellation mode</param>
/// <returns>Shader translator topology</returns>
private static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode)
{
return topology switch
{
PrimitiveTopology.Points => InputTopology.Points,
PrimitiveTopology.Lines or
PrimitiveTopology.LineLoop or
PrimitiveTopology.LineStrip => InputTopology.Lines,
PrimitiveTopology.LinesAdjacency or
PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency,
PrimitiveTopology.Triangles or
PrimitiveTopology.TriangleStrip or
PrimitiveTopology.TriangleFan => InputTopology.Triangles,
PrimitiveTopology.TrianglesAdjacency or
PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency,
PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines
? InputTopology.Lines
: InputTopology.Triangles,
_ => InputTopology.Points,
};
}
}
}

View File

@ -28,9 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
[Flags]
private enum QueriedStateFlags
{
EarlyZForce = 1 << 0,
PrimitiveTopology = 1 << 1,
TessellationMode = 1 << 2,
TransformFeedback = 1 << 3,
}
@ -264,14 +262,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
PipelineState = pipelineState;
}
/// <summary>
/// Indicates that the shader accesses the early Z force state.
/// </summary>
public void RecordEarlyZForce()
{
_queriedState |= QueriedStateFlags.EarlyZForce;
}
/// <summary>
/// Indicates that the shader accesses the primitive topology state.
/// </summary>
@ -280,14 +270,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
_queriedState |= QueriedStateFlags.PrimitiveTopology;
}
/// <summary>
/// Indicates that the shader accesses the tessellation mode state.
/// </summary>
public void RecordTessellationMode()
{
_queriedState |= QueriedStateFlags.TessellationMode;
}
/// <summary>
/// Indicates that the shader accesses the constant buffer use state.
/// </summary>

View File

@ -13,17 +13,6 @@ namespace Ryujinx.Graphics.Shader
static class AttributeTypeExtensions
{
public static string ToVec4Type(this AttributeType type)
{
return type switch
{
AttributeType.Float => "vec4",
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => throw new ArgumentException($"Invalid attribute type \"{type}\"."),
};
}
public static AggregateType ToAggregateType(this AttributeType type)
{
return type switch

View File

@ -0,0 +1,31 @@
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
namespace Ryujinx.Graphics.Shader.CodeGen
{
readonly struct CodeGenParameters
{
public readonly AttributeUsage AttributeUsage;
public readonly ShaderDefinitions Definitions;
public readonly ShaderProperties Properties;
public readonly HostCapabilities HostCapabilities;
public readonly ILogger Logger;
public readonly TargetApi TargetApi;
public CodeGenParameters(
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ShaderProperties properties,
HostCapabilities hostCapabilities,
ILogger logger,
TargetApi targetApi)
{
AttributeUsage = attributeUsage;
Definitions = definitions;
Properties = properties;
HostCapabilities = hostCapabilities;
Logger = logger;
TargetApi = targetApi;
}
}
}

View File

@ -12,7 +12,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public OperandManager OperandManager { get; }
@ -22,10 +27,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private string _indentation;
public CodeGenContext(StructuredProgramInfo info, ShaderConfig config)
public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
OperandManager = new OperandManager();

View File

@ -1,4 +1,5 @@
using Ryujinx.Common;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
@ -13,10 +14,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
public static void Declare(CodeGenContext context, StructuredProgramInfo info)
{
context.AppendLine(context.Config.Options.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
context.AppendLine("#extension GL_ARB_shader_ballot : enable");
}
@ -30,24 +31,24 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
if (context.Config.Stage == ShaderStage.Compute)
if (context.Definitions.Stage == ShaderStage.Compute)
{
context.AppendLine("#extension GL_ARB_compute_shader : enable");
}
else if (context.Config.Stage == ShaderStage.Fragment)
else if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AppendLine("#extension GL_ARB_fragment_shader_interlock : enable");
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
context.AppendLine("#extension GL_INTEL_fragment_shader_ordering : enable");
}
}
else
{
if (context.Config.Stage == ShaderStage.Vertex)
if (context.Definitions.Stage == ShaderStage.Vertex)
{
context.AppendLine("#extension GL_ARB_shader_draw_parameters : enable");
}
@ -55,12 +56,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable");
}
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
}
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
if (context.HostCapabilities.SupportsViewportMask)
{
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
}
@ -71,23 +72,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
context.AppendLine();
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Config.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Config.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Config.Properties.Textures.Values);
DeclareImages(context, context.Config.Properties.Images.Values);
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false);
DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true);
DeclareSamplers(context, context.Properties.Textures.Values);
DeclareImages(context, context.Properties.Images.Values);
if (context.Config.Stage != ShaderStage.Compute)
if (context.Definitions.Stage != ShaderStage.Compute)
{
if (context.Config.Stage == ShaderStage.Geometry)
if (context.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology();
string inPrimitive = inputTopology.ToGlslString();
string inPrimitive = context.Definitions.InputTopology.ToGlslString();
context.AppendLine($"layout (invocations = {context.Config.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AppendLine($"layout (passthrough) in gl_PerVertex");
context.EnterScope();
@ -98,87 +98,75 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
else
{
string outPrimitive = context.Config.OutputTopology.ToGlslString();
string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
int maxOutputVertices = context.Config.GpPassthrough
? inputTopology.ToInputVertices()
: context.Config.MaxOutputVertices;
int maxOutputVertices = context.Definitions.GpPassthrough
? context.Definitions.InputTopology.ToInputVertices()
: context.Definitions.MaxOutputVertices;
context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
}
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationControl)
else if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
int threadsPerInputPrimitive = context.Config.ThreadsPerInputPrimitive;
int threadsPerInputPrimitive = context.Definitions.ThreadsPerInputPrimitive;
context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;");
context.AppendLine();
}
else if (context.Config.Stage == ShaderStage.TessellationEvaluation)
else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
bool tessCw = context.Config.GpuAccessor.QueryTessCw();
bool tessCw = context.Definitions.TessCw;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here aswell.
tessCw = !tessCw;
}
string patchType = context.Config.GpuAccessor.QueryTessPatchType().ToGlsl();
string spacing = context.Config.GpuAccessor.QueryTessSpacing().ToGlsl();
string patchType = context.Definitions.TessPatchType.ToGlsl();
string spacing = context.Definitions.TessSpacing.ToGlsl();
string windingOrder = tessCw ? "cw" : "ccw";
context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;");
context.AppendLine();
}
if (context.Config.UsedInputAttributes != 0 || context.Config.GpPassthrough)
static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
{
DeclareInputAttributes(context, info);
context.AppendLine();
return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
}
if (context.Config.UsedOutputAttributes != 0 || context.Config.Stage != ShaderStage.Fragment)
static bool IsUserDefinedOutput(ShaderStage stage, IoDefinition ioDefinition)
{
DeclareOutputAttributes(context, info);
context.AppendLine();
IoVariable ioVariable = stage == ShaderStage.Fragment ? IoVariable.FragmentOutputColor : IoVariable.UserDefined;
return ioDefinition.StorageKind == StorageKind.Output && ioDefinition.IoVariable == ioVariable;
}
if (context.Config.UsedInputAttributesPerPatch.Count != 0)
DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
DeclareOutputAttributes(context, info.IoDefinitions.Where(x => IsUserDefinedOutput(context.Definitions.Stage, x)));
DeclareInputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.InputPerPatch)));
DeclareOutputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.OutputPerPatch)));
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
DeclareInputAttributesPerPatch(context, context.Config.UsedInputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.UsedOutputAttributesPerPatch.Count != 0)
{
DeclareUsedOutputAttributesPerPatch(context, context.Config.UsedOutputAttributesPerPatch);
context.AppendLine();
}
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
{
var tfOutput = context.Config.GetTransformFeedbackOutput(AttributeConsts.PositionX);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(AttributeConsts.PositionX);
if (tfOutput.Valid)
{
context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
context.EnterScope();
context.AppendLine("vec4 gl_Position;");
context.LeaveScope(context.Config.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
context.LeaveScope(context.Definitions.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
}
}
}
else
{
string localSizeX = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeX());
string localSizeY = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeY());
string localSizeZ = NumberFormatter.FormatInt(context.Config.GpuAccessor.QueryComputeLocalSizeZ());
string localSizeX = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeX);
string localSizeY = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeY);
string localSizeZ = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeZ);
context.AppendLine(
"layout (" +
@ -188,15 +176,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
context.AppendLine();
}
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Config.GpuAccessor.QueryEarlyZForce())
if (context.Definitions.EarlyZForce)
{
context.AppendLine("layout (early_fragment_tests) in;");
context.AppendLine();
}
if (context.Config.Properties.OriginUpperLeft)
if (context.Definitions.OriginUpperLeft)
{
context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;");
context.AppendLine();
@ -251,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
{
if (context.Config.GpuAccessor.QueryHostReducedPrecision())
if (context.HostCapabilities.ReducedPrecision)
{
precise = false;
}
@ -305,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string set = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
set = $"set = {buffer.Set}, ";
}
@ -390,7 +378,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string layout = string.Empty;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}";
}
@ -435,7 +423,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
layout = ", " + layout;
}
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
layout = $", set = {definition.Set}{layout}";
}
@ -444,42 +432,50 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareInputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IaIndexing))
if (context.Definitions.IaIndexing)
{
string suffix = context.Config.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
string suffix = context.Definitions.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
context.AppendLine($"layout (location = 0) in vec4 {DefaultNames.IAttributePrefix}{suffix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedInputAttributes | context.Config.PassthroughAttributes;
while (usedAttributes != 0)
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareInputAttribute(context, info, index);
usedAttributes &= ~(1 << index);
DeclareInputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (inputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareInputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
private static void DeclareInputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> inputs)
{
foreach (int attr in attrs.Order())
foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
{
DeclareInputAttributePerPatch(context, attr);
DeclareInputAttributePerPatch(context, ioDefinition.Location);
}
if (inputs.Any())
{
context.AppendLine();
}
}
private static void DeclareInputAttribute(CodeGenContext context, StructuredProgramInfo info, int attr)
private static void DeclareInputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: false) ? "[]" : string.Empty;
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: false) ? "[]" : string.Empty;
string iq = string.Empty;
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
iq = context.Config.ImapTypes[attr].GetFirstUsedType() switch
iq = context.Definitions.ImapTypes[location].GetFirstUsedType() switch
{
PixelImap.Constant => "flat ",
PixelImap.ScreenLinear => "noperspective ",
@ -487,14 +483,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
};
}
string name = $"{DefaultNames.IAttributePrefix}{attr}";
string name = $"{DefaultNames.IAttributePrefix}{location}";
if (context.Config.TransformFeedbackEnabled && context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.Stage == ShaderStage.Fragment)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: false);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
context.AppendLine($"layout (location = {location}, component = {component}) {iq}in float {name}_{swzMask}{suffix};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@ -503,85 +507,89 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
_ => "float",
};
context.AppendLine($"layout (location = {attr}) in {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
context.AppendLine($"layout (location = {attr}, component = {c}) {iq}in float {name}_{swzMask}{suffix};");
context.AppendLine($"layout (location = {location}) in {type} {name};");
}
}
else
{
bool passthrough = (context.Config.PassthroughAttributes & (1 << attr)) != 0;
string pass = passthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough() ? "passthrough, " : string.Empty;
string type;
bool passthrough = (context.AttributeUsage.PassthroughAttributes & (1 << location)) != 0;
string pass = passthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough ? "passthrough, " : string.Empty;
string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(location, isOutput: false), false);
if (context.Config.Stage == ShaderStage.Vertex)
{
type = context.Config.GpuAccessor.QueryAttributeType(attr).ToVec4Type();
}
else
{
type = AttributeType.Float.ToVec4Type();
}
context.AppendLine($"layout ({pass}location = {attr}) {iq}in {type} {name}{suffix};");
context.AppendLine($"layout ({pass}location = {location}) {iq}in {type} {name}{suffix};");
}
}
private static void DeclareInputAttributePerPatch(CodeGenContext context, int attr)
private static void DeclareInputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch in vec4 {name};");
}
private static void DeclareOutputAttributes(CodeGenContext context, StructuredProgramInfo info)
private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
if (context.Definitions.OaIndexing)
{
context.AppendLine($"layout (location = 0) out vec4 {DefaultNames.OAttributePrefix}[{Constants.MaxAttributes}];");
context.AppendLine();
}
else
{
int usedAttributes = context.Config.UsedOutputAttributes;
outputs = outputs.OrderBy(x => x.Location);
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
int mask = 3 << firstOutput;
IoDefinition firstOutput = outputs.ElementAtOrDefault(0);
IoDefinition secondOutput = outputs.ElementAtOrDefault(1);
if ((usedAttributes & mask) == mask)
if (firstOutput.Location + 1 == secondOutput.Location)
{
usedAttributes &= ~mask;
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
DeclareOutputDualSourceBlendAttribute(context, firstOutput.Location);
outputs = outputs.Skip(2);
}
}
while (usedAttributes != 0)
foreach (var ioDefinition in outputs)
{
int index = BitOperations.TrailingZeroCount(usedAttributes);
DeclareOutputAttribute(context, index);
usedAttributes &= ~(1 << index);
DeclareOutputAttribute(context, ioDefinition.Location, ioDefinition.Component);
}
if (outputs.Any())
{
context.AppendLine();
}
}
}
private static void DeclareOutputAttribute(CodeGenContext context, int attr)
private static void DeclareOutputAttribute(CodeGenContext context, int location, int component)
{
string suffix = IsArrayAttributeGlsl(context.Config.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{attr}{suffix}";
string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: true) ? "[]" : string.Empty;
string name = $"{DefaultNames.OAttributePrefix}{location}{suffix}";
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
int components = context.Config.GetTransformFeedbackOutputComponents(attr, 0);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: true);
if (components > 1)
if (hasComponent)
{
char swzMask = "xyzw"[component];
string xfb = string.Empty;
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, component);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {location}, component = {component}{xfb}) out float {name}_{swzMask};");
}
else
{
int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
string type = components switch
{
2 => "vec2",
@ -592,58 +600,59 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, 0);
var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, 0);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}{xfb}) out {type} {name};");
}
for (int c = components > 1 ? components : 0; c < 4; c++)
{
char swzMask = "xyzw"[c];
string xfb = string.Empty;
var tfOutput = context.Config.GetTransformFeedbackOutput(attr, c);
if (tfOutput.Valid)
{
xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
}
context.AppendLine($"layout (location = {attr}, component = {c}{xfb}) out float {name}_{swzMask};");
context.AppendLine($"layout (location = {location}{xfb}) out {type} {name};");
}
}
else
{
string type = context.Config.Stage != ShaderStage.Fragment ? "vec4" :
context.Config.GpuAccessor.QueryFragmentOutputType(attr) switch
{
AttributeType.Sint => "ivec4",
AttributeType.Uint => "uvec4",
_ => "vec4",
};
string type = context.Definitions.Stage != ShaderStage.Fragment ? "vec4" :
GetVarTypeName(context, context.Definitions.GetFragmentOutputColorType(location), false);
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && context.Config.Stage == ShaderStage.Vertex && attr == 0)
if (context.HostCapabilities.ReducedPrecision && context.Definitions.Stage == ShaderStage.Vertex && location == 0)
{
context.AppendLine($"layout (location = {attr}) invariant out {type} {name};");
context.AppendLine($"layout (location = {location}) invariant out {type} {name};");
}
else
{
context.AppendLine($"layout (location = {attr}) out {type} {name};");
context.AppendLine($"layout (location = {location}) out {type} {name};");
}
}
}
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int location)
{
string name = $"{DefaultNames.OAttributePrefix}{attr}";
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
string name = $"{DefaultNames.OAttributePrefix}{location}";
string name2 = $"{DefaultNames.OAttributePrefix}{(location + 1)}";
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
context.AppendLine($"layout (location = {location}, index = 0) out vec4 {name};");
context.AppendLine($"layout (location = {location}, index = 1) out vec4 {name2};");
}
private static void DeclareOutputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> outputs)
{
foreach (var ioDefinition in outputs)
{
DeclareOutputAttributePerPatch(context, ioDefinition.Location);
}
if (outputs.Any())
{
context.AppendLine();
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int patchLocation)
{
int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
@ -660,29 +669,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
}
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
{
foreach (int attr in attrs.Order())
{
DeclareOutputAttributePerPatch(context, attr);
}
}
private static void DeclareOutputAttributePerPatch(CodeGenContext context, int attr)
{
int location = context.Config.GetPerPatchAttributeLocation(attr);
string name = $"{DefaultNames.PerPatchAttributePrefix}{attr}";
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
}
private static void AppendHelperFunction(CodeGenContext context, string filename)
{
string code = EmbeddedResources.ReadAllText(filename);
code = code.Replace("\t", CodeGenContext.Tab);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB");
code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB");

View File

@ -11,9 +11,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
{
private const string MainFunctionName = "main";
public static string Generate(StructuredProgramInfo info, ShaderConfig config)
public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
CodeGenContext context = new(info, config);
CodeGenContext context = new(info, parameters);
Declarations.Declare(context, info);
@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
};
bool supportsBarrierDivergence = context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence();
bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence;
bool mayHaveReturned = false;
foreach (IAstNode node in visitor.Visit())
@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
// so skip emitting the barrier for those cases.
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed.");
continue;
}

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
string arg = GetSoureExpr(context, operation.GetSource(0), dstType);
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
if (context.HostCapabilities.SupportsShaderBallot)
{
return $"unpackUint2x32(ballotARB({arg})).x";
}

View File

@ -4,11 +4,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
public static string FSIBegin(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "beginInvocationInterlockARB()";
}
else if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel())
else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
{
return "beginFragmentShaderOrderingINTEL()";
}
@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
public static string FSIEnd(CodeGenContext context)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
return "endInvocationInterlockARB()";
}

View File

@ -84,7 +84,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string imageName = GetImageName(context.Config, texOp, indexExpr);
string imageName = GetImageName(context.Properties, texOp, indexExpr);
texCallBuilder.Append('(');
texCallBuilder.Append(imageName);
@ -216,7 +216,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
int coordsIndex = isBindless || isIndexed ? 1 : 0;
@ -273,7 +273,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// 2D Array and Cube shadow samplers with LOD level or bias requires an extension.
// If the extension is not supported, just remove the LOD parameter.
if (isArray && isShadow && (is2D || isCube) && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isArray && isShadow && (is2D || isCube) && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodBias = false;
hasLodLevel = false;
@ -281,7 +281,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
// Cube shadow samplers with LOD level requires an extension.
// If the extension is not supported, just remove the LOD level parameter.
if (isShadow && isCube && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
if (isShadow && isCube && !context.HostCapabilities.SupportsTextureShadowLod)
{
hasLodLevel = false;
}
@ -342,7 +342,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = Src(AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
texCall += "(" + samplerName;
@ -538,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
indexExpr = GetSoureExpr(context, texOp.GetSource(0), AggregateType.S32);
}
string samplerName = GetSamplerName(context.Config, texOp, indexExpr);
string samplerName = GetSamplerName(context.Properties, texOp, indexExpr);
if (texOp.Index == 3)
{
@ -546,7 +546,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
context.Config.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall;
@ -593,8 +593,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int binding = bindingIndex.Value;
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[binding]
: context.Config.Properties.StorageBuffers[binding];
? context.Properties.ConstantBuffers[binding]
: context.Properties.StorageBuffers[binding];
if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant)
{
@ -614,8 +614,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
MemoryDefinition memory = storageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
varName = memory.Name;
varType = memory.Type;
@ -636,7 +636,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
int location = -1;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -648,16 +648,23 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
}
}
(varName, varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(varName, varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
if (IoMap.IsPerVertexBuiltIn(context.Config.Stage, ioVariable, isOutput))
if (IoMap.IsPerVertexBuiltIn(context.Definitions.Stage, ioVariable, isOutput))
{
// Since those exist both as input and output on geometry and tessellation shaders,
// we need the gl_in and gl_out prefixes to disambiguate.
@ -692,7 +699,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
{
varName += "." + "xyzw"[elementIndex.Value & 3];
}
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
else if (srcIndex == firstSrcIndex && context.Definitions.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
{
// GLSL requires that for tessellation control shader outputs,
// that the index expression must be *exactly* "gl_InvocationID",
@ -715,9 +722,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return varName;
}
private static string GetSamplerName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetSamplerName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Textures[texOp.Binding].Name;
string name = resourceDefinitions.Textures[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{
@ -727,9 +734,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return name;
}
private static string GetImageName(ShaderConfig config, AstTextureOperation texOp, string indexExpr)
private static string GetImageName(ShaderProperties resourceDefinitions, AstTextureOperation texOp, string indexExpr)
{
string name = config.Properties.Images[texOp.Binding].Name;
string name = resourceDefinitions.Images[texOp.Binding].Name;
if (texOp.Type.HasFlag(SamplerType.Indexed))
{

View File

@ -7,7 +7,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
static class IoMap
{
public static (string, AggregateType) GetGlslVariable(
ShaderConfig config,
ShaderDefinitions definitions,
HostCapabilities hostCapabilities,
IoVariable ioVariable,
int location,
int component,
@ -25,7 +26,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.DrawIndex => ("gl_DrawIDARB", AggregateType.S32),
IoVariable.FogCoord => ("gl_FogFragCoord", AggregateType.FP32), // Deprecated.
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(definitions, location),
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
@ -38,20 +39,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
IoVariable.PointCoord => ("gl_PointCoord", AggregateType.Vector2 | AggregateType.FP32),
IoVariable.PointSize => ("gl_PointSize", AggregateType.FP32),
IoVariable.Position => ("gl_Position", AggregateType.Vector4 | AggregateType.FP32),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(config.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(config, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(config, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(config, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
IoVariable.PrimitiveId => GetPrimitiveIdVariableName(definitions.Stage, isOutput),
IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Eq"),
IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Ge"),
IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Gt"),
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(hostCapabilities.SupportsShaderBallot),
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Le"),
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Lt"),
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),
IoVariable.TextureCoord => ("gl_TexCoord", AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
IoVariable.ThreadId => ("gl_LocalInvocationID", AggregateType.Vector3 | AggregateType.U32),
IoVariable.ThreadKill => ("gl_HelperInvocation", AggregateType.Bool),
IoVariable.UserDefined => GetUserDefinedVariableName(config, location, component, isOutput, isPerPatch),
IoVariable.UserDefined => GetUserDefinedVariableName(definitions, location, component, isOutput, isPerPatch),
IoVariable.VertexId => ("gl_VertexID", AggregateType.S32),
IoVariable.VertexIndex => ("gl_VertexIndex", AggregateType.S32),
IoVariable.ViewportIndex => ("gl_ViewportIndex", AggregateType.S32),
@ -86,16 +87,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return false;
}
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderConfig config, int location)
private static (string, AggregateType) GetFragmentOutputColorVariableName(ShaderDefinitions definitions, int location)
{
if (location < 0)
{
return (DefaultNames.OAttributePrefix, config.GetFragmentOutputColorType(0));
return (DefaultNames.OAttributePrefix, definitions.GetFragmentOutputColorType(0));
}
string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture);
return (name, config.GetFragmentOutputColorType(location));
return (name, definitions.GetFragmentOutputColorType(location));
}
private static (string, AggregateType) GetPrimitiveIdVariableName(ShaderStage stage, bool isOutput)
@ -104,21 +105,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
return (isOutput || stage != ShaderStage.Geometry ? "gl_PrimitiveID" : "gl_PrimitiveIDIn", AggregateType.S32);
}
private static (string, AggregateType) GetSubgroupMaskVariableName(ShaderConfig config, string cc)
private static (string, AggregateType) GetSubgroupMaskVariableName(bool supportsShaderBallot, string cc)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32)
: ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32);
}
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(ShaderConfig config)
private static (string, AggregateType) GetSubgroupInvocationIdVariableName(bool supportsShaderBallot)
{
return config.GpuAccessor.QueryHostSupportsShaderBallot()
return supportsShaderBallot
? ("gl_SubGroupInvocationARB", AggregateType.U32)
: ("gl_SubgroupInvocationID", AggregateType.U32);
}
private static (string, AggregateType) GetUserDefinedVariableName(ShaderConfig config, int location, int component, bool isOutput, bool isPerPatch)
private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch)
{
string name = isPerPatch
? DefaultNames.PerPatchAttributePrefix
@ -126,17 +127,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (location < 0)
{
return (name, config.GetUserDefinedType(0, isOutput));
return (name, definitions.GetUserDefinedType(0, isOutput));
}
name += location.ToString(CultureInfo.InvariantCulture);
if (config.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput))
{
name += "_" + "xyzw"[component & 3];
}
return (name, config.GetUserDefinedType(location, isOutput));
return (name, definitions.GetUserDefinedType(location, isOutput));
}
}
}

View File

@ -68,8 +68,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
return field.Type & AggregateType.ElementTypeMask;
@ -82,8 +82,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
}
MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory
? context.Config.Properties.LocalMemories[bindingId.Value]
: context.Config.Properties.SharedMemories[bindingId.Value];
? context.Properties.LocalMemories[bindingId.Value]
: context.Properties.SharedMemories[bindingId.Value];
return memory.Type & AggregateType.ElementTypeMask;
@ -102,7 +102,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(1) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -114,13 +114,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
if (operation.SourcesCount > 2 &&
operation.GetSource(2) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
}
}
(_, AggregateType varType) = IoMap.GetGlslVariable(context.Config, ioVariable, location, component, isOutput, isPerPatch);
(_, AggregateType varType) = IoMap.GetGlslVariable(
context.Definitions,
context.HostCapabilities,
ioVariable,
location,
component,
isOutput,
isPerPatch);
return varType & AggregateType.ElementTypeMask;
}

View File

@ -20,21 +20,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public AttributeUsage AttributeUsage { get; }
public ShaderDefinitions Definitions { get; }
public ShaderProperties Properties { get; }
public HostCapabilities HostCapabilities { get; }
public ILogger Logger { get; }
public TargetApi TargetApi { get; }
public int InputVertices { get; }
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> LocalMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, Instruction> SharedMemories { get; } = new Dictionary<int, Instruction>();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new Dictionary<int, SamplerType>();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new Dictionary<int, (Instruction, Instruction, Instruction)>();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new Dictionary<int, (Instruction, Instruction)>();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
public Dictionary<int, Instruction> ConstantBuffers { get; } = new();
public Dictionary<int, Instruction> StorageBuffers { get; } = new();
public Dictionary<int, Instruction> LocalMemories { get; } = new();
public Dictionary<int, Instruction> SharedMemories { get; } = new();
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
public Dictionary<int, (Instruction, Instruction, Instruction)> Samplers { get; } = new();
public Dictionary<int, (Instruction, Instruction)> Images { get; } = new();
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
public Dictionary<IoDefinition, Instruction> InputsPerPatch { get; } = new();
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new();
public StructuredFunction CurrentFunction { get; set; }
private readonly Dictionary<AstOperand, Instruction> _locals = new();
@ -81,25 +89,28 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public CodeGenContext(
StructuredProgramInfo info,
ShaderConfig config,
CodeGenParameters parameters,
GeneratorPool<Instruction> instPool,
GeneratorPool<LiteralInteger> integerPool) : base(SpirvVersionPacked, instPool, integerPool)
{
Info = info;
Config = config;
AttributeUsage = parameters.AttributeUsage;
Definitions = parameters.Definitions;
Properties = parameters.Properties;
HostCapabilities = parameters.HostCapabilities;
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
if (config.Stage == ShaderStage.Geometry)
if (parameters.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inPrimitive = config.GpuAccessor.QueryPrimitiveTopology();
InputVertices = inPrimitive switch
InputVertices = parameters.Definitions.InputTopology switch
{
InputTopology.Points => 1,
InputTopology.Lines => 2,
InputTopology.LinesAdjacency => 2,
InputTopology.Triangles => 3,
InputTopology.TrianglesAdjacency => 3,
_ => throw new InvalidOperationException($"Invalid input topology \"{inPrimitive}\"."),
_ => throw new InvalidOperationException($"Invalid input topology \"{parameters.Definitions.InputTopology}\"."),
};
}

View File

@ -5,7 +5,6 @@ using Spv.Generator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using static Spv.Specification;
using SpvInstruction = Spv.Generator.Instruction;
@ -66,12 +65,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info)
{
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Config.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
DeclareMemories(context, context.Config.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
DeclareSamplers(context, context.Config.Properties.Textures.Values);
DeclareImages(context, context.Config.Properties.Images.Values);
DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private);
DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup);
DeclareSamplers(context, context.Properties.Textures.Values);
DeclareImages(context, context.Properties.Images.Values);
DeclareInputsAndOutputs(context, info);
}
@ -108,7 +107,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
foreach (BufferDefinition buffer in buffers)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0;
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
int alignmentMask = alignment - 1;
int offset = 0;
@ -181,7 +180,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var sampler in samplers)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0;
var dim = (sampler.Type & SamplerType.Mask) switch
{
@ -220,7 +219,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
foreach (var image in images)
{
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? image.Set : 0;
int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0;
var dim = GetDim(image.Type);
@ -314,16 +313,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static void DeclareInputsAndOutputs(CodeGenContext context, StructuredProgramInfo info)
{
int firstLocation = int.MaxValue;
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
foreach (var ioDefinition in info.IoDefinitions)
{
if (ioDefinition.IoVariable == IoVariable.FragmentOutputColor && ioDefinition.Location < firstLocation)
{
firstLocation = ioDefinition.Location;
}
}
}
foreach (var ioDefinition in info.IoDefinitions)
{
PixelImap iq = PixelImap.Unused;
if (context.Config.Stage == ShaderStage.Fragment)
if (context.Definitions.Stage == ShaderStage.Fragment)
{
var ioVariable = ioDefinition.IoVariable;
if (ioVariable == IoVariable.UserDefined)
{
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
iq = context.Definitions.ImapTypes[ioDefinition.Location].GetFirstUsedType();
}
else
{
@ -340,11 +352,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
bool isOutput = ioDefinition.StorageKind.IsOutput();
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation);
}
}
private static void DeclareInputOrOutput(CodeGenContext context, IoDefinition ioDefinition, bool isOutput, bool isPerPatch, PixelImap iq = PixelImap.Unused)
private static void DeclareInputOrOutput(
CodeGenContext context,
IoDefinition ioDefinition,
bool isOutput,
bool isPerPatch,
PixelImap iq = PixelImap.Unused,
int firstLocation = 0)
{
IoVariable ioVariable = ioDefinition.IoVariable;
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
@ -355,12 +373,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(ioDefinition.Location, isOutput);
varType = context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput);
isBuiltIn = false;
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(ioDefinition.Location);
varType = context.Definitions.GetFragmentOutputColorType(ioDefinition.Location);
isBuiltIn = false;
}
else
@ -374,16 +392,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
}
bool hasComponent = context.Config.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput);
if (hasComponent)
{
varType &= AggregateType.ElementTypeMask;
}
else if (ioVariable == IoVariable.UserDefined && context.Config.HasTransformFeedbackOutputs(isOutput))
else if (ioVariable == IoVariable.UserDefined && context.Definitions.HasTransformFeedbackOutputs(isOutput))
{
varType &= AggregateType.ElementTypeMask;
varType |= context.Config.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
varType |= context.Definitions.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch
{
2 => AggregateType.Vector2,
3 => AggregateType.Vector3,
@ -395,20 +413,20 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var spvType = context.GetType(varType, IoMap.GetSpirvBuiltInArrayLength(ioVariable));
bool builtInPassthrough = false;
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Config.Stage, isOutput))
if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput))
{
int arraySize = context.Config.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.InputVertices : 32;
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize));
if (context.Config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
builtInPassthrough = true;
}
}
if (context.Config.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
if (context.Definitions.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch)
{
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Config.ThreadsPerInputPrimitive));
spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Definitions.ThreadsPerInputPrimitive));
}
var spvPointerType = context.TypePointer(storageClass, spvType);
@ -426,7 +444,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.Decorate(spvVar, Decoration.Patch);
}
if (context.Config.GpuAccessor.QueryHostReducedPrecision() && ioVariable == IoVariable.Position)
if (context.HostCapabilities.ReducedPrecision && ioVariable == IoVariable.Position)
{
context.Decorate(spvVar, Decoration.Invariant);
}
@ -439,7 +457,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
int location = context.Config.GetPerPatchAttributeLocation(ioDefinition.Location);
int location = context.AttributeUsage.GetPerPatchAttributeLocation(ioDefinition.Location);
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
}
@ -455,8 +473,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (!isOutput &&
!isPerPatch &&
(context.Config.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
(context.AttributeUsage.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 &&
context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.Decorate(spvVar, Decoration.PassthroughNV);
}
@ -465,13 +483,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
int location = ioDefinition.Location;
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
{
int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
int index = location - firstLocation;
int mask = 3 << firstLocation;
if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
if ((uint)index < 2)
{
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
@ -499,7 +515,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
}
else if (context.Config.TryGetTransformFeedbackOutput(
else if (context.Definitions.TryGetTransformFeedbackOutput(
ioVariable,
ioDefinition.Location,
ioDefinition.Component,

View File

@ -240,10 +240,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
// Barrier on divergent control flow paths may cause the GPU to hang,
// so skip emitting the barrier for those cases.
if (!context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence() &&
if (!context.HostCapabilities.SupportsShaderBarrierDivergence &&
(context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction))
{
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed.");
return OperationResult.Invalid;
}
@ -546,7 +546,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateFSIBegin(CodeGenContext context, AstOperation operation)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.BeginInvocationInterlockEXT();
}
@ -556,7 +556,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateFSIEnd(CodeGenContext context, AstOperation operation)
{
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.EndInvocationInterlockEXT();
}
@ -1446,7 +1446,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
lodBias = Src(AggregateType.FP32);
}
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Config.Stage != ShaderStage.Fragment)
if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Definitions.Stage != ShaderStage.Fragment)
{
// Implicit LOD is only valid on fragment.
// Use the LOD bias as explicit LOD if available.
@ -1804,8 +1804,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
? context.Properties.ConstantBuffers[bindingIndex.Value]
: context.Properties.StorageBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
storageClass = StorageClass.Uniform;
@ -1825,13 +1825,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (storageKind == StorageKind.LocalMemory)
{
storageClass = StorageClass.Private;
varType = context.Config.Properties.LocalMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
varType = context.Properties.LocalMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
baseObj = context.LocalMemories[bindingId.Value];
}
else
{
storageClass = StorageClass.Workgroup;
varType = context.Config.Properties.SharedMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
varType = context.Properties.SharedMemories[bindingId.Value].Type & AggregateType.ElementTypeMask;
baseObj = context.SharedMemories[bindingId.Value];
}
break;
@ -1851,7 +1851,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
if (operation.GetSource(srcIndex++) is not AstOperand vecIndex || vecIndex.Type != OperandType.Constant)
{
@ -1863,7 +1863,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (operation.SourcesCount > srcIndex &&
operation.GetSource(srcIndex) is AstOperand elemIndex &&
elemIndex.Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput))
{
component = elemIndex.Value;
srcIndex++;
@ -1872,11 +1872,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (ioVariable == IoVariable.UserDefined)
{
varType = context.Config.GetUserDefinedType(location, isOutput);
varType = context.Definitions.GetUserDefinedType(location, isOutput);
}
else if (ioVariable == IoVariable.FragmentOutputColor)
{
varType = context.Config.GetFragmentOutputColorType(location);
varType = context.Definitions.GetFragmentOutputColorType(location);
}
else
{
@ -2076,7 +2076,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2087,7 +2087,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2147,7 +2147,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}
@ -2158,7 +2158,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise)
{
context.Decorate(result, Decoration.NoContraction);
}

View File

@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
HelperFunctionsMask.ShuffleXor |
HelperFunctionsMask.SwizzleAdd;
public static byte[] Generate(StructuredProgramInfo info, ShaderConfig config)
public static byte[] Generate(StructuredProgramInfo info, CodeGenParameters parameters)
{
SpvInstructionPool instPool;
SpvLiteralIntegerPool integerPool;
@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
integerPool = _integerPool.Allocate();
}
CodeGenContext context = new(info, config, instPool, integerPool);
CodeGenContext context = new(info, parameters, instPool, integerPool);
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
@ -56,39 +56,40 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
if (config.TransformFeedbackEnabled && config.LastInVertexPipeline)
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{
context.AddCapability(Capability.TransformFeedback);
}
if (config.Stage == ShaderStage.Fragment)
if (parameters.Definitions.Stage == ShaderStage.Fragment)
{
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
{
context.AddCapability(Capability.Geometry);
}
if (context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
if (context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AddCapability(Capability.FragmentShaderPixelInterlockEXT);
context.AddExtension("SPV_EXT_fragment_shader_interlock");
}
}
else if (config.Stage == ShaderStage.Geometry)
else if (parameters.Definitions.Stage == ShaderStage.Geometry)
{
context.AddCapability(Capability.Geometry);
if (config.GpPassthrough && context.Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (parameters.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
{
context.AddExtension("SPV_NV_geometry_shader_passthrough");
context.AddCapability(Capability.GeometryShaderPassthroughNV);
}
}
else if (config.Stage == ShaderStage.TessellationControl || config.Stage == ShaderStage.TessellationEvaluation)
else if (parameters.Definitions.Stage == ShaderStage.TessellationControl ||
parameters.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
context.AddCapability(Capability.Tessellation);
}
else if (config.Stage == ShaderStage.Vertex)
else if (parameters.Definitions.Stage == ShaderStage.Vertex)
{
context.AddCapability(Capability.DrawParameters);
}
@ -170,15 +171,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (funcIndex == 0)
{
context.AddEntryPoint(context.Config.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
context.AddEntryPoint(context.Definitions.Stage.Convert(), spvFunc, "main", context.GetMainInterface());
if (context.Config.Stage == ShaderStage.TessellationControl)
if (context.Definitions.Stage == ShaderStage.TessellationControl)
{
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Config.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive);
}
else if (context.Config.Stage == ShaderStage.TessellationEvaluation)
else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
switch (context.Config.GpuAccessor.QueryTessPatchType())
switch (context.Definitions.TessPatchType)
{
case TessPatchType.Isolines:
context.AddExecutionMode(spvFunc, ExecutionMode.Isolines);
@ -191,7 +192,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
switch (context.Config.GpuAccessor.QueryTessSpacing())
switch (context.Definitions.TessSpacing)
{
case TessSpacing.EqualSpacing:
context.AddExecutionMode(spvFunc, ExecutionMode.SpacingEqual);
@ -204,9 +205,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
break;
}
bool tessCw = context.Config.GpuAccessor.QueryTessCw();
bool tessCw = context.Definitions.TessCw;
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TargetApi == TargetApi.Vulkan)
{
// We invert the front face on Vulkan backend, so we need to do that here as well.
tessCw = !tessCw;
@ -221,37 +222,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCcw);
}
}
else if (context.Config.Stage == ShaderStage.Geometry)
else if (context.Definitions.Stage == ShaderStage.Geometry)
{
InputTopology inputTopology = context.Config.GpuAccessor.QueryPrimitiveTopology();
context.AddExecutionMode(spvFunc, inputTopology switch
context.AddExecutionMode(spvFunc, context.Definitions.InputTopology switch
{
InputTopology.Points => ExecutionMode.InputPoints,
InputTopology.Lines => ExecutionMode.InputLines,
InputTopology.LinesAdjacency => ExecutionMode.InputLinesAdjacency,
InputTopology.Triangles => ExecutionMode.Triangles,
InputTopology.TrianglesAdjacency => ExecutionMode.InputTrianglesAdjacency,
_ => throw new InvalidOperationException($"Invalid input topology \"{inputTopology}\"."),
_ => throw new InvalidOperationException($"Invalid input topology \"{context.Definitions.InputTopology}\"."),
});
context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.Config.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive);
context.AddExecutionMode(spvFunc, context.Config.OutputTopology switch
context.AddExecutionMode(spvFunc, context.Definitions.OutputTopology switch
{
OutputTopology.PointList => ExecutionMode.OutputPoints,
OutputTopology.LineStrip => ExecutionMode.OutputLineStrip,
OutputTopology.TriangleStrip => ExecutionMode.OutputTriangleStrip,
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Config.OutputTopology}\"."),
_ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."),
});
int maxOutputVertices = context.Config.GpPassthrough ? context.InputVertices : context.Config.MaxOutputVertices;
int maxOutputVertices = context.Definitions.GpPassthrough ? context.InputVertices : context.Definitions.MaxOutputVertices;
context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)maxOutputVertices);
}
else if (context.Config.Stage == ShaderStage.Fragment)
else if (context.Definitions.Stage == ShaderStage.Fragment)
{
context.AddExecutionMode(spvFunc, context.Config.Properties.OriginUpperLeft
context.AddExecutionMode(spvFunc, context.Definitions.OriginUpperLeft
? ExecutionMode.OriginUpperLeft
: ExecutionMode.OriginLowerLeft);
@ -260,22 +259,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddExecutionMode(spvFunc, ExecutionMode.DepthReplacing);
}
if (context.Config.GpuAccessor.QueryEarlyZForce())
if (context.Definitions.EarlyZForce)
{
context.AddExecutionMode(spvFunc, ExecutionMode.EarlyFragmentTests);
}
if ((info.HelperFunctionsMask & HelperFunctionsMask.FSI) != 0 &&
context.Config.GpuAccessor.QueryHostSupportsFragmentShaderInterlock())
context.HostCapabilities.SupportsFragmentShaderInterlock)
{
context.AddExecutionMode(spvFunc, ExecutionMode.PixelInterlockOrderedEXT);
}
}
else if (context.Config.Stage == ShaderStage.Compute)
else if (context.Definitions.Stage == ShaderStage.Compute)
{
var localSizeX = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeX();
var localSizeY = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeY();
var localSizeZ = (SpvLiteralInteger)context.Config.GpuAccessor.QueryComputeLocalSizeZ();
var localSizeX = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeX;
var localSizeY = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeY;
var localSizeZ = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeZ;
context.AddExecutionMode(
spvFunc,
@ -285,7 +284,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
localSizeZ);
}
if (context.Config.TransformFeedbackEnabled && context.Config.LastInVertexPipeline)
if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
{
context.AddExecutionMode(spvFunc, ExecutionMode.Xfb);
}

View File

@ -1,3 +1,4 @@
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections;
using System.Collections.Generic;
@ -11,11 +12,26 @@ namespace Ryujinx.Graphics.Shader.Decoders
private readonly List<DecodedFunction> _functionsWithId;
public int FunctionsWithIdCount => _functionsWithId.Count;
public DecodedProgram(DecodedFunction mainFunction, IReadOnlyDictionary<ulong, DecodedFunction> functions)
public AttributeUsage AttributeUsage { get; }
public FeatureFlags UsedFeatures { get; }
public byte ClipDistancesWritten { get; }
public int Cb1DataSize { get; }
public DecodedProgram(
DecodedFunction mainFunction,
IReadOnlyDictionary<ulong, DecodedFunction> functions,
AttributeUsage attributeUsage,
FeatureFlags usedFeatures,
byte clipDistancesWritten,
int cb1DataSize)
{
MainFunction = mainFunction;
_functions = functions;
_functionsWithId = new List<DecodedFunction>();
_functionsWithId = new();
AttributeUsage = attributeUsage;
UsedFeatures = usedFeatures;
ClipDistancesWritten = clipDistancesWritten;
Cb1DataSize = cb1DataSize;
}
public DecodedFunction GetFunctionByAddress(ulong address)

View File

@ -9,8 +9,45 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
static class Decoder
{
public static DecodedProgram Decode(ShaderConfig config, ulong startAddress)
private class Context
{
public AttributeUsage AttributeUsage { get; }
public FeatureFlags UsedFeatures { get; private set; }
public byte ClipDistancesWritten { get; private set; }
public int Cb1DataSize { get; private set; }
private readonly IGpuAccessor _gpuAccessor;
public Context(IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
AttributeUsage = new(gpuAccessor);
}
public uint ConstantBuffer1Read(int offset)
{
if (Cb1DataSize < offset + 4)
{
Cb1DataSize = offset + 4;
}
return _gpuAccessor.ConstantBuffer1Read(offset);
}
public void SetUsedFeature(FeatureFlags flags)
{
UsedFeatures |= flags;
}
public void SetClipDistanceWritten(int index)
{
ClipDistancesWritten |= (byte)(1 << index);
}
}
public static DecodedProgram Decode(ShaderDefinitions definitions, IGpuAccessor gpuAccessor, ulong startAddress)
{
Context context = new(gpuAccessor);
Queue<DecodedFunction> functionsQueue = new();
Dictionary<ulong, DecodedFunction> functionsVisited = new();
@ -89,7 +126,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
}
FillBlock(config, currBlock, limitAddress, startAddress);
FillBlock(definitions, gpuAccessor, context, currBlock, limitAddress, startAddress);
if (currBlock.OpCodes.Count != 0)
{
@ -148,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
}
// Try to find targets for BRX (indirect branch) instructions.
hasNewTarget = FindBrxTargets(config, blocks, GetBlock);
hasNewTarget = FindBrxTargets(context, blocks, GetBlock);
// If we discovered new branch targets from the BRX instruction,
// we need another round of decoding to decode the new blocks.
@ -160,7 +197,13 @@ namespace Ryujinx.Graphics.Shader.Decoders
currentFunction.SetBlocks(blocks.ToArray());
}
return new DecodedProgram(mainFunction, functionsVisited);
return new DecodedProgram(
mainFunction,
functionsVisited,
context.AttributeUsage,
context.UsedFeatures,
context.ClipDistancesWritten,
context.Cb1DataSize);
}
private static bool BinarySearch(List<Block> blocks, ulong address, out int index)
@ -198,10 +241,14 @@ namespace Ryujinx.Graphics.Shader.Decoders
return false;
}
private static void FillBlock(ShaderConfig config, Block block, ulong limitAddress, ulong startAddress)
private static void FillBlock(
ShaderDefinitions definitions,
IGpuAccessor gpuAccessor,
Context context,
Block block,
ulong limitAddress,
ulong startAddress)
{
IGpuAccessor gpuAccessor = config.GpuAccessor;
ulong address = block.Address;
int bufferOffset = 0;
ReadOnlySpan<ulong> buffer = ReadOnlySpan<ulong>.Empty;
@ -235,27 +282,31 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (op.Props.HasFlag(InstProps.TexB))
{
config.SetUsedFeature(FeatureFlags.Bindless);
context.SetUsedFeature(FeatureFlags.Bindless);
}
if (op.Name == InstName.Ald || op.Name == InstName.Ast || op.Name == InstName.Ipa)
switch (op.Name)
{
SetUserAttributeUses(config, op.Name, opCode);
}
else if (op.Name == InstName.Pbk || op.Name == InstName.Pcnt || op.Name == InstName.Ssy)
{
block.AddPushOp(op);
}
else if (op.Name == InstName.Ldl || op.Name == InstName.Stl)
{
config.SetUsedFeature(FeatureFlags.LocalMemory);
}
else if (op.Name == InstName.Atoms ||
op.Name == InstName.AtomsCas ||
op.Name == InstName.Lds ||
op.Name == InstName.Sts)
{
config.SetUsedFeature(FeatureFlags.SharedMemory);
case InstName.Ald:
case InstName.Ast:
case InstName.Ipa:
SetUserAttributeUses(definitions, context, op.Name, opCode);
break;
case InstName.Pbk:
case InstName.Pcnt:
case InstName.Ssy:
block.AddPushOp(op);
break;
case InstName.Ldl:
case InstName.Stl:
context.SetUsedFeature(FeatureFlags.LocalMemory);
break;
case InstName.Atoms:
case InstName.AtomsCas:
case InstName.Lds:
case InstName.Sts:
context.SetUsedFeature(FeatureFlags.SharedMemory);
break;
}
block.OpCodes.Add(op);
@ -267,7 +318,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
block.EndAddress = address;
}
private static void SetUserAttributeUses(ShaderConfig config, InstName name, ulong opCode)
private static void SetUserAttributeUses(ShaderDefinitions definitions, Context context, InstName name, ulong opCode)
{
int offset;
int count = 1;
@ -304,13 +355,13 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
if (isStore)
{
config.SetAllOutputUserAttributes();
config.SetUsedFeature(FeatureFlags.OaIndexing);
context.AttributeUsage.SetAllOutputUserAttributes();
definitions.EnableOutputIndexing();
}
else
{
config.SetAllInputUserAttributes();
config.SetUsedFeature(FeatureFlags.IaIndexing);
context.AttributeUsage.SetAllInputUserAttributes();
definitions.EnableInputIndexing();
}
}
else
@ -328,11 +379,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetOutputUserAttributePerPatch(index);
context.AttributeUsage.SetOutputUserAttributePerPatch(index);
}
else
{
config.SetInputUserAttributePerPatch(index);
context.AttributeUsage.SetInputUserAttributePerPatch(index);
}
}
}
@ -343,11 +394,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
if (isStore)
{
config.SetOutputUserAttribute(index);
context.AttributeUsage.SetOutputUserAttribute(index);
}
else
{
config.SetInputUserAttribute(index, (userAttr >> 2) & 3);
context.AttributeUsage.SetInputUserAttribute(index, (userAttr >> 2) & 3);
}
}
@ -356,7 +407,54 @@ namespace Ryujinx.Graphics.Shader.Decoders
(attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) ||
(attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)))
{
config.SetUsedFeature(FeatureFlags.FixedFuncAttr);
context.SetUsedFeature(FeatureFlags.FixedFuncAttr);
}
else
{
if (isStore)
{
switch (attr)
{
case AttributeConsts.Layer:
if (definitions.Stage != ShaderStage.Compute && definitions.Stage != ShaderStage.Fragment)
{
context.SetUsedFeature(FeatureFlags.RtLayer);
}
break;
case AttributeConsts.ClipDistance0:
case AttributeConsts.ClipDistance1:
case AttributeConsts.ClipDistance2:
case AttributeConsts.ClipDistance3:
case AttributeConsts.ClipDistance4:
case AttributeConsts.ClipDistance5:
case AttributeConsts.ClipDistance6:
case AttributeConsts.ClipDistance7:
if (definitions.Stage == ShaderStage.Vertex)
{
context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4);
}
break;
}
}
else
{
switch (attr)
{
case AttributeConsts.PositionX:
case AttributeConsts.PositionY:
if (definitions.Stage == ShaderStage.Fragment)
{
context.SetUsedFeature(FeatureFlags.FragCoordXY);
}
break;
case AttributeConsts.InstanceId:
if (definitions.Stage == ShaderStage.Vertex)
{
context.SetUsedFeature(FeatureFlags.InstanceId);
}
break;
}
}
}
}
}
@ -379,7 +477,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
return condOp.Pred == RegisterConsts.PredicateTrueIndex && !condOp.PredInv;
}
private static bool FindBrxTargets(ShaderConfig config, IEnumerable<Block> blocks, Func<ulong, Block> getBlock)
private static bool FindBrxTargets(Context context, IEnumerable<Block> blocks, Func<ulong, Block> getBlock)
{
bool hasNewTarget = false;
@ -406,7 +504,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
for (int i = 0; i < cbOffsetsCount; i++)
{
uint targetOffset = config.ConstantBuffer1Read(cbBaseOffset + i * 4);
uint targetOffset = context.ConstantBuffer1Read(cbBaseOffset + i * 4);
ulong targetAddress = baseOffset + targetOffset;
if (visited.Add(targetAddress))

View File

@ -0,0 +1,169 @@
using Ryujinx.Common.Memory;
namespace Ryujinx.Graphics.Shader
{
/// <summary>
/// GPU graphics state that the shader depends on.
/// </summary>
public readonly struct GpuGraphicsState
{
/// <summary>
/// Early Z force enable.
/// </summary>
public readonly bool EarlyZForce;
/// <summary>
/// Primitive topology of current draw.
/// </summary>
public readonly InputTopology Topology;
/// <summary>
/// Tessellation winding order.
/// </summary>
public readonly bool TessCw;
/// <summary>
/// Tessellation patch type.
/// </summary>
public readonly TessPatchType TessPatchType;
/// <summary>
/// Tessellation spacing.
/// </summary>
public readonly TessSpacing TessSpacing;
/// <summary>
/// Indicates whether alpha-to-coverage is enabled.
/// </summary>
public readonly bool AlphaToCoverageEnable;
/// <summary>
/// Indicates whether alpha-to-coverage dithering is enabled.
/// </summary>
public readonly bool AlphaToCoverageDitherEnable;
/// <summary>
/// Indicates whether the viewport transform is disabled.
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Depth mode zero to one or minus one to one.
/// </summary>
public readonly bool DepthMode;
/// <summary>
/// Indicates if the point size is set on the shader or is fixed.
/// </summary>
public readonly bool ProgramPointSizeEnable;
/// <summary>
/// Point size used if <see cref="ProgramPointSizeEnable" /> is false.
/// </summary>
public readonly float PointSize;
/// <summary>
/// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded.
/// </summary>
public readonly AlphaTestOp AlphaTestCompare;
/// <summary>
/// When alpha test is enabled, indicates the value to compare with the fragment output alpha.
/// </summary>
public readonly float AlphaTestReference;
/// <summary>
/// Type of the vertex attributes consumed by the shader.
/// </summary>
public readonly Array32<AttributeType> AttributeTypes;
/// <summary>
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
/// </summary>
public readonly bool HasConstantBufferDrawParameters;
/// <summary>
/// Type of the fragment shader outputs.
/// </summary>
public readonly Array8<AttributeType> FragmentOutputTypes;
/// <summary>
/// Indicates whether dual source blend is enabled.
/// </summary>
public readonly bool DualSourceBlendEnable;
/// <summary>
/// Indicates if negation of the viewport Y axis is enabled.
/// </summary>
public readonly bool YNegateEnabled;
/// <summary>
/// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner.
/// </summary>
public readonly bool OriginUpperLeft;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
/// <param name="earlyZForce">Early Z force enable</param>
/// <param name="topology">Primitive topology</param>
/// <param name="tessCw">Tessellation winding order (clockwise or counter-clockwise)</param>
/// <param name="tessPatchType">Tessellation patch type</param>
/// <param name="tessSpacing">Tessellation spacing</param>
/// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param>
/// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param>
/// <param name="depthMode">Depth mode zero to one or minus one to one</param>
/// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param>
/// <param name="pointSize">Point size if not set from shader</param>
/// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param>
/// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param>
/// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param>
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
/// <param name="dualSourceBlendEnable">Indicates whether dual source blend is enabled</param>
/// <param name="yNegateEnabled">Indicates if negation of the viewport Y axis is enabled</param>
/// <param name="originUpperLeft">If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner</param>
public GpuGraphicsState(
bool earlyZForce,
InputTopology topology,
bool tessCw,
TessPatchType tessPatchType,
TessSpacing tessSpacing,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool viewportTransformDisable,
bool depthMode,
bool programPointSizeEnable,
float pointSize,
AlphaTestOp alphaTestCompare,
float alphaTestReference,
in Array32<AttributeType> attributeTypes,
bool hasConstantBufferDrawParameters,
in Array8<AttributeType> fragmentOutputTypes,
bool dualSourceBlendEnable,
bool yNegateEnabled,
bool originUpperLeft)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessCw = tessCw;
TessPatchType = tessPatchType;
TessSpacing = tessSpacing;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
ViewportTransformDisable = viewportTransformDisable;
DepthMode = depthMode;
ProgramPointSizeEnable = programPointSizeEnable;
PointSize = pointSize;
AlphaTestCompare = alphaTestCompare;
AlphaTestReference = alphaTestReference;
AttributeTypes = attributeTypes;
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
FragmentOutputTypes = fragmentOutputTypes;
DualSourceBlendEnable = dualSourceBlendEnable;
YNegateEnabled = yNegateEnabled;
OriginUpperLeft = originUpperLeft;
}
}
}

View File

@ -1,21 +1,13 @@
using System;
using Ryujinx.Graphics.Shader.CodeGen;
using System;
namespace Ryujinx.Graphics.Shader
{
/// <summary>
/// GPU state access interface.
/// </summary>
public interface IGpuAccessor
public interface IGpuAccessor : ILogger
{
/// <summary>
/// Prints a log message.
/// </summary>
/// <param name="message">Message to print</param>
void Log(string message)
{
// No default log output.
}
/// <summary>
/// Reads data from the constant buffer 1.
/// </summary>
@ -34,44 +26,6 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Queries the alpha test comparison operator that is being used currently.
/// If alpha test is disabled, it should be set to <see cref="AlphaTestOp.Always"/>.
/// </summary>
/// <returns>Current alpha test comparison</returns>
AlphaTestOp QueryAlphaTestCompare()
{
return AlphaTestOp.Always;
}
/// <summary>
/// Queries the current alpha test reference value used by the comparison.
/// </summary>
/// <returns>Current alpha test reference value</returns>
float QueryAlphaTestReference()
{
return 0f;
}
/// <summary>
/// Queries the type of the vertex shader input attribute at the specified <paramref name="location"/>.
/// </summary>
/// <param name="location">Location of the input attribute</param>
/// <returns>Input type</returns>
AttributeType QueryAttributeType(int location)
{
return AttributeType.Float;
}
/// <summary>
/// Queries whenever the alpha-to-coverage dithering feature is enabled.
/// </summary>
/// <returns>True if the feature is enabled, false otherwise</returns>
bool QueryAlphaToCoverageDitherEnable()
{
return false;
}
/// <summary>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -114,16 +68,6 @@ namespace Ryujinx.Graphics.Shader
return index;
}
/// <summary>
/// Queries output type for fragment shaders.
/// </summary>
/// <param name="location">Location of the framgent output</param>
/// <returns>Output location</returns>
AttributeType QueryFragmentOutputType(int location)
{
return AttributeType.Float;
}
/// <summary>
/// Queries Local Size X for compute shaders.
/// </summary>
@ -179,12 +123,12 @@ namespace Ryujinx.Graphics.Shader
}
/// <summary>
/// Queries if host state forces early depth testing.
/// Queries specialized GPU graphics state that the shader depends on.
/// </summary>
/// <returns>True if early depth testing is forced</returns>
bool QueryEarlyZForce()
/// <returns>GPU graphics state</returns>
GpuGraphicsState QueryGraphicsState()
{
return false;
return default;
}
/// <summary>
@ -223,15 +167,6 @@ namespace Ryujinx.Graphics.Shader
return false;
}
/// <summary>
/// Queries dual source blend state.
/// </summary>
/// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
bool QueryDualSourceBlendEnable()
{
return false;
}
/// <summary>
/// Queries host about the presence of the FrontFacing built-in variable bug.
/// </summary>
@ -412,25 +347,6 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
/// </summary>
/// <returns>Current point size</returns>
float QueryPointSize()
{
return 1f;
}
/// <summary>
/// Queries the state that indicates if the program point size should be explicitly set on the shader
/// or read from the GPU state.
/// </summary>
/// <returns>True if the shader is expected to set the point size explicitly, false otherwise</returns>
bool QueryProgramPointSize()
{
return true;
}
/// <summary>
/// Queries sampler type information.
/// </summary>
@ -453,42 +369,6 @@ namespace Ryujinx.Graphics.Shader
return true;
}
/// <summary>
/// Queries current primitive topology for geometry shaders.
/// </summary>
/// <returns>Current primitive topology</returns>
InputTopology QueryPrimitiveTopology()
{
return InputTopology.Points;
}
/// <summary>
/// Queries the tessellation evaluation shader primitive winding order.
/// </summary>
/// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
bool QueryTessCw()
{
return false;
}
/// <summary>
/// Queries the tessellation evaluation shader abstract patch type.
/// </summary>
/// <returns>Abstract patch type</returns>
TessPatchType QueryTessPatchType()
{
return TessPatchType.Triangles;
}
/// <summary>
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
/// </summary>
/// <returns>Spacing between tessellated vertices of the patch</returns>
TessSpacing QueryTessSpacing()
{
return TessSpacing.EqualSpacing;
}
/// <summary>
/// Queries texture format information, for shaders using image load or store.
/// </summary>
@ -504,15 +384,6 @@ namespace Ryujinx.Graphics.Shader
return TextureFormat.R8G8B8A8Unorm;
}
/// <summary>
/// Queries depth mode information from the GPU state.
/// </summary>
/// <returns>True if current depth mode is -1 to 1, false if 0 to 1</returns>
bool QueryTransformDepthMinusOneToOne()
{
return false;
}
/// <summary>
/// Queries transform feedback enable state.
/// </summary>
@ -542,24 +413,6 @@ namespace Ryujinx.Graphics.Shader
return 0;
}
/// <summary>
/// Queries if host state disables the viewport transform.
/// </summary>
/// <returns>True if the viewport transform is disabled</returns>
bool QueryViewportTransformDisable()
{
return false;
}
/// <summary>
/// Queries Y negate enable state.
/// </summary>
/// <returns>True if Y negate of the fragment coordinates is enabled, false otherwise</returns>
bool QueryYNegateEnabled()
{
return false;
}
/// <summary>
/// Registers a texture used by the shader.
/// </summary>

View File

@ -0,0 +1,17 @@
namespace Ryujinx.Graphics.Shader.CodeGen
{
/// <summary>
/// Shader code generation logging interface.
/// </summary>
public interface ILogger
{
/// <summary>
/// Prints a log message.
/// </summary>
/// <param name="message">Message to print</param>
void Log(string message)
{
// No default log output.
}
}
}

View File

@ -127,25 +127,25 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return Const(0);
}
StagesMask validUseMask = isOutput ? entry.OutputMask : entry.InputMask;
if (((StagesMask)(1 << (int)context.Config.Stage) & validUseMask) == StagesMask.None)
if (((StagesMask)(1 << (int)context.TranslatorContext.Definitions.Stage) & validUseMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}.");
return Const(0);
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}.");
return Const(0);
}
if (HasInvocationId(context.Config.Stage, isOutput) && !isPerPatch)
if (HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput) && !isPerPatch)
{
primVertex = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -156,12 +156,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
StorageKind storageKind = isPerPatch
? (isOutput ? StorageKind.OutputPerPatch : StorageKind.InputPerPatch)
: (isOutput ? StorageKind.Output : StorageKind.Input);
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput, innerIndex, in entry);
IoVariable ioVariable = GetIoVariable(context.TranslatorContext.Definitions.Stage, in entry);
AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput);
bool hasElementIndex = elementCount > 1;
@ -190,25 +190,25 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(isPerPatch ? _attributesPerPatch : _attributes).TryGetValue(offset, out AttributeEntry entry))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid.");
return;
}
if (((StagesMask)(1 << (int)context.Config.Stage) & entry.OutputMask) == StagesMask.None)
if (((StagesMask)(1 << (int)context.TranslatorContext.Definitions.Stage) & entry.OutputMask) == StagesMask.None)
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}.");
return;
}
if (!IsSupportedByHost(context.Config.GpuAccessor, context.Config.Stage, entry.IoVariable))
if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable))
{
context.Config.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.Config.Stage}.");
context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}.");
return;
}
Operand invocationId = null;
if (HasInvocationId(context.Config.Stage, isOutput: true) && !isPerPatch)
if (HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput: true) && !isPerPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -217,12 +217,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
int innerIndex = innerOffset / 4;
StorageKind storageKind = isPerPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
IoVariable ioVariable = GetIoVariable(context.Config.Stage, in entry);
AggregateType type = GetType(context.Config, isOutput: true, innerIndex, in entry);
IoVariable ioVariable = GetIoVariable(context.TranslatorContext.Definitions.Stage, in entry);
AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput: true, innerIndex, in entry);
int elementCount = GetElementCount(type);
bool isArray = type.HasFlag(AggregateType.Array);
bool hasArrayIndex = isArray || context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput: true);
bool hasElementIndex = elementCount > 1;
@ -271,7 +271,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return true;
}
public static IoVariable GetIoVariable(ShaderConfig config, int offset, out int location)
public static IoVariable GetIoVariable(ShaderDefinitions definitions, int offset, out int location)
{
location = 0;
@ -280,17 +280,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
return IoVariable.Invalid;
}
if (((StagesMask)(1 << (int)config.Stage) & entry.OutputMask) == StagesMask.None)
if (((StagesMask)(1 << (int)definitions.Stage) & entry.OutputMask) == StagesMask.None)
{
return IoVariable.Invalid;
}
if (config.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
if (definitions.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true))
{
location = (offset - entry.BaseOffset) / 16;
}
return GetIoVariable(config.Stage, in entry);
return GetIoVariable(definitions.Stage, in entry);
}
private static IoVariable GetIoVariable(ShaderStage stage, in AttributeEntry entry)
@ -303,17 +303,17 @@ namespace Ryujinx.Graphics.Shader.Instructions
return entry.IoVariable;
}
private static AggregateType GetType(ShaderConfig config, bool isOutput, int innerIndex, in AttributeEntry entry)
private static AggregateType GetType(ShaderDefinitions definitions, bool isOutput, int innerIndex, in AttributeEntry entry)
{
AggregateType type = entry.Type;
if (entry.IoVariable == IoVariable.UserDefined)
{
type = config.GetUserDefinedType(innerIndex / 4, isOutput);
type = definitions.GetUserDefinedType(innerIndex / 4, isOutput);
}
else if (entry.IoVariable == IoVariable.FragmentOutputColor)
{
type = config.GetFragmentOutputColorType(innerIndex / 4);
type = definitions.GetFragmentOutputColorType(innerIndex / 4);
}
return type;

View File

@ -9,350 +9,350 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
context.GetOp<InstAtomCas>();
context.Config.GpuAccessor.Log("Shader instruction AtomCas is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomCas is not implemented.");
}
public static void AtomsCas(EmitterContext context)
{
context.GetOp<InstAtomsCas>();
context.Config.GpuAccessor.Log("Shader instruction AtomsCas is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomsCas is not implemented.");
}
public static void B2r(EmitterContext context)
{
context.GetOp<InstB2r>();
context.Config.GpuAccessor.Log("Shader instruction B2r is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction B2r is not implemented.");
}
public static void Bpt(EmitterContext context)
{
context.GetOp<InstBpt>();
context.Config.GpuAccessor.Log("Shader instruction Bpt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Bpt is not implemented.");
}
public static void Cctl(EmitterContext context)
{
context.GetOp<InstCctl>();
context.Config.GpuAccessor.Log("Shader instruction Cctl is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctl is not implemented.");
}
public static void Cctll(EmitterContext context)
{
context.GetOp<InstCctll>();
context.Config.GpuAccessor.Log("Shader instruction Cctll is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctll is not implemented.");
}
public static void Cctlt(EmitterContext context)
{
context.GetOp<InstCctlt>();
context.Config.GpuAccessor.Log("Shader instruction Cctlt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctlt is not implemented.");
}
public static void Cs2r(EmitterContext context)
{
context.GetOp<InstCs2r>();
context.Config.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Cs2r is not implemented.");
}
public static void FchkR(EmitterContext context)
{
context.GetOp<InstFchkR>();
context.Config.GpuAccessor.Log("Shader instruction FchkR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkR is not implemented.");
}
public static void FchkI(EmitterContext context)
{
context.GetOp<InstFchkI>();
context.Config.GpuAccessor.Log("Shader instruction FchkI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkI is not implemented.");
}
public static void FchkC(EmitterContext context)
{
context.GetOp<InstFchkC>();
context.Config.GpuAccessor.Log("Shader instruction FchkC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkC is not implemented.");
}
public static void Getcrsptr(EmitterContext context)
{
context.GetOp<InstGetcrsptr>();
context.Config.GpuAccessor.Log("Shader instruction Getcrsptr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Getcrsptr is not implemented.");
}
public static void Getlmembase(EmitterContext context)
{
context.GetOp<InstGetlmembase>();
context.Config.GpuAccessor.Log("Shader instruction Getlmembase is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Getlmembase is not implemented.");
}
public static void Ide(EmitterContext context)
{
context.GetOp<InstIde>();
context.Config.GpuAccessor.Log("Shader instruction Ide is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ide is not implemented.");
}
public static void IdpR(EmitterContext context)
{
context.GetOp<InstIdpR>();
context.Config.GpuAccessor.Log("Shader instruction IdpR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpR is not implemented.");
}
public static void IdpC(EmitterContext context)
{
context.GetOp<InstIdpC>();
context.Config.GpuAccessor.Log("Shader instruction IdpC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpC is not implemented.");
}
public static void ImadspR(EmitterContext context)
{
context.GetOp<InstImadspR>();
context.Config.GpuAccessor.Log("Shader instruction ImadspR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspR is not implemented.");
}
public static void ImadspI(EmitterContext context)
{
context.GetOp<InstImadspI>();
context.Config.GpuAccessor.Log("Shader instruction ImadspI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspI is not implemented.");
}
public static void ImadspC(EmitterContext context)
{
context.GetOp<InstImadspC>();
context.Config.GpuAccessor.Log("Shader instruction ImadspC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspC is not implemented.");
}
public static void ImadspRc(EmitterContext context)
{
context.GetOp<InstImadspRc>();
context.Config.GpuAccessor.Log("Shader instruction ImadspRc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspRc is not implemented.");
}
public static void Jcal(EmitterContext context)
{
context.GetOp<InstJcal>();
context.Config.GpuAccessor.Log("Shader instruction Jcal is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jcal is not implemented.");
}
public static void Jmp(EmitterContext context)
{
context.GetOp<InstJmp>();
context.Config.GpuAccessor.Log("Shader instruction Jmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmp is not implemented.");
}
public static void Jmx(EmitterContext context)
{
context.GetOp<InstJmx>();
context.Config.GpuAccessor.Log("Shader instruction Jmx is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmx is not implemented.");
}
public static void Ld(EmitterContext context)
{
context.GetOp<InstLd>();
context.Config.GpuAccessor.Log("Shader instruction Ld is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ld is not implemented.");
}
public static void Lepc(EmitterContext context)
{
context.GetOp<InstLepc>();
context.Config.GpuAccessor.Log("Shader instruction Lepc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Lepc is not implemented.");
}
public static void Longjmp(EmitterContext context)
{
context.GetOp<InstLongjmp>();
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
}
public static void Pexit(EmitterContext context)
{
context.GetOp<InstPexit>();
context.Config.GpuAccessor.Log("Shader instruction Pexit is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pexit is not implemented.");
}
public static void Pixld(EmitterContext context)
{
context.GetOp<InstPixld>();
context.Config.GpuAccessor.Log("Shader instruction Pixld is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pixld is not implemented.");
}
public static void Plongjmp(EmitterContext context)
{
context.GetOp<InstPlongjmp>();
context.Config.GpuAccessor.Log("Shader instruction Plongjmp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Plongjmp is not implemented.");
}
public static void Pret(EmitterContext context)
{
context.GetOp<InstPret>();
context.Config.GpuAccessor.Log("Shader instruction Pret is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Pret is not implemented.");
}
public static void PrmtR(EmitterContext context)
{
context.GetOp<InstPrmtR>();
context.Config.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented.");
}
public static void PrmtI(EmitterContext context)
{
context.GetOp<InstPrmtI>();
context.Config.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtI is not implemented.");
}
public static void PrmtC(EmitterContext context)
{
context.GetOp<InstPrmtC>();
context.Config.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented.");
}
public static void PrmtRc(EmitterContext context)
{
context.GetOp<InstPrmtRc>();
context.Config.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented.");
}
public static void R2b(EmitterContext context)
{
context.GetOp<InstR2b>();
context.Config.GpuAccessor.Log("Shader instruction R2b is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction R2b is not implemented.");
}
public static void Ram(EmitterContext context)
{
context.GetOp<InstRam>();
context.Config.GpuAccessor.Log("Shader instruction Ram is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Ram is not implemented.");
}
public static void Rtt(EmitterContext context)
{
context.GetOp<InstRtt>();
context.Config.GpuAccessor.Log("Shader instruction Rtt is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Rtt is not implemented.");
}
public static void Sam(EmitterContext context)
{
context.GetOp<InstSam>();
context.Config.GpuAccessor.Log("Shader instruction Sam is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Sam is not implemented.");
}
public static void Setcrsptr(EmitterContext context)
{
context.GetOp<InstSetcrsptr>();
context.Config.GpuAccessor.Log("Shader instruction Setcrsptr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Setcrsptr is not implemented.");
}
public static void Setlmembase(EmitterContext context)
{
context.GetOp<InstSetlmembase>();
context.Config.GpuAccessor.Log("Shader instruction Setlmembase is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Setlmembase is not implemented.");
}
public static void St(EmitterContext context)
{
context.GetOp<InstSt>();
context.Config.GpuAccessor.Log("Shader instruction St is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction St is not implemented.");
}
public static void Stp(EmitterContext context)
{
context.GetOp<InstStp>();
context.Config.GpuAccessor.Log("Shader instruction Stp is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Stp is not implemented.");
}
public static void Txa(EmitterContext context)
{
context.GetOp<InstTxa>();
context.Config.GpuAccessor.Log("Shader instruction Txa is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Txa is not implemented.");
}
public static void Vabsdiff(EmitterContext context)
{
context.GetOp<InstVabsdiff>();
context.Config.GpuAccessor.Log("Shader instruction Vabsdiff is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff is not implemented.");
}
public static void Vabsdiff4(EmitterContext context)
{
context.GetOp<InstVabsdiff4>();
context.Config.GpuAccessor.Log("Shader instruction Vabsdiff4 is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff4 is not implemented.");
}
public static void Vadd(EmitterContext context)
{
context.GetOp<InstVadd>();
context.Config.GpuAccessor.Log("Shader instruction Vadd is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vadd is not implemented.");
}
public static void Votevtg(EmitterContext context)
{
context.GetOp<InstVotevtg>();
context.Config.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
}
public static void Vset(EmitterContext context)
{
context.GetOp<InstVset>();
context.Config.GpuAccessor.Log("Shader instruction Vset is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vset is not implemented.");
}
public static void Vshl(EmitterContext context)
{
context.GetOp<InstVshl>();
context.Config.GpuAccessor.Log("Shader instruction Vshl is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshl is not implemented.");
}
public static void Vshr(EmitterContext context)
{
context.GetOp<InstVshr>();
context.Config.GpuAccessor.Log("Shader instruction Vshr is not implemented.");
context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshr is not implemented.");
}
}
}

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// Some of those attributes are per invocation,
// so we should ignore any primitive vertex indexing for those.
bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.Config.Stage, op.O) && !op.P;
bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.TranslatorContext.Definitions.Stage, op.O) && !op.P;
if (!op.Phys)
{
@ -52,10 +52,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
{
int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
context.FlagAttributeRead(offset);
int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
bool isOutput = op.O && CanLoadOutput(offset);
if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value))
@ -69,10 +66,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
int offset = FixedFuncToUserAttribute(context.Config, op.Imm11 + index * 4, op.O);
context.FlagAttributeRead(offset);
int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
bool isOutput = op.O && CanLoadOutput(offset);
context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false));
@ -98,7 +92,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
Operand vecIndex = context.ShiftRightU32(offset, Const(4));
Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
Operand invocationId = AttributeMap.HasInvocationId(context.Config.Stage, isOutput: true)
Operand invocationId = AttributeMap.HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput: true)
? context.Load(StorageKind.Input, IoVariable.InvocationId)
: null;
@ -110,15 +104,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
int offset = op.Imm11 + index * 4;
if (!context.Config.IsUsedOutputAttribute(offset))
if (!context.TranslatorContext.AttributeUsage.IsUsedOutputAttribute(offset))
{
return;
}
offset = FixedFuncToUserAttribute(context.Config, offset, isOutput: true);
context.FlagAttributeWritten(offset);
offset = FixedFuncToUserAttribute(context.TranslatorContext, offset, isOutput: true);
AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd));
}
}
@ -128,8 +119,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstIpa op = context.GetOp<InstIpa>();
context.FlagAttributeRead(op.Imm10);
Operand res;
bool isFixedFunc = false;
@ -151,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
int index = (op.Imm10 - AttributeConsts.UserAttributeBase) >> 4;
if (context.Config.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
if (context.TranslatorContext.Definitions.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
{
res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
}
@ -162,11 +151,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
// because the shader code is not expecting scaled values.
res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
if (op.Imm10 == AttributeConsts.PositionY && context.Config.Options.TargetApi != TargetApi.OpenGL)
if (op.Imm10 == AttributeConsts.PositionY && context.TranslatorContext.Options.TargetApi != TargetApi.OpenGL)
{
// If YNegate is enabled, we need to flip the fragment coordinates vertically, unless
// the API supports changing the origin (only OpenGL does).
if (context.Config.GpuAccessor.QueryYNegateEnabled())
if (context.TranslatorContext.Definitions.YNegateEnabled)
{
Operand viewportHeight = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.ViewportSize), Const(1));
@ -174,7 +163,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
}
else if (op.Imm10 == AttributeConsts.FrontFacing && context.Config.GpuAccessor.QueryHostHasFrontFacingBug())
else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug())
{
// gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
// This weird trick makes it behave.
@ -231,12 +220,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (!(emit || cut))
{
context.Config.GpuAccessor.Log("Invalid OUT encoding.");
context.TranslatorContext.GpuAccessor.Log("Invalid OUT encoding.");
}
if (emit)
{
if (context.Config.LastInVertexPipeline)
if (context.TranslatorContext.Definitions.LastInVertexPipeline)
{
context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
@ -289,13 +278,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// TODO: If two sided rendering is enabled, then this should return
// FrontColor if the fragment is front facing, and back color otherwise.
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
else if (attr == AttributeConsts.FogCoord)
{
// TODO: We likely need to emulate the fixed-function functionality for FogCoord here.
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
@ -305,7 +294,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.Config, attr, isOutput: false));
selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
return true;
}
@ -318,53 +307,44 @@ namespace Ryujinx.Graphics.Shader.Instructions
return AttributeMap.GenerateAttributeLoad(context, null, offset, isOutput: false, isPerPatch: false);
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, bool isOutput)
private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, bool isOutput)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
bool supportsLayerFromVertexOrTess = translatorContext.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
if (attr == AttributeConsts.Layer && config.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
if (attr == AttributeConsts.Layer && translatorContext.Definitions.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.Layer, 0, isOutput);
config.SetLayerOutputAttribute(attr);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.Layer, 0, isOutput);
translatorContext.SetLayerOutputAttribute(attr);
}
else if (attr == AttributeConsts.FogCoord)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
}
else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
}
else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
{
attr = FixedFuncToUserAttribute(config, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
}
return attr;
}
private static int FixedFuncToUserAttribute(ShaderConfig config, int attr, int baseAttr, int baseIndex, bool isOutput)
private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, int baseAttr, int baseIndex, bool isOutput)
{
int index = (attr - baseAttr) >> 4;
int userAttrIndex = config.GetFreeUserAttribute(isOutput, baseIndex + index);
int userAttrIndex = translatorContext.AttributeUsage.GetFreeUserAttribute(isOutput, baseIndex + index);
if ((uint)userAttrIndex < Constants.MaxAttributes)
{
attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
if (isOutput)
{
config.SetOutputUserAttributeFixedFunc(userAttrIndex);
}
else
{
config.SetInputUserAttributeFixedFunc(userAttrIndex);
}
}
else
{
config.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
translatorContext.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
}
return attr;
@ -372,7 +352,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value)
{
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
if (context.TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
{
if (attr == AttributeConsts.InstanceId)
{

View File

@ -16,7 +16,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid barrier mode: {op.BarOp}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid barrier mode: {op.BarOp}.");
}
}

View File

@ -174,7 +174,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (dstType == IDstFmt.U64)
{
context.Config.GpuAccessor.Log("Unimplemented 64-bits F2I.");
context.TranslatorContext.GpuAccessor.Log("Unimplemented 64-bits F2I.");
}
Instruction fpType = srcType.ToInstFPType();
@ -297,7 +297,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if ((srcType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32 || (dstType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32)
{
context.Config.GpuAccessor.Log("Invalid I2I encoding.");
context.TranslatorContext.GpuAccessor.Log("Invalid I2I encoding.");
return;
}

View File

@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (scaleConst.AsFloat() == 1f)
{
context.Config.GpuAccessor.Log($"Invalid FP multiply scale \"{scale}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid FP multiply scale \"{scale}\".");
}
if (isFP64)

View File

@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.CurrBlock.Successors.Count <= startIndex)
{
context.Config.GpuAccessor.Log($"Failed to find targets for BRX instruction at 0x{currOp.Address:X}.");
context.TranslatorContext.GpuAccessor.Log($"Failed to find targets for BRX instruction at 0x{currOp.Address:X}.");
return;
}
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (context.IsNonMain)
{
context.Config.GpuAccessor.Log("Invalid exit on non-main function.");
context.TranslatorContext.GpuAccessor.Log("Invalid exit on non-main function.");
return;
}
@ -218,7 +218,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log("Invalid return on main function.");
context.TranslatorContext.GpuAccessor.Log("Invalid return on main function.");
}
}

View File

@ -371,7 +371,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Iadd3 has invalid component selection {part}.");
context.TranslatorContext.GpuAccessor.Log($"Iadd3 has invalid component selection {part}.");
}
return src;
@ -555,7 +555,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
modeConv = XmadCop.Csfu;
break;
default:
context.Config.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
return;
}
@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
default:
context.Config.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\".");
return;
}

View File

@ -26,9 +26,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Atoms(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Atoms instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Atoms instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -50,7 +50,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
_ => AtomSize.U32,
};
Operand id = Const(context.Config.ResourceManager.SharedMemoryId);
Operand id = Const(context.ResourceManager.SharedMemoryId);
Operand res = EmitAtomicOp(context, StorageKind.SharedMemory, op.AtomOp, size, id, offset, value);
context.Copy(GetDest(op.Dest), res);
@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.LsSize > LsSize2.B64)
{
context.Config.GpuAccessor.Log($"Invalid LDC size: {op.LsSize}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid LDC size: {op.LsSize}.");
return;
}
@ -119,9 +119,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Lds(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Lds instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Lds instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -155,9 +155,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
public static void Sts(EmitterContext context)
{
if (context.Config.Stage != ShaderStage.Compute)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute)
{
context.Config.GpuAccessor.Log($"Sts instruction is not valid on \"{context.Config.Stage}\" stage.");
context.TranslatorContext.GpuAccessor.Log($"Sts instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage.");
return;
}
@ -173,19 +173,19 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (slot.Type == OperandType.Constant)
{
int binding = context.Config.ResourceManager.GetConstantBufferBinding(slot.Value);
int binding = context.ResourceManager.GetConstantBufferBinding(slot.Value);
return context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
}
else
{
Operand value = Const(0);
uint cbUseMask = context.Config.GpuAccessor.QueryConstantBufferUse();
uint cbUseMask = context.TranslatorContext.GpuAccessor.QueryConstantBufferUse();
while (cbUseMask != 0)
{
int cbIndex = BitOperations.TrailingZeroCount(cbUseMask);
int binding = context.Config.ResourceManager.GetConstantBufferBinding(cbIndex);
int binding = context.ResourceManager.GetConstantBufferBinding(cbIndex);
Operand isCurrent = context.ICompareEqual(slot, Const(cbIndex));
Operand currentValue = context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex);
@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.And:
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Xor:
@ -239,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Or:
@ -249,7 +249,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Max:
@ -263,7 +263,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
case AtomOp.Min:
@ -277,7 +277,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid reduction type: {type}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}.");
}
break;
}
@ -295,13 +295,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid load size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid load size: {size}.");
return;
}
int id = storageKind == StorageKind.LocalMemory
? context.Config.ResourceManager.LocalMemoryId
: context.Config.ResourceManager.SharedMemoryId;
? context.ResourceManager.LocalMemoryId
: context.ResourceManager.SharedMemoryId;
bool isSmallInt = size < LsSize2.B32;
int count = size switch
@ -376,13 +376,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid store size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}.");
return;
}
int id = storageKind == StorageKind.LocalMemory
? context.Config.ResourceManager.LocalMemoryId
: context.Config.ResourceManager.SharedMemoryId;
? context.ResourceManager.LocalMemoryId
: context.ResourceManager.SharedMemoryId;
bool isSmallInt = size < LsSize2.B32;
int count = size switch
@ -444,7 +444,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
if (size > LsSize2.B128)
{
context.Config.GpuAccessor.Log($"Invalid store size: {size}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}.");
return;
}

View File

@ -88,24 +88,24 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
case SReg.ThreadKill:
src = context.Config.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
src = context.TranslatorContext.Definitions.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0);
break;
case SReg.InvocationInfo:
if (context.Config.Stage != ShaderStage.Compute && context.Config.Stage != ShaderStage.Fragment)
if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute && context.TranslatorContext.Definitions.Stage != ShaderStage.Fragment)
{
// Note: Lowest 8-bits seems to contain some primitive index,
// but it seems to be NVIDIA implementation specific as it's only used
// to calculate ISBE offsets, so we can just keep it as zero.
if (context.Config.Stage == ShaderStage.TessellationControl ||
context.Config.Stage == ShaderStage.TessellationEvaluation)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl ||
context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation)
{
src = context.ShiftLeft(context.Load(StorageKind.Input, IoVariable.PatchVertices), Const(16));
}
else
{
src = Const(context.Config.GpuAccessor.QueryPrimitiveTopology().ToInputVertices() << 16);
src = Const(context.TranslatorContext.Definitions.InputTopology.ToInputVertices() << 16);
}
}
else

View File

@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
default:
context.Config.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\".");
context.TranslatorContext.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\".");
break;
}

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
@ -194,7 +195,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image atomic sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image atomic sampler type.");
return;
}
@ -258,7 +259,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// TODO: FP and 64-bit formats.
TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64
? (isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormatAtomic(imm))
? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm))
: GetTextureFormat(size);
if (compareAndSwap)
@ -277,7 +278,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@ -309,13 +310,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
SamplerType type = ConvertSamplerType(dimensions);
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image store sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type.");
return;
}
@ -388,9 +387,9 @@ namespace Ryujinx.Graphics.Shader.Instructions
Array.Resize(ref dests, outputIndex);
}
TextureFormat format = isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormat(handle);
TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle);
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@ -433,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = GetTextureFormat(size);
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@ -477,7 +476,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image reduction sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image reduction sampler type.");
return;
}
@ -539,7 +538,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
// TODO: FP and 64-bit formats.
TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64
? (isBindless ? TextureFormat.Unknown : context.Config.GetTextureFormatAtomic(imm))
? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm))
: GetTextureFormat(size);
sourcesList.Add(Rb());
@ -553,7 +552,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@ -582,7 +581,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid image store sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type.");
return;
}
@ -647,7 +646,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (!isBindless)
{
format = context.Config.GetTextureFormat(imm);
format = ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, imm);
}
}
else
@ -680,7 +679,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Coherent;
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageStore,
type,
format,

View File

@ -57,8 +57,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstTld op = context.GetOp<InstTld>();
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
var lod = op.Lod ? Lod.Ll : Lod.Lz;
EmitTex(context, TextureFlags.IntCoords, op.Dim, lod, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Ms, false, op.Toff);
@ -68,8 +66,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
InstTldB op = context.GetOp<InstTldB>();
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
var flags = TextureFlags.IntCoords | TextureFlags.Bindless;
var lod = op.Lod ? Lod.Ll : Lod.Lz;
@ -224,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// For bindless, we don't have any way to know the texture type,
// so we assume it's texture buffer when the sampler type is 1D, since that's more common.
bool isTypeBuffer = isBindless || context.Config.GpuAccessor.QuerySamplerType(imm) == SamplerType.TextureBuffer;
bool isTypeBuffer = isBindless || context.TranslatorContext.GpuAccessor.QuerySamplerType(imm) == SamplerType.TextureBuffer;
if (isTypeBuffer)
{
type = SamplerType.TextureBuffer;
@ -386,7 +382,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid texture sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid texture sampler type.");
return;
}
@ -478,16 +474,14 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (type == SamplerType.None)
{
context.Config.GpuAccessor.Log("Invalid texel fetch sampler type.");
context.TranslatorContext.GpuAccessor.Log("Invalid texel fetch sampler type.");
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
if (tldsOp.Target == TldsTarget.Texture1DLodZero &&
context.Config.GpuAccessor.QuerySamplerType(tldsOp.TidB) == SamplerType.TextureBuffer)
context.TranslatorContext.GpuAccessor.QuerySamplerType(tldsOp.TidB) == SamplerType.TextureBuffer)
{
type = SamplerType.TextureBuffer;
flags &= ~TextureFlags.LodLevel;
@ -884,7 +878,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(dest++, RegisterType.Gpr);
}
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.Lod,
type,
TextureFormat.Unknown,
@ -1065,8 +1059,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
context.Config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand Ra()
{
if (srcA > RegisterConsts.RegisterZeroIndex)
@ -1106,12 +1098,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
type = context.Config.GpuAccessor.QuerySamplerType(imm);
type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm);
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
int binding = isBindless ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSize,
type,
TextureFormat.Unknown,
@ -1147,7 +1139,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] dests,
Operand[] sources)
{
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.Config.ResourceManager.GetTextureOrImageBinding(
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSample,
type,
TextureFormat.Unknown,

View File

@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
else
{
context.Config.GpuAccessor.Log($"Invalid vote operation: {op.VoteMode}.");
context.TranslatorContext.GpuAccessor.Log($"Invalid vote operation: {op.VoteMode}.");
}
if (op.Dest != RegisterConsts.RegisterZeroIndex)

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
// When debug mode is enabled, we disable expression propagation
// (this makes comparison with the disassembly easier).
if (!context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
if (!context.DebugMode)
{
AstBlockVisitor visitor = new(mainBlock);

View File

@ -18,8 +18,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public IReadOnlyDictionary<int, MemoryDefinition> LocalMemories => _localMemories;
public IReadOnlyDictionary<int, MemoryDefinition> SharedMemories => _sharedMemories;
public readonly bool OriginUpperLeft;
public ShaderProperties()
{
_constantBuffers = new Dictionary<int, BufferDefinition>();
@ -30,29 +28,24 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
_sharedMemories = new Dictionary<int, MemoryDefinition>();
}
public ShaderProperties(bool originUpperLeft) : this()
public void AddOrUpdateConstantBuffer(BufferDefinition definition)
{
OriginUpperLeft = originUpperLeft;
_constantBuffers[definition.Binding] = definition;
}
public void AddOrUpdateConstantBuffer(int binding, BufferDefinition definition)
public void AddOrUpdateStorageBuffer(BufferDefinition definition)
{
_constantBuffers[binding] = definition;
_storageBuffers[definition.Binding] = definition;
}
public void AddOrUpdateStorageBuffer(int binding, BufferDefinition definition)
public void AddOrUpdateTexture(TextureDefinition definition)
{
_storageBuffers[binding] = definition;
_textures[definition.Binding] = definition;
}
public void AddOrUpdateTexture(int binding, TextureDefinition descriptor)
public void AddOrUpdateImage(TextureDefinition definition)
{
_textures[binding] = descriptor;
}
public void AddOrUpdateImage(int binding, TextureDefinition descriptor)
{
_images[binding] = descriptor;
_images[definition.Binding] = definition;
}
public int AddLocalMemory(MemoryDefinition definition)
@ -70,5 +63,48 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
return id;
}
public static TextureFormat GetTextureFormat(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1)
{
// When the formatted load extension is supported, we don't need to
// specify a format, we can just declare it without a format and the GPU will handle it.
if (gpuAccessor.QueryHostSupportsImageLoadFormatted())
{
return TextureFormat.Unknown;
}
var format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (format == TextureFormat.Unknown)
{
gpuAccessor.Log($"Unknown format for texture {handle}.");
format = TextureFormat.R8G8B8A8Unorm;
}
return format;
}
private static bool FormatSupportsAtomic(TextureFormat format)
{
return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
}
public static TextureFormat GetTextureFormatAtomic(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1)
{
// Atomic image instructions do not support GL_EXT_shader_image_load_formatted,
// and must have a type specified. Default to R32Sint if not available.
var format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (!FormatSupportsAtomic(format))
{
gpuAccessor.Log($"Unsupported format for texture {handle}: {format}.");
format = TextureFormat.R32Sint;
}
return format;
}
}
}

View File

@ -8,9 +8,14 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
static class StructuredProgram
{
public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList<Function> functions, ShaderConfig config)
public static StructuredProgramInfo MakeStructuredProgram(
IReadOnlyList<Function> functions,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
bool debugMode)
{
StructuredProgramContext context = new(config);
StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode);
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
{
@ -82,13 +87,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int location = 0;
int component = 0;
if (context.Config.HasPerLocationInputOrOutput(ioVariable, isOutput))
if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput))
{
location = operation.GetSource(1).Value;
if (operation.SourcesCount > 2 &&
operation.GetSource(2).Type == OperandType.Constant &&
context.Config.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput))
{
component = operation.GetSource(2).Value;
}
@ -98,7 +103,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
}
else if (storageKind == StorageKind.ConstantBuffer && operation.GetSource(0).Type == OperandType.Constant)
{
context.Config.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value);
context.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value);
}
}

View File

@ -28,17 +28,25 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public StructuredProgramInfo Info { get; }
public ShaderConfig Config { get; }
public ShaderDefinitions Definitions { get; }
public ResourceManager ResourceManager { get; }
public bool DebugMode { get; }
public StructuredProgramContext(ShaderConfig config)
public StructuredProgramContext(
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
bool debugMode)
{
Info = new StructuredProgramInfo();
Config = config;
Definitions = definitions;
ResourceManager = resourceManager;
DebugMode = debugMode;
if (config.GpPassthrough)
if (definitions.GpPassthrough)
{
int passthroughAttributes = config.PassthroughAttributes;
int passthroughAttributes = attributeUsage.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
@ -52,11 +60,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.PointSize));
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.ClipDistance));
}
else if (config.Stage == ShaderStage.Fragment)
{
// Potentially used for texture coordinate scaling.
Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord));
}
}
public void EnterFunction(
@ -304,11 +307,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
int cbufSlot = operand.GetCbufSlot();
int cbufOffset = operand.GetCbufOffset();
int binding = Config.ResourceManager.GetConstantBufferBinding(cbufSlot);
int binding = ResourceManager.GetConstantBufferBinding(cbufSlot);
int vecIndex = cbufOffset >> 2;
int elemIndex = cbufOffset & 3;
Config.ResourceManager.SetUsedConstantBufferBinding(binding);
ResourceManager.SetUsedConstantBufferBinding(binding);
IAstNode[] sources = new IAstNode[]
{

View File

@ -2,22 +2,6 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.StructuredIr
{
readonly struct TransformFeedbackOutput
{
public readonly bool Valid;
public readonly int Buffer;
public readonly int Offset;
public readonly int Stride;
public TransformFeedbackOutput(int buffer, int offset, int stride)
{
Valid = true;
Buffer = buffer;
Offset = offset;
Stride = stride;
}
}
class StructuredProgramInfo
{
public List<StructuredFunction> Functions { get; }

View File

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class AttributeUsage
{
public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; }
public int UsedOutputAttributes { get; private set; }
public HashSet<int> UsedInputAttributesPerPatch { get; }
public HashSet<int> UsedOutputAttributesPerPatch { get; }
public HashSet<int> NextUsedInputAttributesPerPatch { get; private set; }
public int PassthroughAttributes { get; private set; }
private int _nextUsedInputAttributes;
private int _thisUsedInputAttributes;
private Dictionary<int, int> _perPatchAttributeLocations;
private readonly IGpuAccessor _gpuAccessor;
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
public AttributeUsage(IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
UsedInputAttributesPerPatch = new();
UsedOutputAttributesPerPatch = new();
}
public void SetInputUserAttribute(int index, int component)
{
int mask = 1 << index;
UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.One << (index * 4 + component);
}
public void SetInputUserAttributePerPatch(int index)
{
UsedInputAttributesPerPatch.Add(index);
}
public void SetOutputUserAttribute(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetOutputUserAttributePerPatch(int index)
{
UsedOutputAttributesPerPatch.Add(index);
}
public void MergeFromtNextStage(bool gpPassthrough, bool nextUsesFixedFunctionAttributes, AttributeUsage nextStage)
{
NextInputAttributesComponents = nextStage.ThisInputAttributesComponents;
NextUsedInputAttributesPerPatch = nextStage.UsedInputAttributesPerPatch;
NextUsesFixedFuncAttributes = nextUsesFixedFunctionAttributes;
MergeOutputUserAttributes(gpPassthrough, nextStage.UsedInputAttributes, nextStage.UsedInputAttributesPerPatch);
if (UsedOutputAttributesPerPatch.Count != 0)
{
// Regular and per-patch input/output locations can't overlap,
// so we must assign on our location using unused regular input/output locations.
Dictionary<int, int> locationsMap = new();
int freeMask = ~UsedOutputAttributes;
foreach (int attr in UsedOutputAttributesPerPatch)
{
int location = BitOperations.TrailingZeroCount(freeMask);
if (location == 32)
{
_gpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}.");
break;
}
locationsMap.Add(attr, location);
freeMask &= ~(1 << location);
}
// Both stages must agree on the locations, so use the same "map" for both.
_perPatchAttributeLocations = locationsMap;
nextStage._perPatchAttributeLocations = locationsMap;
}
}
private void MergeOutputUserAttributes(bool gpPassthrough, int mask, IEnumerable<int> perPatch)
{
_nextUsedInputAttributes = mask;
if (gpPassthrough)
{
PassthroughAttributes = mask & ~UsedOutputAttributes;
}
else
{
UsedOutputAttributes |= mask;
UsedOutputAttributesPerPatch.UnionWith(perPatch);
}
}
public int GetPerPatchAttributeLocation(int index)
{
if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location))
{
return index;
}
return location;
}
public bool IsUsedOutputAttribute(int attr)
{
// The check for fixed function attributes on the next stage is conservative,
// returning false if the output is just not used by the next stage is also valid.
if (NextUsesFixedFuncAttributes &&
attr >= AttributeConsts.UserAttributeBase &&
attr < AttributeConsts.UserAttributeEnd)
{
int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
return (_nextUsedInputAttributes & (1 << index)) != 0;
}
return true;
}
public int GetFreeUserAttribute(bool isOutput, int index)
{
int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
int bit = -1;
while (useMask != -1)
{
bit = BitOperations.TrailingZeroCount(~useMask);
if (bit == 32)
{
bit = -1;
break;
}
else if (index < 1)
{
break;
}
useMask |= 1 << bit;
index--;
}
return bit;
}
public void SetAllInputUserAttributes()
{
UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
}
public void SetAllOutputUserAttributes()
{
UsedOutputAttributes |= Constants.AllAttributesMask;
}
}
}

View File

@ -11,7 +11,8 @@ namespace Ryujinx.Graphics.Shader.Translation
class EmitterContext
{
public DecodedProgram Program { get; }
public ShaderConfig Config { get; }
public TranslatorContext TranslatorContext { get; }
public ResourceManager ResourceManager { get; }
public bool IsNonMain { get; }
@ -54,10 +55,15 @@ namespace Ryujinx.Graphics.Shader.Translation
_labels = new Dictionary<ulong, BlockLabel>();
}
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this()
public EmitterContext(
TranslatorContext translatorContext,
ResourceManager resourceManager,
DecodedProgram program,
bool isNonMain) : this()
{
TranslatorContext = translatorContext;
ResourceManager = resourceManager;
Program = program;
Config = config;
IsNonMain = isNonMain;
EmitStart();
@ -65,12 +71,12 @@ namespace Ryujinx.Graphics.Shader.Translation
private void EmitStart()
{
if (Config.Stage == ShaderStage.Vertex &&
Config.Options.TargetApi == TargetApi.Vulkan &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex &&
TranslatorContext.Options.TargetApi == TargetApi.Vulkan &&
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
{
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(Config.GpuAccessor.QueryPointSize()));
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
}
}
@ -115,49 +121,6 @@ namespace Ryujinx.Graphics.Shader.Translation
_operations.Add(operation);
}
public void FlagAttributeRead(int attribute)
{
if (Config.Stage == ShaderStage.Vertex && attribute == AttributeConsts.InstanceId)
{
Config.SetUsedFeature(FeatureFlags.InstanceId);
}
else if (Config.Stage == ShaderStage.Fragment)
{
switch (attribute)
{
case AttributeConsts.PositionX:
case AttributeConsts.PositionY:
Config.SetUsedFeature(FeatureFlags.FragCoordXY);
break;
}
}
}
public void FlagAttributeWritten(int attribute)
{
if (Config.Stage == ShaderStage.Vertex)
{
switch (attribute)
{
case AttributeConsts.ClipDistance0:
case AttributeConsts.ClipDistance1:
case AttributeConsts.ClipDistance2:
case AttributeConsts.ClipDistance3:
case AttributeConsts.ClipDistance4:
case AttributeConsts.ClipDistance5:
case AttributeConsts.ClipDistance6:
case AttributeConsts.ClipDistance7:
Config.SetClipDistanceWritten((attribute - AttributeConsts.ClipDistance0) / 4);
break;
}
}
if (Config.Stage != ShaderStage.Fragment && attribute == AttributeConsts.Layer)
{
Config.SetUsedFeature(FeatureFlags.RtLayer);
}
}
public void MarkLabel(Operand label)
{
Add(Instruction.MarkLabel, label);
@ -203,14 +166,14 @@ namespace Ryujinx.Graphics.Shader.Translation
public void PrepareForVertexReturn()
{
if (!Config.GpuAccessor.QueryHostSupportsTransformFeedback() && Config.GpuAccessor.QueryTransformFeedbackEnabled())
if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled())
{
Operand vertexCount = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(1));
for (int tfbIndex = 0; tfbIndex < Constants.TfeBuffersCount; tfbIndex++)
{
var locations = Config.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = Config.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
Operand baseOffset = this.Load(StorageKind.StorageBuffer, Constants.TfeInfoBinding, Const(0), Const(tfbIndex));
Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex);
@ -242,7 +205,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
if (Config.GpuAccessor.QueryViewportTransformDisable())
if (TranslatorContext.Definitions.ViewportTransformDisable)
{
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
Operand y = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1));
@ -254,7 +217,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
}
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl())
{
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
@ -263,12 +226,10 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
}
if (Config.Stage != ShaderStage.Geometry && Config.HasLayerInputAttribute)
if (TranslatorContext.Definitions.Stage != ShaderStage.Geometry && TranslatorContext.HasLayerInputAttribute)
{
Config.SetUsedFeature(FeatureFlags.RtLayer);
int attrVecIndex = Config.GpLayerInputAttribute >> 2;
int attrComponentIndex = Config.GpLayerInputAttribute & 3;
int attrVecIndex = TranslatorContext.GpLayerInputAttribute >> 2;
int attrComponentIndex = TranslatorContext.GpLayerInputAttribute & 3;
Operand layer = this.Load(StorageKind.Output, IoVariable.UserDefined, null, Const(attrVecIndex), Const(attrComponentIndex));
@ -278,7 +239,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
{
if (Config.GpuAccessor.QueryViewportTransformDisable())
if (TranslatorContext.Definitions.ViewportTransformDisable)
{
oldXLocal = Local();
this.Copy(oldXLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(0)));
@ -291,7 +252,7 @@ namespace Ryujinx.Graphics.Shader.Translation
oldYLocal = null;
}
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl())
{
oldZLocal = Local();
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
@ -311,13 +272,13 @@ namespace Ryujinx.Graphics.Shader.Translation
return true;
}
if (Config.LastInVertexPipeline &&
(Config.Stage == ShaderStage.Vertex || Config.Stage == ShaderStage.TessellationEvaluation) &&
(Config.Options.Flags & TranslationFlags.VertexA) == 0)
if (TranslatorContext.Definitions.LastInVertexPipeline &&
(TranslatorContext.Definitions.Stage == ShaderStage.Vertex || TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation) &&
(TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0)
{
PrepareForVertexReturn();
}
else if (Config.Stage == ShaderStage.Geometry)
else if (TranslatorContext.Definitions.Stage == ShaderStage.Geometry)
{
void WritePositionOutput(int primIndex)
{
@ -345,20 +306,19 @@ namespace Ryujinx.Graphics.Shader.Translation
this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(3), w);
}
if (Config.GpPassthrough && !Config.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough())
{
int inputVertices = Config.GpuAccessor.QueryPrimitiveTopology().ToInputVertices();
int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices();
for (int primIndex = 0; primIndex < inputVertices; primIndex++)
{
WritePositionOutput(primIndex);
int passthroughAttributes = Config.PassthroughAttributes;
int passthroughAttributes = TranslatorContext.AttributeUsage.PassthroughAttributes;
while (passthroughAttributes != 0)
{
int index = BitOperations.TrailingZeroCount(passthroughAttributes);
WriteUserDefinedOutput(index, primIndex);
Config.SetOutputUserAttribute(index);
passthroughAttributes &= ~(1 << index);
}
@ -368,20 +328,20 @@ namespace Ryujinx.Graphics.Shader.Translation
this.EndPrimitive();
}
}
else if (Config.Stage == ShaderStage.Fragment)
else if (TranslatorContext.Definitions.Stage == ShaderStage.Fragment)
{
GenerateAlphaToCoverageDitherDiscard();
bool supportsBgra = Config.GpuAccessor.QueryHostSupportsBgraFormat();
bool supportsBgra = TranslatorContext.GpuAccessor.QueryHostSupportsBgraFormat();
if (Config.OmapDepth)
if (TranslatorContext.Definitions.OmapDepth)
{
Operand src = Register(Config.GetDepthRegister(), RegisterType.Gpr);
Operand src = Register(TranslatorContext.GetDepthRegister(), RegisterType.Gpr);
this.Store(StorageKind.Output, IoVariable.FragmentOutputDepth, null, src);
}
AlphaTestOp alphaTestOp = Config.GpuAccessor.QueryAlphaTestCompare();
AlphaTestOp alphaTestOp = TranslatorContext.Definitions.AlphaTestCompare;
if (alphaTestOp != AlphaTestOp.Always)
{
@ -389,7 +349,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
this.Discard();
}
else if ((Config.OmapTargets & 8) != 0)
else if ((TranslatorContext.Definitions.OmapTargets & 8) != 0)
{
Instruction comparator = alphaTestOp switch
{
@ -405,7 +365,7 @@ namespace Ryujinx.Graphics.Shader.Translation
Debug.Assert(comparator != 0, $"Invalid alpha test operation \"{alphaTestOp}\".");
Operand alpha = Register(3, RegisterType.Gpr);
Operand alphaRef = ConstF(Config.GpuAccessor.QueryAlphaTestReference());
Operand alphaRef = ConstF(TranslatorContext.Definitions.AlphaTestReference);
Operand alphaPass = Add(Instruction.FP32 | comparator, Local(), alpha, alphaRef);
Operand alphaPassLabel = Label();
@ -427,7 +387,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
for (int component = 0; component < 4; component++)
{
bool componentEnabled = (Config.OmapTargets & (1 << (rtIndex * 4 + component))) != 0;
bool componentEnabled = (TranslatorContext.Definitions.OmapTargets & (1 << (rtIndex * 4 + component))) != 0;
if (!componentEnabled)
{
continue;
@ -460,10 +420,9 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
bool targetEnabled = (Config.OmapTargets & (0xf << (rtIndex * 4))) != 0;
bool targetEnabled = (TranslatorContext.Definitions.OmapTargets & (0xf << (rtIndex * 4))) != 0;
if (targetEnabled)
{
Config.SetOutputUserAttribute(rtIndex);
regIndexBase += 4;
}
}
@ -475,7 +434,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private void GenerateAlphaToCoverageDitherDiscard()
{
// If the feature is disabled, or alpha is not written, then we're done.
if (!Config.GpuAccessor.QueryAlphaToCoverageDitherEnable() || (Config.OmapTargets & 8) == 0)
if (!TranslatorContext.Definitions.AlphaToCoverageDitherEnable || (TranslatorContext.Definitions.OmapTargets & 8) == 0)
{
return;
}

View File

@ -12,15 +12,12 @@ namespace Ryujinx.Graphics.Shader.Translation
None = 0,
// Affected by resolution scaling.
IntegerSampling = 1 << 0,
FragCoordXY = 1 << 1,
Bindless = 1 << 2,
InstanceId = 1 << 3,
DrawParameters = 1 << 4,
RtLayer = 1 << 5,
IaIndexing = 1 << 7,
OaIndexing = 1 << 8,
FixedFuncAttr = 1 << 9,
LocalMemory = 1 << 10,
SharedMemory = 1 << 11,

View File

@ -0,0 +1,34 @@
namespace Ryujinx.Graphics.Shader.Translation
{
class HostCapabilities
{
public readonly bool ReducedPrecision;
public readonly bool SupportsFragmentShaderInterlock;
public readonly bool SupportsFragmentShaderOrderingIntel;
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
public HostCapabilities(
bool reducedPrecision,
bool supportsFragmentShaderInterlock,
bool supportsFragmentShaderOrderingIntel,
bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
ReducedPrecision = reducedPrecision;
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}
}
}

View File

@ -1,12 +1,13 @@
using Ryujinx.Graphics.Shader.Instructions;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
class BindlessElimination
{
public static void RunPass(BasicBlock block, ShaderConfig config)
public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// We can turn a bindless into regular access by recognizing the pattern
// produced by the compiler for separate texture and sampler.
@ -43,7 +44,15 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
SetHandle(config, texOp, bindlessHandle.GetCbufOffset(), bindlessHandle.GetCbufSlot(), rewriteSamplerType, isImage: false);
SetHandle(
resourceManager,
gpuAccessor,
texOp,
bindlessHandle.GetCbufOffset(),
bindlessHandle.GetCbufSlot(),
rewriteSamplerType,
isImage: false);
continue;
}
@ -140,7 +149,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
SetHandle(
config,
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
@ -150,7 +160,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
config,
resourceManager,
gpuAccessor,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
@ -173,17 +184,17 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
if (texOp.Inst == Instruction.ImageAtomic)
{
texOp.Format = config.GetTextureFormatAtomic(cbufOffset, cbufSlot);
texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot);
}
else
{
texOp.Format = config.GetTextureFormat(cbufOffset, cbufSlot);
texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot);
}
}
bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer;
SetHandle(config, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true);
}
}
}
@ -220,11 +231,18 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return null;
}
private static void SetHandle(ShaderConfig config, TextureOperation texOp, int cbufOffset, int cbufSlot, bool rewriteSamplerType, bool isImage)
private static void SetHandle(
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TextureOperation texOp,
int cbufOffset,
int cbufSlot,
bool rewriteSamplerType,
bool isImage)
{
if (rewriteSamplerType)
{
SamplerType newType = config.GpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
SamplerType newType = gpuAccessor.QuerySamplerType(cbufOffset, cbufSlot);
if (texOp.Inst.IsTextureQuery())
{
@ -253,7 +271,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
int binding = config.ResourceManager.GetTextureOrImageBinding(
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,

View File

@ -9,7 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
private const int NvnTextureBufferIndex = 2;
public static void RunPass(BasicBlock block, ShaderConfig config)
public static void RunPass(BasicBlock block, ResourceManager resourceManager)
{
// We can turn a bindless texture access into a indexed access,
// as long the following conditions are true:
@ -44,7 +44,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand ldcSrc0 = handleAsgOp.GetSource(0);
if (ldcSrc0.Type != OperandType.Constant ||
!config.ResourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
!resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) ||
src0CbufSlot != NvnTextureBufferIndex)
{
continue;
@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
TurnIntoIndexed(config, texOp, addSrc1.Value / 4);
TurnIntoIndexed(resourceManager, texOp, addSrc1.Value / 4);
Operand index = Local();
@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
private static void TurnIntoIndexed(ShaderConfig config, TextureOperation texOp, int handle)
private static void TurnIntoIndexed(ResourceManager resourceManager, TextureOperation texOp, int handle)
{
int binding = config.ResourceManager.GetTextureOrImageBinding(
int binding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type | SamplerType.Indexed,
texOp.Format,

View File

@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class ConstantFolding
{
public static void RunPass(ShaderConfig config, Operation operation)
public static void RunPass(ResourceManager resourceManager, Operation operation)
{
if (!AreAllSourcesConstant(operation))
{
@ -158,7 +158,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
int binding = operation.GetSource(0).Value;
int fieldIndex = operation.GetSource(1).Value;
if (config.ResourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0)
if (resourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0)
{
int vecIndex = operation.GetSource(2).Value;
int elemIndex = operation.GetSource(3).Value;

View File

@ -205,7 +205,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static void RunPass(
HelperFunctionManager hfm,
BasicBlock[] blocks,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage)
{
GtsContext gtsContext = new(hfm);
@ -220,14 +225,20 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (IsGlobalMemory(operation.StorageKind))
{
LinkedListNode<INode> nextNode = ReplaceGlobalMemoryWithStorage(gtsContext, config, block, node);
LinkedListNode<INode> nextNode = ReplaceGlobalMemoryWithStorage(
gtsContext,
resourceManager,
gpuAccessor,
targetLanguage,
block,
node);
if (nextNode == null)
{
// The returned value being null means that the global memory replacement failed,
// so we just make loads read 0 and stores do nothing.
config.GpuAccessor.Log($"Failed to reserve storage buffer for global memory operation \"{operation.Inst}\".");
gpuAccessor.Log($"Failed to reserve storage buffer for global memory operation \"{operation.Inst}\".");
if (operation.Dest != null)
{
@ -286,7 +297,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static LinkedListNode<INode> ReplaceGlobalMemoryWithStorage(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
BasicBlock block,
LinkedListNode<INode> node)
{
@ -303,7 +316,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand offset = result.Offset;
bool storageUnaligned = config.GpuAccessor.QueryHasUnalignedStorageBuffer();
bool storageUnaligned = gpuAccessor.QueryHasUnalignedStorageBuffer();
if (storageUnaligned)
{
@ -312,7 +325,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand baseAddressMasked = Local();
Operand hostOffset = Local();
int alignment = config.GpuAccessor.QueryHostStorageBufferOffsetAlignment();
int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment();
Operation maskOp = new(Instruction.BitwiseAnd, baseAddressMasked, baseAddress, Const(-alignment));
Operation subOp = new(Instruction.Subtract, hostOffset, globalAddress, baseAddressMasked);
@ -333,13 +346,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
offset = newOffset;
}
if (CanUseInlineStorageOp(operation, config.Options.TargetLanguage))
if (CanUseInlineStorageOp(operation, targetLanguage))
{
return GenerateInlineStorageOp(config, node, operation, offset, result);
return GenerateInlineStorageOp(resourceManager, node, operation, offset, result);
}
else
{
if (!TryGenerateSingleTargetStorageOp(gtsContext, config, operation, result, out int functionId))
if (!TryGenerateSingleTargetStorageOp(
gtsContext,
resourceManager,
targetLanguage,
operation,
result,
out int functionId))
{
return null;
}
@ -354,7 +373,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// the base address might be stored.
// Generate a helper function that will check all possible storage buffers and use the right one.
if (!TryGenerateMultiTargetStorageOp(gtsContext, config, block, operation, out int functionId))
if (!TryGenerateMultiTargetStorageOp(
gtsContext,
resourceManager,
gpuAccessor,
targetLanguage,
block,
operation,
out int functionId))
{
return null;
}
@ -375,14 +401,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
private static LinkedListNode<INode> GenerateInlineStorageOp(
ShaderConfig config,
ResourceManager resourceManager,
LinkedListNode<INode> node,
Operation operation,
Operand offset,
SearchResult result)
{
bool isStore = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
if (!config.ResourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
{
return null;
}
@ -474,7 +500,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static bool TryGenerateSingleTargetStorageOp(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
Operation operation,
SearchResult result,
out int functionId)
@ -514,7 +541,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
if (!TryGenerateStorageOp(
config,
resourceManager,
targetLanguage,
context,
operation.Inst,
operation.StorageKind,
@ -555,7 +583,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static bool TryGenerateMultiTargetStorageOp(
GtsContext gtsContext,
ShaderConfig config,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
BasicBlock block,
Operation operation,
out int functionId)
@ -624,7 +654,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (targetCbs.Count == 0)
{
config.GpuAccessor.Log($"Failed to find storage buffer for global memory operation \"{operation.Inst}\".");
gpuAccessor.Log($"Failed to find storage buffer for global memory operation \"{operation.Inst}\".");
}
if (gtsContext.TryGetFunctionId(operation, isMultiTarget: true, targetCbs, out functionId))
@ -685,13 +715,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
SearchResult result = new(sbCbSlot, sbCbOffset);
int alignment = config.GpuAccessor.QueryHostStorageBufferOffsetAlignment();
int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment();
Operand baseAddressMasked = context.BitwiseAnd(baseAddrLow, Const(-alignment));
Operand hostOffset = context.ISubtract(globalAddressLow, baseAddressMasked);
if (!TryGenerateStorageOp(
config,
resourceManager,
targetLanguage,
context,
operation.Inst,
operation.StorageKind,
@ -781,7 +812,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
private static bool TryGenerateStorageOp(
ShaderConfig config,
ResourceManager resourceManager,
TargetLanguage targetLanguage,
EmitterContext context,
Instruction inst,
StorageKind storageKind,
@ -794,7 +826,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = null;
bool isStore = inst.IsAtomic() || inst == Instruction.Store;
if (!config.ResourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding))
{
return false;
}
@ -820,7 +852,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = context.AtomicCompareAndSwap(StorageKind.StorageBuffer, binding, Const(0), wordOffset, compare, value);
break;
case Instruction.AtomicMaxS32:
if (config.Options.TargetLanguage == TargetLanguage.Spirv)
if (targetLanguage == TargetLanguage.Spirv)
{
resultValue = context.AtomicMaxS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
}
@ -836,7 +868,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resultValue = context.AtomicMaxU32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
break;
case Instruction.AtomicMinS32:
if (config.Options.TargetLanguage == TargetLanguage.Spirv)
if (targetLanguage == TargetLanguage.Spirv)
{
resultValue = context.AtomicMinS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value);
}

View File

@ -7,40 +7,40 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class Optimizer
{
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static void RunPass(TransformContext context)
{
RunOptimizationPasses(blocks, config);
RunOptimizationPasses(context.Blocks, context.ResourceManager);
// TODO: Some of those are not optimizations and shouldn't be here.
GlobalToStorage.RunPass(hfm, blocks, config);
GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage);
bool hostSupportsShaderFloat64 = config.GpuAccessor.QueryHostSupportsShaderFloat64();
bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64();
// Those passes are looking for specific patterns and only needs to run once.
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
BindlessToIndexed.RunPass(blocks[blkIndex], config);
BindlessElimination.RunPass(blocks[blkIndex], config);
BindlessToIndexed.RunPass(context.Blocks[blkIndex], context.ResourceManager);
BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor);
// FragmentCoord only exists on fragment shaders, so we don't need to check other stages.
if (config.Stage == ShaderStage.Fragment)
if (context.Stage == ShaderStage.Fragment)
{
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
EliminateMultiplyByFragmentCoordW(context.Blocks[blkIndex]);
}
// If the host does not support double operations, we need to turn them into float operations.
if (!hostSupportsShaderFloat64)
{
DoubleToFloat.RunPass(hfm, blocks[blkIndex]);
DoubleToFloat.RunPass(context.Hfm, context.Blocks[blkIndex]);
}
}
// Run optimizations one last time to remove any code that is now optimizable after above passes.
RunOptimizationPasses(blocks, config);
RunOptimizationPasses(context.Blocks, context.ResourceManager);
}
private static void RunOptimizationPasses(BasicBlock[] blocks, ShaderConfig config)
private static void RunOptimizationPasses(BasicBlock[] blocks, ResourceManager resourceManager)
{
bool modified;
@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
ConstantFolding.RunPass(config, operation);
ConstantFolding.RunPass(resourceManager, operation);
Simplification.RunPass(operation);
if (DestIsLocalVar(operation))

View File

@ -50,10 +50,10 @@ namespace Ryujinx.Graphics.Shader.Translation
public ShaderProperties Properties { get; }
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties)
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor)
{
_gpuAccessor = gpuAccessor;
Properties = properties;
Properties = new();
_stage = stage;
_stagePrefix = GetShaderStagePrefix(stage);
@ -62,15 +62,15 @@ namespace Ryujinx.Graphics.Shader.Translation
_cbSlotToBindingMap.AsSpan().Fill(-1);
_sbSlotToBindingMap.AsSpan().Fill(-1);
_sbSlots = new Dictionary<int, int>();
_sbSlotsReverse = new Dictionary<int, int>();
_sbSlots = new();
_sbSlotsReverse = new();
_usedConstantBufferBindings = new HashSet<int>();
_usedConstantBufferBindings = new();
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
_usedTextures = new();
_usedImages = new();
properties.AddOrUpdateConstantBuffer(0, new BufferDefinition(BufferLayout.Std140, 0, 0, "support_buffer", SupportBuffer.GetStructureType()));
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType()));
LocalMemoryId = -1;
SharedMemoryId = -1;
@ -312,11 +312,11 @@ namespace Ryujinx.Graphics.Shader.Translation
if (isImage)
{
Properties.AddOrUpdateImage(binding, definition);
Properties.AddOrUpdateImage(definition);
}
else
{
Properties.AddOrUpdateTexture(binding, definition);
Properties.AddOrUpdateTexture(definition);
}
if (layer == 0)
@ -500,7 +500,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
});
Properties.AddOrUpdateConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, binding, name, type));
}
private void AddNewStorageBuffer(int binding, string name)
@ -510,7 +510,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
});
Properties.AddOrUpdateStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, 1, binding, name, type));
}
public static string GetShaderStagePrefix(ShaderStage stage)

View File

@ -1,639 +0,0 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderConfig
{
private const int ThreadsPerWarp = 32;
public ShaderStage Stage { get; }
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; private set; }
public bool HasLayerInputAttribute { get; private set; }
public int GpLayerInputAttribute { get; private set; }
public int ThreadsPerInputPrimitive { get; }
public OutputTopology OutputTopology { get; }
public int MaxOutputVertices { get; }
public int LocalMemorySize { get; }
public ImapPixelType[] ImapTypes { get; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public IGpuAccessor GpuAccessor { get; }
public TranslationOptions Options { get; }
public ShaderProperties Properties => ResourceManager.Properties;
public ResourceManager ResourceManager { get; set; }
public bool TransformFeedbackEnabled { get; }
private TransformFeedbackOutput[] _transformFeedbackOutputs;
readonly struct TransformFeedbackVariable : IEquatable<TransformFeedbackVariable>
{
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0)
{
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is TransformFeedbackVariable tfbVar && Equals(tfbVar);
}
public bool Equals(TransformFeedbackVariable other)
{
return IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)IoVariable | (Location << 8) | (Component << 16);
}
public override string ToString()
{
return $"{IoVariable}.{Location}.{Component}";
}
}
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
public int Size { get; private set; }
public byte ClipDistancesWritten { get; private set; }
public FeatureFlags UsedFeatures { get; private set; }
public int Cb1DataSize { get; private set; }
public bool LayerOutputWritten { get; private set; }
public int LayerOutputAttribute { get; private set; }
public bool NextUsesFixedFuncAttributes { get; private set; }
public int UsedInputAttributes { get; private set; }
public int UsedOutputAttributes { get; private set; }
public HashSet<int> UsedInputAttributesPerPatch { get; }
public HashSet<int> UsedOutputAttributesPerPatch { get; }
public HashSet<int> NextUsedInputAttributesPerPatch { get; private set; }
public int PassthroughAttributes { get; private set; }
private int _nextUsedInputAttributes;
private int _thisUsedInputAttributes;
private Dictionary<int, int> _perPatchAttributeLocations;
public UInt128 NextInputAttributesComponents { get; private set; }
public UInt128 ThisInputAttributesComponents { get; private set; }
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options, int localMemorySize)
{
Stage = stage;
GpuAccessor = gpuAccessor;
Options = options;
LocalMemorySize = localMemorySize;
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
TransformFeedbackEnabled =
stage != ShaderStage.Compute &&
gpuAccessor.QueryTransformFeedbackEnabled() &&
gpuAccessor.QueryHostSupportsTransformFeedback();
UsedInputAttributesPerPatch = new HashSet<int>();
UsedOutputAttributesPerPatch = new HashSet<int>();
ShaderProperties properties;
switch (stage)
{
case ShaderStage.Fragment:
bool originUpperLeft = options.TargetApi == TargetApi.Vulkan || gpuAccessor.QueryYNegateEnabled();
properties = new ShaderProperties(originUpperLeft);
break;
default:
properties = new ShaderProperties();
break;
}
ResourceManager = new ResourceManager(stage, gpuAccessor, properties);
if (!gpuAccessor.QueryHostSupportsTransformFeedback() && gpuAccessor.QueryTransformFeedbackEnabled())
{
StructureType tfeInfoStruct = new(new StructureField[]
{
new(AggregateType.Array | AggregateType.U32, "base_offset", 4),
new(AggregateType.U32, "vertex_count"),
});
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
properties.AddOrUpdateStorageBuffer(Constants.TfeInfoBinding, tfeInfoBuffer);
StructureType tfeDataStruct = new(new StructureField[]
{
new(AggregateType.Array | AggregateType.U32, "data", 0),
});
for (int i = 0; i < Constants.TfeBuffersCount; i++)
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
properties.AddOrUpdateStorageBuffer(binding, tfeDataBuffer);
}
}
}
public ShaderConfig(
ShaderStage stage,
OutputTopology outputTopology,
int maxOutputVertices,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(stage, gpuAccessor, options, 0)
{
ThreadsPerInputPrimitive = 1;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
}
public ShaderConfig(
ShaderHeader header,
IGpuAccessor gpuAccessor,
TranslationOptions options) : this(header.Stage, gpuAccessor, options, GetLocalMemorySize(header))
{
GpPassthrough = header.Stage == ShaderStage.Geometry && header.GpPassthrough;
ThreadsPerInputPrimitive = header.ThreadsPerInputPrimitive;
OutputTopology = header.OutputTopology;
MaxOutputVertices = header.MaxOutputVertexCount;
ImapTypes = header.ImapTypes;
OmapTargets = header.OmapTargets;
OmapSampleMask = header.OmapSampleMask;
OmapDepth = header.OmapDepth;
LastInVertexPipeline = header.Stage < ShaderStage.Fragment;
}
private static int GetLocalMemorySize(ShaderHeader header)
{
return header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp);
}
private void EnsureTransformFeedbackInitialized()
{
if (HasTransformFeedbackOutputs() && _transformFeedbackOutputs == null)
{
TransformFeedbackOutput[] transformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
ulong vecMap = 0UL;
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = GpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte wordOffset = locations[i];
if (wordOffset < 0xc0)
{
transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
vecMap |= 1UL << (wordOffset / 4);
}
}
}
_transformFeedbackOutputs = transformFeedbackOutputs;
while (vecMap != 0)
{
int vecIndex = BitOperations.TrailingZeroCount(vecMap);
for (int subIndex = 0; subIndex < 4; subIndex++)
{
int wordOffset = vecIndex * 4 + subIndex;
int byteOffset = wordOffset * 4;
if (transformFeedbackOutputs[wordOffset].Valid)
{
IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location);
int component = 0;
if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true))
{
component = subIndex;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
_transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]);
}
}
vecMap &= ~(1UL << vecIndex);
}
}
}
public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
{
EnsureTransformFeedbackInitialized();
return _transformFeedbackOutputs;
}
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
{
EnsureTransformFeedbackInitialized();
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput);
}
private bool HasTransformFeedbackOutputs()
{
return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment);
}
public bool HasTransformFeedbackOutputs(bool isOutput)
{
return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment));
}
public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput)
{
if (ioVariable == IoVariable.UserDefined)
{
return (!isOutput && !UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutput && !UsedFeatures.HasFlag(FeatureFlags.OaIndexing));
}
return ioVariable == IoVariable.FragmentOutputColor;
}
public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput)
{
if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput))
{
return false;
}
return GetTransformFeedbackOutputComponents(location, component) == 1;
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset)
{
EnsureTransformFeedbackInitialized();
return _transformFeedbackOutputs[wordOffset];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
{
return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component);
}
public int GetTransformFeedbackOutputComponents(int location, int component)
{
EnsureTransformFeedbackInitialized();
int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4;
int index = baseIndex + component;
int count = 1;
for (; count < 4; count++)
{
ref var prev = ref _transformFeedbackOutputs[baseIndex + count - 1];
ref var curr = ref _transformFeedbackOutputs[baseIndex + count];
int prevOffset = prev.Offset;
int currOffset = curr.Offset;
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
{
break;
}
}
if (baseIndex + count <= index)
{
return 1;
}
return count;
}
public AggregateType GetFragmentOutputColorType(int location)
{
return AggregateType.Vector4 | GpuAccessor.QueryFragmentOutputType(location).ToAggregateType();
}
public AggregateType GetUserDefinedType(int location, bool isOutput)
{
if ((!isOutput && UsedFeatures.HasFlag(FeatureFlags.IaIndexing)) ||
(isOutput && UsedFeatures.HasFlag(FeatureFlags.OaIndexing)))
{
return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32;
}
AggregateType type = AggregateType.Vector4;
if (Stage == ShaderStage.Vertex && !isOutput)
{
type |= GpuAccessor.QueryAttributeType(location).ToAggregateType();
}
else
{
type |= AggregateType.FP32;
}
return type;
}
public int GetDepthRegister()
{
// The depth register is always two registers after the last color output.
return BitOperations.PopCount((uint)OmapTargets) + 1;
}
public uint ConstantBuffer1Read(int offset)
{
if (Cb1DataSize < offset + 4)
{
Cb1DataSize = offset + 4;
}
return GpuAccessor.ConstantBuffer1Read(offset);
}
public TextureFormat GetTextureFormat(int handle, int cbufSlot = -1)
{
// When the formatted load extension is supported, we don't need to
// specify a format, we can just declare it without a format and the GPU will handle it.
if (GpuAccessor.QueryHostSupportsImageLoadFormatted())
{
return TextureFormat.Unknown;
}
var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (format == TextureFormat.Unknown)
{
GpuAccessor.Log($"Unknown format for texture {handle}.");
format = TextureFormat.R8G8B8A8Unorm;
}
return format;
}
private static bool FormatSupportsAtomic(TextureFormat format)
{
return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint;
}
public TextureFormat GetTextureFormatAtomic(int handle, int cbufSlot = -1)
{
// Atomic image instructions do not support GL_EXT_shader_image_load_formatted,
// and must have a type specified. Default to R32Sint if not available.
var format = GpuAccessor.QueryTextureFormat(handle, cbufSlot);
if (!FormatSupportsAtomic(format))
{
GpuAccessor.Log($"Unsupported format for texture {handle}: {format}.");
format = TextureFormat.R32Sint;
}
return format;
}
public void SizeAdd(int size)
{
Size += size;
}
public void InheritFrom(ShaderConfig other)
{
ClipDistancesWritten |= other.ClipDistancesWritten;
UsedFeatures |= other.UsedFeatures;
UsedInputAttributes |= other.UsedInputAttributes;
UsedOutputAttributes |= other.UsedOutputAttributes;
}
public void SetLayerOutputAttribute(int attr)
{
LayerOutputWritten = true;
LayerOutputAttribute = attr;
}
public void SetGeometryShaderLayerInputAttribute(int attr)
{
HasLayerInputAttribute = true;
GpLayerInputAttribute = attr;
}
public void SetLastInVertexPipeline()
{
LastInVertexPipeline = true;
}
public void SetInputUserAttributeFixedFunc(int index)
{
UsedInputAttributes |= 1 << index;
}
public void SetOutputUserAttributeFixedFunc(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetInputUserAttribute(int index, int component)
{
int mask = 1 << index;
UsedInputAttributes |= mask;
_thisUsedInputAttributes |= mask;
ThisInputAttributesComponents |= UInt128.One << (index * 4 + component);
}
public void SetInputUserAttributePerPatch(int index)
{
UsedInputAttributesPerPatch.Add(index);
}
public void SetOutputUserAttribute(int index)
{
UsedOutputAttributes |= 1 << index;
}
public void SetOutputUserAttributePerPatch(int index)
{
UsedOutputAttributesPerPatch.Add(index);
}
public void MergeFromtNextStage(ShaderConfig config)
{
NextInputAttributesComponents = config.ThisInputAttributesComponents;
NextUsedInputAttributesPerPatch = config.UsedInputAttributesPerPatch;
NextUsesFixedFuncAttributes = config.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr);
MergeOutputUserAttributes(config.UsedInputAttributes, config.UsedInputAttributesPerPatch);
if (UsedOutputAttributesPerPatch.Count != 0)
{
// Regular and per-patch input/output locations can't overlap,
// so we must assign on our location using unused regular input/output locations.
Dictionary<int, int> locationsMap = new();
int freeMask = ~UsedOutputAttributes;
foreach (int attr in UsedOutputAttributesPerPatch)
{
int location = BitOperations.TrailingZeroCount(freeMask);
if (location == 32)
{
config.GpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}.");
break;
}
locationsMap.Add(attr, location);
freeMask &= ~(1 << location);
}
// Both stages must agree on the locations, so use the same "map" for both.
_perPatchAttributeLocations = locationsMap;
config._perPatchAttributeLocations = locationsMap;
}
// We don't consider geometry shaders using the geometry shader passthrough feature
// as being the last because when this feature is used, it can't actually modify any of the outputs,
// so the stage that comes before it is the last one that can do modifications.
if (config.Stage != ShaderStage.Fragment && (config.Stage != ShaderStage.Geometry || !config.GpPassthrough))
{
LastInVertexPipeline = false;
}
}
public void MergeOutputUserAttributes(int mask, IEnumerable<int> perPatch)
{
_nextUsedInputAttributes = mask;
if (GpPassthrough)
{
PassthroughAttributes = mask & ~UsedOutputAttributes;
}
else
{
UsedOutputAttributes |= mask;
UsedOutputAttributesPerPatch.UnionWith(perPatch);
}
}
public int GetPerPatchAttributeLocation(int index)
{
if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location))
{
return index;
}
return location;
}
public bool IsUsedOutputAttribute(int attr)
{
// The check for fixed function attributes on the next stage is conservative,
// returning false if the output is just not used by the next stage is also valid.
if (NextUsesFixedFuncAttributes &&
attr >= AttributeConsts.UserAttributeBase &&
attr < AttributeConsts.UserAttributeEnd)
{
int index = (attr - AttributeConsts.UserAttributeBase) >> 4;
return (_nextUsedInputAttributes & (1 << index)) != 0;
}
return true;
}
public int GetFreeUserAttribute(bool isOutput, int index)
{
int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes;
int bit = -1;
while (useMask != -1)
{
bit = BitOperations.TrailingZeroCount(~useMask);
if (bit == 32)
{
bit = -1;
break;
}
else if (index < 1)
{
break;
}
useMask |= 1 << bit;
index--;
}
return bit;
}
public void SetAllInputUserAttributes()
{
UsedInputAttributes |= Constants.AllAttributesMask;
ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4);
}
public void SetAllOutputUserAttributes()
{
UsedOutputAttributes |= Constants.AllAttributesMask;
}
public void SetClipDistanceWritten(int index)
{
ClipDistancesWritten |= (byte)(1 << index);
}
public void SetUsedFeature(FeatureFlags flags)
{
UsedFeatures |= flags;
}
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
{
return new ShaderProgramInfo(
ResourceManager.GetConstantBufferDescriptors(),
ResourceManager.GetStorageBufferDescriptors(),
ResourceManager.GetTextureDescriptors(),
ResourceManager.GetImageDescriptors(),
identification,
GpLayerInputAttribute,
Stage,
UsedFeatures.HasFlag(FeatureFlags.FragCoordXY),
UsedFeatures.HasFlag(FeatureFlags.InstanceId),
UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
UsedFeatures.HasFlag(FeatureFlags.RtLayer),
ClipDistancesWritten,
OmapTargets);
}
}
}

View File

@ -0,0 +1,315 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using System.Numerics;
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderDefinitions
{
private readonly GpuGraphicsState _graphicsState;
public ShaderStage Stage { get; }
public int ComputeLocalSizeX { get; }
public int ComputeLocalSizeY { get; }
public int ComputeLocalSizeZ { get; }
public bool TessCw => _graphicsState.TessCw;
public TessPatchType TessPatchType => _graphicsState.TessPatchType;
public TessSpacing TessSpacing => _graphicsState.TessSpacing;
public bool AlphaToCoverageDitherEnable => _graphicsState.AlphaToCoverageEnable && _graphicsState.AlphaToCoverageDitherEnable;
public bool ViewportTransformDisable => _graphicsState.ViewportTransformDisable;
public bool DepthMode => _graphicsState.DepthMode;
public float PointSize => _graphicsState.PointSize;
public AlphaTestOp AlphaTestCompare => _graphicsState.AlphaTestCompare;
public float AlphaTestReference => _graphicsState.AlphaTestReference;
public bool GpPassthrough { get; }
public bool LastInVertexPipeline { get; set; }
public int ThreadsPerInputPrimitive { get; }
public InputTopology InputTopology => _graphicsState.Topology;
public OutputTopology OutputTopology { get; }
public int MaxOutputVertices { get; }
public bool DualSourceBlend => _graphicsState.DualSourceBlendEnable;
public bool EarlyZForce => _graphicsState.EarlyZForce;
public bool YNegateEnabled => _graphicsState.YNegateEnabled;
public bool OriginUpperLeft => _graphicsState.OriginUpperLeft;
public ImapPixelType[] ImapTypes { get; }
public bool IaIndexing { get; private set; }
public bool OaIndexing { get; private set; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public bool TransformFeedbackEnabled { get; }
private readonly TransformFeedbackOutput[] _transformFeedbackOutputs;
readonly struct TransformFeedbackVariable : IEquatable<TransformFeedbackVariable>
{
public IoVariable IoVariable { get; }
public int Location { get; }
public int Component { get; }
public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0)
{
IoVariable = ioVariable;
Location = location;
Component = component;
}
public override bool Equals(object other)
{
return other is TransformFeedbackVariable tfbVar && Equals(tfbVar);
}
public bool Equals(TransformFeedbackVariable other)
{
return IoVariable == other.IoVariable &&
Location == other.Location &&
Component == other.Component;
}
public override int GetHashCode()
{
return (int)IoVariable | (Location << 8) | (Component << 16);
}
public override string ToString()
{
return $"{IoVariable}.{Location}.{Component}";
}
}
private readonly Dictionary<TransformFeedbackVariable, TransformFeedbackOutput> _transformFeedbackDefinitions;
public ShaderDefinitions(ShaderStage stage)
{
Stage = stage;
}
public ShaderDefinitions(
ShaderStage stage,
int computeLocalSizeX,
int computeLocalSizeY,
int computeLocalSizeZ)
{
Stage = stage;
ComputeLocalSizeX = computeLocalSizeX;
ComputeLocalSizeY = computeLocalSizeY;
ComputeLocalSizeZ = computeLocalSizeZ;
}
public ShaderDefinitions(
ShaderStage stage,
GpuGraphicsState graphicsState,
bool gpPassthrough,
int threadsPerInputPrimitive,
OutputTopology outputTopology,
int maxOutputVertices)
{
Stage = stage;
_graphicsState = graphicsState;
GpPassthrough = gpPassthrough;
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
}
public ShaderDefinitions(
ShaderStage stage,
GpuGraphicsState graphicsState,
bool gpPassthrough,
int threadsPerInputPrimitive,
OutputTopology outputTopology,
int maxOutputVertices,
ImapPixelType[] imapTypes,
int omapTargets,
bool omapSampleMask,
bool omapDepth,
bool transformFeedbackEnabled,
ulong transformFeedbackVecMap,
TransformFeedbackOutput[] transformFeedbackOutputs)
{
Stage = stage;
_graphicsState = graphicsState;
GpPassthrough = gpPassthrough;
ThreadsPerInputPrimitive = threadsPerInputPrimitive;
OutputTopology = outputTopology;
MaxOutputVertices = maxOutputVertices;
ImapTypes = imapTypes;
OmapTargets = omapTargets;
OmapSampleMask = omapSampleMask;
OmapDepth = omapDepth;
LastInVertexPipeline = stage < ShaderStage.Fragment;
TransformFeedbackEnabled = transformFeedbackEnabled;
_transformFeedbackOutputs = transformFeedbackOutputs;
_transformFeedbackDefinitions = new();
while (transformFeedbackVecMap != 0)
{
int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap);
for (int subIndex = 0; subIndex < 4; subIndex++)
{
int wordOffset = vecIndex * 4 + subIndex;
int byteOffset = wordOffset * 4;
if (transformFeedbackOutputs[wordOffset].Valid)
{
IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location);
int component = 0;
if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true))
{
component = subIndex;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
_transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]);
}
}
transformFeedbackVecMap &= ~(1UL << vecIndex);
}
}
public void EnableInputIndexing()
{
IaIndexing = true;
}
public void EnableOutputIndexing()
{
OaIndexing = true;
}
public TransformFeedbackOutput[] GetTransformFeedbackOutputs()
{
if (!HasTransformFeedbackOutputs())
{
return null;
}
return _transformFeedbackOutputs;
}
public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput)
{
if (!HasTransformFeedbackOutputs())
{
transformFeedbackOutput = default;
return false;
}
var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component);
return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput);
}
private bool HasTransformFeedbackOutputs()
{
return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment);
}
public bool HasTransformFeedbackOutputs(bool isOutput)
{
return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment));
}
public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput)
{
if (ioVariable == IoVariable.UserDefined)
{
return (!isOutput && !IaIndexing) || (isOutput && !OaIndexing);
}
return ioVariable == IoVariable.FragmentOutputColor;
}
public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput)
{
if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput))
{
return false;
}
return GetTransformFeedbackOutputComponents(location, component) == 1;
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset)
{
return _transformFeedbackOutputs[wordOffset];
}
public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component)
{
return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component);
}
public int GetTransformFeedbackOutputComponents(int location, int component)
{
int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4;
int index = baseIndex + component;
int count = 1;
for (; count < 4; count++)
{
ref var prev = ref _transformFeedbackOutputs[baseIndex + count - 1];
ref var curr = ref _transformFeedbackOutputs[baseIndex + count];
int prevOffset = prev.Offset;
int currOffset = curr.Offset;
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
{
break;
}
}
if (baseIndex + count <= index)
{
return 1;
}
return count;
}
public AggregateType GetFragmentOutputColorType(int location)
{
return AggregateType.Vector4 | _graphicsState.FragmentOutputTypes[location].ToAggregateType();
}
public AggregateType GetUserDefinedType(int location, bool isOutput)
{
if ((!isOutput && IaIndexing) || (isOutput && OaIndexing))
{
return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32;
}
AggregateType type = AggregateType.Vector4;
if (Stage == ShaderStage.Vertex && !isOutput)
{
type |= _graphicsState.AttributeTypes[location].ToAggregateType();
}
else
{
type |= AggregateType.FP32;
}
return type;
}
}
}

View File

@ -5,18 +5,22 @@ namespace Ryujinx.Graphics.Shader.Translation
{
static class ShaderIdentifier
{
public static ShaderIdentification Identify(IReadOnlyList<Function> functions, ShaderConfig config)
public static ShaderIdentification Identify(
IReadOnlyList<Function> functions,
IGpuAccessor gpuAccessor,
ShaderStage stage,
InputTopology inputTopology,
out int layerInputAttr)
{
if (config.Stage == ShaderStage.Geometry &&
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
!config.GpuAccessor.QueryHostSupportsGeometryShader() &&
IsLayerPassthroughGeometryShader(functions, out int layerInputAttr))
if (stage == ShaderStage.Geometry &&
inputTopology == InputTopology.Triangles &&
!gpuAccessor.QueryHostSupportsGeometryShader() &&
IsLayerPassthroughGeometryShader(functions, out layerInputAttr))
{
config.SetGeometryShaderLayerInputAttribute(layerInputAttr);
return ShaderIdentification.GeometryLayerPassthrough;
}
layerInputAttr = 0;
return ShaderIdentification.None;
}

View File

@ -0,0 +1,33 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
namespace Ryujinx.Graphics.Shader.Translation
{
readonly ref struct TransformContext
{
public readonly HelperFunctionManager Hfm;
public readonly BasicBlock[] Blocks;
public readonly ResourceManager ResourceManager;
public readonly IGpuAccessor GpuAccessor;
public readonly TargetLanguage TargetLanguage;
public readonly ShaderStage Stage;
public readonly ref FeatureFlags UsedFeatures;
public TransformContext(
HelperFunctionManager hfm,
BasicBlock[] blocks,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
TargetLanguage targetLanguage,
ShaderStage stage,
ref FeatureFlags usedFeatures)
{
Hfm = hfm;
Blocks = blocks;
ResourceManager = resourceManager;
GpuAccessor = gpuAccessor;
TargetLanguage = targetLanguage;
Stage = stage;
UsedFeatures = ref usedFeatures;
}
}
}

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.Shader.Translation
{
readonly struct TransformFeedbackOutput
{
public readonly bool Valid;
public readonly int Buffer;
public readonly int Offset;
public readonly int Stride;
public TransformFeedbackOutput(int buffer, int offset, int stride)
{
Valid = true;
Buffer = buffer;
Offset = offset;
Stride = stride;
}
}
}

View File

@ -0,0 +1,93 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class DrawParametersReplace : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Vertex;
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
if (context.GpuAccessor.QueryHasConstantBufferDrawParameters())
{
if (ReplaceConstantBufferWithDrawParameters(node, operation))
{
context.UsedFeatures |= FeatureFlags.DrawParameters;
}
}
else if (HasConstantBufferDrawParameters(operation))
{
context.UsedFeatures |= FeatureFlags.DrawParameters;
}
return node;
}
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
{
Operand GenerateLoad(IoVariable ioVariable)
{
Operand value = Local();
node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, value, Const((int)ioVariable)));
return value;
}
bool modified = false;
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex));
modified = true;
break;
case Constants.NvnBaseInstanceByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance));
modified = true;
break;
case Constants.NvnDrawIndexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex));
modified = true;
break;
}
}
}
return modified;
}
private static bool HasConstantBufferDrawParameters(Operation operation)
{
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
case Constants.NvnBaseInstanceByteOffset / 4:
case Constants.NvnDrawIndexByteOffset / 4:
return true;
}
}
}
return false;
}
}
}

View File

@ -0,0 +1,36 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class ForcePreciseEnable : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Fragment && gpuAccessor.QueryHostReducedPrecision();
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
// There are some cases where a small bias is added to values to prevent division by zero.
// When operating with reduced precision, it is possible for this bias to get rounded to 0
// and cause a division by zero.
// To prevent that, we force those operations to be precise even if the host wants
// imprecise operations for performance.
Operation operation = (Operation)node.Value;
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
operation.GetSource(0).Type == OperandType.Constant &&
operation.GetSource(0).AsFloat() == 1f &&
operation.GetSource(1).AsgOp is Operation addOp &&
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
addOp.GetSource(1).Type == OperandType.Constant)
{
addOp.ForcePrecise = true;
}
return node;
}
}
}

View File

@ -0,0 +1,11 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
interface ITransformPass
{
abstract static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures);
abstract static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node);
}
}

View File

@ -0,0 +1,58 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class SharedAtomicSignedCas : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return targetLanguage != TargetLanguage.Spirv && stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory);
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.Inst == Instruction.AtomicMaxS32)
{
name = HelperFunctionName.SharedAtomicMaxS32;
}
else if (operation.Inst == Instruction.AtomicMinS32)
{
name = HelperFunctionName.SharedAtomicMinS32;
}
else
{
return node;
}
if (operation.StorageKind != StorageKind.SharedMemory)
{
return node;
}
Operand result = operation.Dest;
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
}
}

View File

@ -0,0 +1,57 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class SharedStoreSmallIntCas : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory);
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.StorageKind == StorageKind.SharedMemory8)
{
name = HelperFunctionName.SharedStore8;
}
else if (operation.StorageKind == StorageKind.SharedMemory16)
{
name = HelperFunctionName.SharedStore16;
}
else
{
return node;
}
if (operation.Inst != Instruction.Store)
{
return node;
}
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
}
}

View File

@ -1,268 +1,45 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
static class Rewriter
class TexturePass : ITransformPass
{
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
bool isVertexShader = config.Stage == ShaderStage.Vertex;
bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
return true;
}
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
if (node.Value is TextureOperation texOp)
{
BasicBlock block = blocks[blkIndex];
node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage);
node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage);
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
if (texOp.Inst == Instruction.TextureSample)
{
if (node.Value is not Operation operation)
node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor);
if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
{
continue;
}
if (isVertexShader)
{
if (hasConstantBufferDrawParameters)
{
if (ReplaceConstantBufferWithDrawParameters(node, operation))
{
config.SetUsedFeature(FeatureFlags.DrawParameters);
}
}
else if (HasConstantBufferDrawParameters(operation))
{
config.SetUsedFeature(FeatureFlags.DrawParameters);
}
}
if (isImpreciseFragmentShader)
{
EnableForcePreciseIfNeeded(operation);
}
if (hasVectorIndexingBug)
{
InsertVectorComponentSelect(node, config);
}
if (operation is TextureOperation texOp)
{
node = InsertTexelFetchScale(hfm, node, config);
node = InsertTextureSizeUnscale(hfm, node, config);
if (texOp.Inst == Instruction.TextureSample)
{
node = InsertCoordNormalization(hfm, node, config);
node = InsertCoordGatherBias(node, config);
node = InsertConstOffsets(node, config);
if (texOp.Type == SamplerType.TextureBuffer && !supportsSnormBufferTextureFormat)
{
node = InsertSnormNormalization(node, config);
}
}
}
else
{
node = InsertSharedStoreSmallInt(hfm, node);
if (config.Options.TargetLanguage != TargetLanguage.Spirv)
{
node = InsertSharedAtomicSigned(hfm, node);
}
node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
}
}
}
return node;
}
private static void EnableForcePreciseIfNeeded(Operation operation)
{
// There are some cases where a small bias is added to values to prevent division by zero.
// When operating with reduced precision, it is possible for this bias to get rounded to 0
// and cause a division by zero.
// To prevent that, we force those operations to be precise even if the host wants
// imprecise operations for performance.
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
operation.GetSource(0).Type == OperandType.Constant &&
operation.GetSource(0).AsFloat() == 1f &&
operation.GetSource(1).AsgOp is Operation addOp &&
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
addOp.GetSource(1).Type == OperandType.Constant)
{
addOp.ForcePrecise = true;
}
}
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
{
Operation operation = (Operation)node.Value;
if (operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.ConstantBuffer ||
operation.SourcesCount < 3)
{
return;
}
Operand bindingIndex = operation.GetSource(0);
Operand fieldIndex = operation.GetSource(1);
Operand elemIndex = operation.GetSource(operation.SourcesCount - 1);
if (bindingIndex.Type != OperandType.Constant ||
fieldIndex.Type != OperandType.Constant ||
elemIndex.Type == OperandType.Constant)
{
return;
}
BufferDefinition buffer = config.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1,
};
if (elemCount == 1)
{
return;
}
Operand result = null;
for (int i = 0; i < elemCount; i++)
{
Operand value = Local();
Operand[] inputs = new Operand[operation.SourcesCount];
for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++)
{
inputs[srcIndex] = operation.GetSource(srcIndex);
}
inputs[^1] = Const(i);
Operation loadOp = new(Instruction.Load, StorageKind.ConstantBuffer, value, inputs);
node.List.AddBefore(node, loadOp);
if (i == 0)
{
result = value;
}
else
{
Operand isCurrentIndex = Local();
Operand selection = Local();
Operation compareOp = new(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) });
Operation selectOp = new(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result });
node.List.AddBefore(node, compareOp);
node.List.AddBefore(node, selectOp);
result = selection;
}
}
operation.TurnIntoCopy(result);
}
private static LinkedListNode<INode> InsertSharedStoreSmallInt(HelperFunctionManager hfm, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.StorageKind == StorageKind.SharedMemory8)
{
name = HelperFunctionName.SharedStore8;
}
else if (operation.StorageKind == StorageKind.SharedMemory16)
{
name = HelperFunctionName.SharedStore16;
}
else
{
return node;
}
if (operation.Inst != Instruction.Store)
{
return node;
}
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
private static LinkedListNode<INode> InsertSharedAtomicSigned(HelperFunctionManager hfm, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
HelperFunctionName name;
if (operation.Inst == Instruction.AtomicMaxS32)
{
name = HelperFunctionName.SharedAtomicMaxS32;
}
else if (operation.Inst == Instruction.AtomicMinS32)
{
name = HelperFunctionName.SharedAtomicMinS32;
}
else
{
return node;
}
if (operation.StorageKind != StorageKind.SharedMemory)
{
return node;
}
Operand result = operation.Dest;
Operand memoryId = operation.GetSource(0);
Operand byteOffset = operation.GetSource(1);
Operand value = operation.GetSource(2);
Debug.Assert(memoryId.Type == OperandType.Constant);
int functionId = hfm.GetOrCreateFunctionId(name, memoryId.Value);
Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value };
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs));
Utils.DeleteNode(node, operation);
return newNode;
}
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertTexelFetchScale(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
ShaderStage stage)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -280,20 +57,20 @@ namespace Ryujinx.Graphics.Shader.Translation
(intCoords || isImage) &&
!isBindless &&
!isIndexed &&
config.Stage.SupportsRenderScale() &&
stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
int samplerIndex = isImage
? config.ResourceManager.GetTextureDescriptors().Length + config.ResourceManager.FindImageDescriptorIndex(texOp.Binding)
: config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
? resourceManager.GetTextureDescriptors().Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
: resourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = 0; index < coordsCount; index++)
{
Operand scaledCoord = Local();
Operand[] callArgs;
if (config.Stage == ShaderStage.Fragment)
if (stage == ShaderStage.Fragment)
{
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
}
@ -311,7 +88,11 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static LinkedListNode<INode> InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertTextureSizeUnscale(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
ShaderStage stage)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -322,11 +103,11 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.Index < 2 &&
!isBindless &&
!isIndexed &&
config.Stage.SupportsRenderScale() &&
stage.SupportsRenderScale() &&
TypeSupportsScale(texOp.Type))
{
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
int samplerIndex = config.ResourceManager.FindTextureDescriptorIndex(texOp.Binding);
int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding);
for (int index = texOp.DestsCount - 1; index >= 0; index--)
{
@ -356,19 +137,12 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static bool IsImageInstructionWithScale(Instruction inst)
{
// Currently, we don't support scaling images that are modified,
// so we only need to care about the load instruction.
return inst == Instruction.ImageLoad;
}
private static bool TypeSupportsScale(SamplerType type)
{
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
private static LinkedListNode<INode> InsertCoordNormalization(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertCoordNormalization(
HelperFunctionManager hfm,
LinkedListNode<INode> node,
ResourceManager resourceManager,
IGpuAccessor gpuAccessor,
ShaderStage stage)
{
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
@ -386,9 +160,9 @@ namespace Ryujinx.Graphics.Shader.Translation
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
bool isCoordNormalized = config.GpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
if (isCoordNormalized || intCoords)
{
@ -400,8 +174,6 @@ namespace Ryujinx.Graphics.Shader.Translation
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
config.SetUsedFeature(FeatureFlags.IntegerSampling);
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
for (int index = 0; index < normCoordsCount; index++)
@ -429,7 +201,7 @@ namespace Ryujinx.Graphics.Shader.Translation
new[] { coordSize },
texSizeSources));
config.ResourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
Operand source = texOp.GetSource(coordsIndex + index);
@ -439,13 +211,13 @@ namespace Ryujinx.Graphics.Shader.Translation
texOp.SetSource(coordsIndex + index, coordNormalized);
InsertTextureSizeUnscale(hfm, textureSizeNode, config);
InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
}
return node;
}
private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// The gather behavior when the coordinate sits right in the middle of two texels is not well defined.
// To ensure the correct texel is sampled, we add a small bias value to the coordinate.
@ -457,25 +229,18 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
int gatherBiasPrecision = config.GpuAccessor.QueryHostGatherBiasPrecision();
int gatherBiasPrecision = gpuAccessor.QueryHostGatherBiasPrecision();
if (!isGather || gatherBiasPrecision == 0)
{
return node;
}
#pragma warning disable IDE0059 // Remove unnecessary value assignment
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
bool isArray = (texOp.Type & SamplerType.Array) != 0;
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
#pragma warning restore IDE0059
int coordsCount = texOp.Type.GetDimensions();
int coordsIndex = isBindless || isIndexed ? 1 : 0;
config.SetUsedFeature(FeatureFlags.IntegerSampling);
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
for (int index = 0; index < normCoordsCount; index++)
@ -524,7 +289,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
// Non-constant texture offsets are not allowed (according to the spec),
// however some GPUs does support that.
@ -540,7 +305,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool hasInvalidOffset = (hasOffset || hasOffsets) && !config.GpuAccessor.QueryHostSupportsNonConstantTextureOffset();
bool hasInvalidOffset = (hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset();
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
@ -673,8 +438,6 @@ namespace Ryujinx.Graphics.Shader.Translation
if (isGather && !isShadow)
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand[] newSources = new Operand[sources.Length];
sources.CopyTo(newSources, 0);
@ -741,8 +504,6 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else
{
config.SetUsedFeature(FeatureFlags.IntegerSampling);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
for (int index = 0; index < coordsCount; index++)
@ -840,7 +601,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return texSizes;
}
private static LinkedListNode<INode> InsertSnormNormalization(LinkedListNode<INode> node, ShaderConfig config)
private static LinkedListNode<INode> InsertSnormNormalization(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
{
TextureOperation texOp = (TextureOperation)node.Value;
@ -851,9 +612,9 @@ namespace Ryujinx.Graphics.Shader.Translation
return node;
}
(int cbufSlot, int handle) = config.ResourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
(int cbufSlot, int handle) = resourceManager.GetCbufSlotAndHandleForTexture(texOp.Binding);
TextureFormat format = config.GpuAccessor.QueryTextureFormat(handle, cbufSlot);
TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
int maxPositive = format switch
{
@ -926,63 +687,16 @@ namespace Ryujinx.Graphics.Shader.Translation
return res;
}
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
private static bool IsImageInstructionWithScale(Instruction inst)
{
Operand GenerateLoad(IoVariable ioVariable)
{
Operand value = Local();
node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, value, Const((int)ioVariable)));
return value;
}
bool modified = false;
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex));
modified = true;
break;
case Constants.NvnBaseInstanceByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance));
modified = true;
break;
case Constants.NvnDrawIndexByteOffset / 4:
operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex));
modified = true;
break;
}
}
}
return modified;
// Currently, we don't support scaling images that are modified,
// so we only need to care about the load instruction.
return inst == Instruction.ImageLoad;
}
private static bool HasConstantBufferDrawParameters(Operation operation)
private static bool TypeSupportsScale(SamplerType type)
{
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
{
Operand src = operation.GetSource(srcIndex);
if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0)
{
switch (src.GetCbufOffset())
{
case Constants.NvnBaseVertexByteOffset / 4:
case Constants.NvnBaseInstanceByteOffset / 4:
case Constants.NvnDrawIndexByteOffset / 4:
return true;
}
}
}
return false;
return (type & SamplerType.Mask) == SamplerType.Texture2D;
}
}
}

View File

@ -0,0 +1,41 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
static class TransformPasses
{
public static void RunPass(TransformContext context)
{
RunPass<DrawParametersReplace>(context);
RunPass<ForcePreciseEnable>(context);
RunPass<VectorComponentSelect>(context);
RunPass<TexturePass>(context);
RunPass<SharedStoreSmallIntCas>(context);
RunPass<SharedAtomicSignedCas>(context);
}
private static void RunPass<T>(TransformContext context) where T : ITransformPass
{
if (!T.IsEnabled(context.GpuAccessor, context.Stage, context.TargetLanguage, context.UsedFeatures))
{
return;
}
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
BasicBlock block = context.Blocks[blkIndex];
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (node.Value is not Operation)
{
continue;
}
node = T.RunPass(context, node);
}
}
}
}
}

View File

@ -0,0 +1,96 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
class VectorComponentSelect : ITransformPass
{
public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
{
return gpuAccessor.QueryHostHasVectorIndexingBug();
}
public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
{
Operation operation = (Operation)node.Value;
if (operation.Inst != Instruction.Load ||
operation.StorageKind != StorageKind.ConstantBuffer ||
operation.SourcesCount < 3)
{
return node;
}
Operand bindingIndex = operation.GetSource(0);
Operand fieldIndex = operation.GetSource(1);
Operand elemIndex = operation.GetSource(operation.SourcesCount - 1);
if (bindingIndex.Type != OperandType.Constant ||
fieldIndex.Type != OperandType.Constant ||
elemIndex.Type == OperandType.Constant)
{
return node;
}
BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[bindingIndex.Value];
StructureField field = buffer.Type.Fields[fieldIndex.Value];
int elemCount = (field.Type & AggregateType.ElementCountMask) switch
{
AggregateType.Vector2 => 2,
AggregateType.Vector3 => 3,
AggregateType.Vector4 => 4,
_ => 1
};
if (elemCount == 1)
{
return node;
}
Operand result = null;
for (int i = 0; i < elemCount; i++)
{
Operand value = Local();
Operand[] inputs = new Operand[operation.SourcesCount];
for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++)
{
inputs[srcIndex] = operation.GetSource(srcIndex);
}
inputs[^1] = Const(i);
Operation loadOp = new(Instruction.Load, StorageKind.ConstantBuffer, value, inputs);
node.List.AddBefore(node, loadOp);
if (i == 0)
{
result = value;
}
else
{
Operand isCurrentIndex = Local();
Operand selection = Local();
Operation compareOp = new(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) });
Operation selectOp = new(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result });
node.List.AddBefore(node, compareOp);
node.List.AddBefore(node, selectOp);
result = selection;
}
}
operation.TurnIntoCopy(result);
return node;
}
}
}

View File

@ -1,11 +1,6 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using System;
using System.Collections.Generic;
using System.Linq;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@ -13,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
public static class Translator
{
private const int ThreadsPerWarp = 32;
private const int HeaderSize = 0x50;
internal readonly struct FunctionCode
@ -30,94 +26,31 @@ namespace Ryujinx.Graphics.Shader.Translation
return DecodeShader(address, gpuAccessor, options);
}
internal static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
for (int i = 0; i < functions.Length; i++)
{
cfgs[i] = ControlFlowGraph.Create(functions[i].Code);
if (i != 0)
{
frus[i] = RegisterUsage.RunPass(cfgs[i]);
}
}
List<Function> funcs = new(functions.Length);
for (int i = 0; i < functions.Length; i++)
{
funcs.Add(null);
}
HelperFunctionManager hfm = new(funcs, config.Stage);
for (int i = 0; i < functions.Length; i++)
{
var cfg = cfgs[i];
int inArgumentsCount = 0;
int outArgumentsCount = 0;
if (i != 0)
{
var fru = frus[i];
inArgumentsCount = fru.InArguments.Length;
outArgumentsCount = fru.OutArguments.Length;
}
if (cfg.Blocks.Length != 0)
{
RegisterUsage.FixupCalls(cfg.Blocks, frus);
Dominance.FindDominators(cfg);
Dominance.FindDominanceFrontiers(cfg.Blocks);
Ssa.Rename(cfg.Blocks);
Optimizer.RunPass(hfm, cfg.Blocks, config);
Rewriter.RunPass(hfm, cfg.Blocks, config);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
}
var identification = ShaderIdentifier.Identify(funcs, config);
var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
var info = config.CreateProgramInfo(identification);
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString()),
};
}
private static TranslatorContext DecodeShader(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options)
{
ShaderConfig config;
int localMemorySize;
ShaderDefinitions definitions;
DecodedProgram program;
ulong maxEndAddress = 0;
if (options.Flags.HasFlag(TranslationFlags.Compute))
{
config = new ShaderConfig(ShaderStage.Compute, gpuAccessor, options, gpuAccessor.QueryComputeLocalMemorySize());
definitions = CreateComputeDefinitions(gpuAccessor);
localMemorySize = gpuAccessor.QueryComputeLocalMemorySize();
program = Decoder.Decode(config, address);
program = Decoder.Decode(definitions, gpuAccessor, address);
}
else
{
config = new ShaderConfig(new ShaderHeader(gpuAccessor, address), gpuAccessor, options);
ShaderHeader header = new(gpuAccessor, address);
program = Decoder.Decode(config, address + HeaderSize);
definitions = CreateGraphicsDefinitions(gpuAccessor, header);
localMemorySize = GetLocalMemorySize(header);
program = Decoder.Decode(definitions, gpuAccessor, address + HeaderSize);
}
ulong maxEndAddress = 0;
foreach (DecodedFunction function in program)
{
foreach (Block block in function.Blocks)
@ -129,12 +62,76 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
config.SizeAdd((int)maxEndAddress + (options.Flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize));
int size = (int)maxEndAddress + (options.Flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize);
return new TranslatorContext(address, program, config);
return new TranslatorContext(address, size, localMemorySize, definitions, gpuAccessor, options, program);
}
internal static FunctionCode[] EmitShader(DecodedProgram program, ShaderConfig config, bool initializeOutputs, out int initializationOperations)
private static ShaderDefinitions CreateComputeDefinitions(IGpuAccessor gpuAccessor)
{
return new ShaderDefinitions(
ShaderStage.Compute,
gpuAccessor.QueryComputeLocalSizeX(),
gpuAccessor.QueryComputeLocalSizeY(),
gpuAccessor.QueryComputeLocalSizeZ());
}
private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header)
{
bool transformFeedbackEnabled =
gpuAccessor.QueryTransformFeedbackEnabled() &&
gpuAccessor.QueryHostSupportsTransformFeedback();
TransformFeedbackOutput[] transformFeedbackOutputs = null;
ulong transformFeedbackVecMap = 0UL;
if (transformFeedbackEnabled)
{
transformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++)
{
var locations = gpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex);
var stride = gpuAccessor.QueryTransformFeedbackStride(tfbIndex);
for (int i = 0; i < locations.Length; i++)
{
byte wordOffset = locations[i];
if (wordOffset < 0xc0)
{
transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride);
transformFeedbackVecMap |= 1UL << (wordOffset / 4);
}
}
}
}
return new ShaderDefinitions(
header.Stage,
gpuAccessor.QueryGraphicsState(),
header.Stage == ShaderStage.Geometry && header.GpPassthrough,
header.ThreadsPerInputPrimitive,
header.OutputTopology,
header.MaxOutputVertexCount,
header.ImapTypes,
header.OmapTargets,
header.OmapSampleMask,
header.OmapDepth,
transformFeedbackEnabled,
transformFeedbackVecMap,
transformFeedbackOutputs);
}
private static int GetLocalMemorySize(ShaderHeader header)
{
return header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp);
}
internal static FunctionCode[] EmitShader(
TranslatorContext translatorContext,
ResourceManager resourceManager,
DecodedProgram program,
bool initializeOutputs,
out int initializationOperations)
{
initializationOperations = 0;
@ -149,11 +146,11 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int index = 0; index < functions.Length; index++)
{
EmitterContext context = new(program, config, index != 0);
EmitterContext context = new(translatorContext, resourceManager, program, index != 0);
if (initializeOutputs && index == 0)
{
EmitOutputsInitialization(context, config);
EmitOutputsInitialization(context, translatorContext.AttributeUsage, translatorContext.GpuAccessor, translatorContext.Stage);
initializationOperations = context.OperationsCount;
}
@ -168,27 +165,27 @@ namespace Ryujinx.Graphics.Shader.Translation
EmitOps(context, block);
}
functions[index] = new FunctionCode(context.GetOperations());
functions[index] = new(context.GetOperations());
}
return functions;
}
private static void EmitOutputsInitialization(EmitterContext context, ShaderConfig config)
private static void EmitOutputsInitialization(EmitterContext context, AttributeUsage attributeUsage, IGpuAccessor gpuAccessor, ShaderStage stage)
{
// Compute has no output attributes, and fragment is the last stage, so we
// don't need to initialize outputs on those stages.
if (config.Stage == ShaderStage.Compute || config.Stage == ShaderStage.Fragment)
if (stage == ShaderStage.Compute || stage == ShaderStage.Fragment)
{
return;
}
if (config.Stage == ShaderStage.Vertex)
if (stage == ShaderStage.Vertex)
{
InitializePositionOutput(context);
}
UInt128 usedAttributes = context.Config.NextInputAttributesComponents;
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
while (usedAttributes != UInt128.Zero)
{
int index = (int)UInt128.TrailingZeroCount(usedAttributes);
@ -197,7 +194,7 @@ namespace Ryujinx.Graphics.Shader.Translation
usedAttributes &= ~(UInt128.One << index);
// We don't need to initialize passthrough attributes.
if ((context.Config.PassthroughAttributes & (1 << vecIndex)) != 0)
if ((context.TranslatorContext.AttributeUsage.PassthroughAttributes & (1 << vecIndex)) != 0)
{
continue;
}
@ -205,30 +202,28 @@ namespace Ryujinx.Graphics.Shader.Translation
InitializeOutputComponent(context, vecIndex, index & 3, perPatch: false);
}
if (context.Config.NextUsedInputAttributesPerPatch != null)
if (context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch != null)
{
foreach (int vecIndex in context.Config.NextUsedInputAttributesPerPatch.Order())
foreach (int vecIndex in context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch.Order())
{
InitializeOutput(context, vecIndex, perPatch: true);
}
}
if (config.NextUsesFixedFuncAttributes)
if (attributeUsage.NextUsesFixedFuncAttributes)
{
bool supportsLayerFromVertexOrTess = config.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
bool supportsLayerFromVertexOrTess = gpuAccessor.QueryHostSupportsLayerVertexTessellation();
int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
for (int i = fixedStartAttr; i < fixedStartAttr + 5 + AttributeConsts.TexCoordCount; i++)
{
int index = config.GetFreeUserAttribute(isOutput: true, i);
int index = attributeUsage.GetFreeUserAttribute(isOutput: true, i);
if (index < 0)
{
break;
}
InitializeOutput(context, index, perPatch: false);
config.SetOutputUserAttributeFixedFunc(index);
}
}
}
@ -253,11 +248,11 @@ namespace Ryujinx.Graphics.Shader.Translation
{
StorageKind storageKind = perPatch ? StorageKind.OutputPerPatch : StorageKind.Output;
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.OaIndexing))
if (context.TranslatorContext.Definitions.OaIndexing)
{
Operand invocationId = null;
if (context.Config.Stage == ShaderStage.TessellationControl && !perPatch)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch)
{
invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
}
@ -268,7 +263,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else
{
if (context.Config.Stage == ShaderStage.TessellationControl && !perPatch)
if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch)
{
Operand invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId);
context.Store(storageKind, IoVariable.UserDefined, Const(location), invocationId, Const(c), ConstF(c == 3 ? 1f : 0f));
@ -286,7 +281,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
InstOp op = block.OpCodes[opIndex];
if (context.Config.Options.Flags.HasFlag(TranslationFlags.DebugMode))
if (context.TranslatorContext.Options.Flags.HasFlag(TranslationFlags.DebugMode))
{
string instName;
@ -298,7 +293,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
instName = "???";
context.Config.GpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
context.TranslatorContext.GpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16}).");
}
string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}";

View File

@ -1,8 +1,11 @@
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using Ryujinx.Graphics.Shader.CodeGen.Spirv;
using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
using Ryujinx.Graphics.Shader.Translation.Transforms;
using System;
using System.Collections.Generic;
using System.Linq;
@ -15,22 +18,47 @@ namespace Ryujinx.Graphics.Shader.Translation
public class TranslatorContext
{
private readonly DecodedProgram _program;
private readonly ShaderConfig _config;
private readonly int _localMemorySize;
public ulong Address { get; }
public int Size { get; }
public int Cb1DataSize => _program.Cb1DataSize;
public ShaderStage Stage => _config.Stage;
public int Size => _config.Size;
public int Cb1DataSize => _config.Cb1DataSize;
public bool LayerOutputWritten => _config.LayerOutputWritten;
internal bool HasLayerInputAttribute { get; private set; }
internal int GpLayerInputAttribute { get; private set; }
public IGpuAccessor GpuAccessor => _config.GpuAccessor;
internal AttributeUsage AttributeUsage => _program.AttributeUsage;
internal TranslatorContext(ulong address, DecodedProgram program, ShaderConfig config)
internal ShaderDefinitions Definitions { get; }
public ShaderStage Stage => Definitions.Stage;
internal IGpuAccessor GpuAccessor { get; }
internal TranslationOptions Options { get; }
internal FeatureFlags UsedFeatures { get; private set; }
public bool LayerOutputWritten { get; private set; }
public int LayerOutputAttribute { get; private set; }
internal TranslatorContext(
ulong address,
int size,
int localMemorySize,
ShaderDefinitions definitions,
IGpuAccessor gpuAccessor,
TranslationOptions options,
DecodedProgram program)
{
Address = address;
Size = size;
_program = program;
_config = config;
_localMemorySize = localMemorySize;
Definitions = definitions;
GpuAccessor = gpuAccessor;
Options = options;
UsedFeatures = program.UsedFeatures;
}
private static bool IsLoadUserDefined(Operation operation)
@ -131,63 +159,259 @@ namespace Ryujinx.Graphics.Shader.Translation
return output;
}
public void SetNextStage(TranslatorContext nextStage)
internal int GetDepthRegister()
{
_config.MergeFromtNextStage(nextStage._config);
// The depth register is always two registers after the last color output.
return BitOperations.PopCount((uint)Definitions.OmapTargets) + 1;
}
public void SetLayerOutputAttribute(int attr)
{
LayerOutputWritten = true;
LayerOutputAttribute = attr;
}
public void SetGeometryShaderLayerInputAttribute(int attr)
{
_config.SetGeometryShaderLayerInputAttribute(attr);
UsedFeatures |= FeatureFlags.RtLayer;
HasLayerInputAttribute = true;
GpLayerInputAttribute = attr;
}
public void SetLastInVertexPipeline()
{
_config.SetLastInVertexPipeline();
Definitions.LastInVertexPipeline = true;
}
public ShaderProgram Translate(TranslatorContext other = null)
public void SetNextStage(TranslatorContext nextStage)
{
bool usesLocalMemory = _config.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
AttributeUsage.MergeFromtNextStage(
Definitions.GpPassthrough,
nextStage.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr),
nextStage.AttributeUsage);
_config.ResourceManager.SetCurrentLocalMemory(_config.LocalMemorySize, usesLocalMemory);
if (_config.Stage == ShaderStage.Compute)
// We don't consider geometry shaders using the geometry shader passthrough feature
// as being the last because when this feature is used, it can't actually modify any of the outputs,
// so the stage that comes before it is the last one that can do modifications.
if (nextStage.Definitions.Stage != ShaderStage.Fragment &&
(nextStage.Definitions.Stage != ShaderStage.Geometry || !nextStage.Definitions.GpPassthrough))
{
bool usesSharedMemory = _config.UsedFeatures.HasFlag(FeatureFlags.SharedMemory);
Definitions.LastInVertexPipeline = false;
}
}
_config.ResourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
public ShaderProgram Translate()
{
ResourceManager resourceManager = CreateResourceManager();
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
if (Stage == ShaderStage.Compute)
{
bool usesSharedMemory = _program.UsedFeatures.HasFlag(FeatureFlags.SharedMemory);
resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory);
}
FunctionCode[] code = EmitShader(_program, _config, initializeOutputs: other == null, out _);
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: true, out _);
if (other != null)
return Translate(code, resourceManager, UsedFeatures, _program.ClipDistancesWritten);
}
public ShaderProgram Translate(TranslatorContext other)
{
ResourceManager resourceManager = CreateResourceManager();
bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory);
FunctionCode[] code = EmitShader(this, resourceManager, _program, initializeOutputs: false, out _);
bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory);
resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory);
FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, initializeOutputs: true, out int aStart);
code = Combine(otherCode, code, aStart);
return Translate(
code,
resourceManager,
UsedFeatures | other.UsedFeatures,
(byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten));
}
private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
for (int i = 0; i < functions.Length; i++)
{
other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, Enumerable.Empty<int>());
cfgs[i] = ControlFlowGraph.Create(functions[i].Code);
// We need to share the resource manager since both shaders accesses the same constant buffers.
other._config.ResourceManager = _config.ResourceManager;
other._config.ResourceManager.SetCurrentLocalMemory(other._config.LocalMemorySize, other._config.UsedFeatures.HasFlag(FeatureFlags.LocalMemory));
FunctionCode[] otherCode = EmitShader(other._program, other._config, initializeOutputs: true, out int aStart);
code = Combine(otherCode, code, aStart);
_config.InheritFrom(other._config);
if (i != 0)
{
frus[i] = RegisterUsage.RunPass(cfgs[i]);
}
}
return Translator.Translate(code, _config);
List<Function> funcs = new(functions.Length);
for (int i = 0; i < functions.Length; i++)
{
funcs.Add(null);
}
HelperFunctionManager hfm = new(funcs, Definitions.Stage);
for (int i = 0; i < functions.Length; i++)
{
var cfg = cfgs[i];
int inArgumentsCount = 0;
int outArgumentsCount = 0;
if (i != 0)
{
var fru = frus[i];
inArgumentsCount = fru.InArguments.Length;
outArgumentsCount = fru.OutArguments.Length;
}
if (cfg.Blocks.Length != 0)
{
RegisterUsage.FixupCalls(cfg.Blocks, frus);
Dominance.FindDominators(cfg);
Dominance.FindDominanceFrontiers(cfg.Blocks);
Ssa.Rename(cfg.Blocks);
TransformContext context = new(
hfm,
cfg.Blocks,
resourceManager,
GpuAccessor,
Options.TargetLanguage,
Definitions.Stage,
ref usedFeatures);
Optimizer.RunPass(context);
TransformPasses.RunPass(context);
}
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
}
var identification = ShaderIdentifier.Identify(funcs, GpuAccessor, Definitions.Stage, Definitions.InputTopology, out int layerInputAttr);
return Generate(
funcs,
AttributeUsage,
Definitions,
resourceManager,
usedFeatures,
clipDistancesWritten,
identification,
layerInputAttr);
}
private ShaderProgram Generate(
IReadOnlyList<Function> funcs,
AttributeUsage attributeUsage,
ShaderDefinitions definitions,
ResourceManager resourceManager,
FeatureFlags usedFeatures,
byte clipDistancesWritten,
ShaderIdentification identification = ShaderIdentification.None,
int layerInputAttr = 0)
{
var sInfo = StructuredProgram.MakeStructuredProgram(
funcs,
attributeUsage,
definitions,
resourceManager,
Options.Flags.HasFlag(TranslationFlags.DebugMode));
var info = new ShaderProgramInfo(
resourceManager.GetConstantBufferDescriptors(),
resourceManager.GetStorageBufferDescriptors(),
resourceManager.GetTextureDescriptors(),
resourceManager.GetImageDescriptors(),
identification,
layerInputAttr,
definitions.Stage,
usedFeatures.HasFlag(FeatureFlags.FragCoordXY),
usedFeatures.HasFlag(FeatureFlags.InstanceId),
usedFeatures.HasFlag(FeatureFlags.DrawParameters),
usedFeatures.HasFlag(FeatureFlags.RtLayer),
clipDistancesWritten,
definitions.OmapTargets);
var hostCapabilities = new HostCapabilities(
GpuAccessor.QueryHostReducedPrecision(),
GpuAccessor.QueryHostSupportsFragmentShaderInterlock(),
GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel(),
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());
var parameters = new CodeGenParameters(attributeUsage, definitions, resourceManager.Properties, hostCapabilities, GpuAccessor, Options.TargetApi);
return Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, parameters)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, parameters)),
_ => throw new NotImplementedException(Options.TargetLanguage.ToString()),
};
}
private ResourceManager CreateResourceManager()
{
ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor);
if (!GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled())
{
StructureType tfeInfoStruct = new(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "base_offset", 4),
new StructureField(AggregateType.U32, "vertex_count")
});
BufferDefinition tfeInfoBuffer = new(BufferLayout.Std430, 1, Constants.TfeInfoBinding, "tfe_info", tfeInfoStruct);
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeInfoBuffer);
StructureType tfeDataStruct = new(new StructureField[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
});
for (int i = 0; i < Constants.TfeBuffersCount; i++)
{
int binding = Constants.TfeBufferBaseBinding + i;
BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct);
resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer);
}
}
return resourceManager;
}
public ShaderProgram GenerateGeometryPassthrough()
{
int outputAttributesMask = _config.UsedOutputAttributes;
int layerOutputAttr = _config.LayerOutputAttribute;
int outputAttributesMask = AttributeUsage.UsedOutputAttributes;
int layerOutputAttr = LayerOutputAttribute;
OutputTopology outputTopology;
int maxOutputVertices;
switch (GpuAccessor.QueryPrimitiveTopology())
switch (Definitions.InputTopology)
{
case InputTopology.Points:
outputTopology = OutputTopology.PointList;
@ -204,9 +428,10 @@ namespace Ryujinx.Graphics.Shader.Translation
break;
}
ShaderConfig config = new(ShaderStage.Geometry, outputTopology, maxOutputVertices, GpuAccessor, _config.Options);
var attributeUsage = new AttributeUsage(GpuAccessor);
var resourceManager = new ResourceManager(ShaderStage.Geometry, GpuAccessor);
EmitterContext context = new(default, config, false);
var context = new EmitterContext();
for (int v = 0; v < maxOutputVertices; v++)
{
@ -231,10 +456,7 @@ namespace Ryujinx.Graphics.Shader.Translation
else
{
context.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(attrIndex), Const(c), value);
config.SetOutputUserAttribute(attrIndex);
}
config.SetInputUserAttribute(attrIndex, c);
}
}
@ -254,16 +476,15 @@ namespace Ryujinx.Graphics.Shader.Translation
var cfg = ControlFlowGraph.Create(operations);
var function = new Function(cfg.Blocks, "main", false, 0, 0);
var sInfo = StructuredProgram.MakeStructuredProgram(new[] { function }, config);
var definitions = new ShaderDefinitions(
ShaderStage.Geometry,
GpuAccessor.QueryGraphicsState(),
false,
1,
outputTopology,
maxOutputVertices);
var info = config.CreateProgramInfo();
return config.Options.TargetLanguage switch
{
TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, config)),
TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, config)),
_ => throw new NotImplementedException(config.Options.TargetLanguage.ToString()),
};
return Generate(new[] { function }, attributeUsage, definitions, resourceManager, FeatureFlags.RtLayer, 0);
}
}
}

View File

@ -18,6 +18,12 @@ namespace Ryujinx.Graphics.Vulkan
void AddCommandBufferDependencies(CommandBufferScoped cbs);
}
interface IMirrorable<T> where T : IDisposable
{
Auto<T> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored);
void ClearMirrors(CommandBufferScoped cbs, int offset, int size);
}
class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
{
private int _referenceCount;
@ -26,6 +32,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BitMap _cbOwnership;
private readonly MultiFenceHolder _waitable;
private readonly IAutoPrivate[] _referencedObjs;
private readonly IMirrorable<T> _mirrorable;
private bool _disposed;
private bool _destroyed;
@ -37,6 +44,11 @@ namespace Ryujinx.Graphics.Vulkan
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
}
public Auto(T value, IMirrorable<T> mirrorable, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value, waitable, referencedObjs)
{
_mirrorable = mirrorable;
}
public Auto(T value, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value)
{
_waitable = waitable;
@ -48,9 +60,17 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public T Get(CommandBufferScoped cbs, int offset, int size)
public T GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
{
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size);
var mirror = _mirrorable.GetMirrorable(cbs, ref offset, size, out mirrored);
mirror._waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, false);
return mirror.Get(cbs);
}
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
{
_mirrorable?.ClearMirrors(cbs, offset, size);
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
return Get(cbs);
}

View File

@ -0,0 +1,263 @@
using Ryujinx.Common.Memory;
using System;
using System.Numerics;
namespace Ryujinx.Graphics.Vulkan
{
interface IBitMapListener
{
void BitMapSignal(int index, int count);
}
struct BitMapStruct<T> where T : IArray<long>
{
public const int IntSize = 64;
private const int IntShift = 6;
private const int IntMask = IntSize - 1;
private T _masks;
public BitMapStruct()
{
_masks = default;
}
public bool BecomesUnsetFrom(in BitMapStruct<T> from, ref BitMapStruct<T> into)
{
bool result = false;
int masks = _masks.Length;
for (int i = 0; i < masks; i++)
{
long fromMask = from._masks[i];
long unsetMask = (~fromMask) & (fromMask ^ _masks[i]);
into._masks[i] = unsetMask;
result |= unsetMask != 0;
}
return result;
}
public void SetAndSignalUnset<T2>(in BitMapStruct<T> from, ref T2 listener) where T2 : struct, IBitMapListener
{
BitMapStruct<T> result = new();
if (BecomesUnsetFrom(from, ref result))
{
// Iterate the set bits in the result, and signal them.
int offset = 0;
int masks = _masks.Length;
ref T resultMasks = ref result._masks;
for (int i = 0; i < masks; i++)
{
long value = resultMasks[i];
while (value != 0)
{
int bit = BitOperations.TrailingZeroCount((ulong)value);
listener.BitMapSignal(offset + bit, 1);
value &= ~(1L << bit);
}
offset += IntSize;
}
}
_masks = from._masks;
}
public void SignalSet(Action<int, int> action)
{
// Iterate the set bits in the result, and signal them.
int offset = 0;
int masks = _masks.Length;
for (int i = 0; i < masks; i++)
{
long value = _masks[i];
while (value != 0)
{
int bit = BitOperations.TrailingZeroCount((ulong)value);
action(offset + bit, 1);
value &= ~(1L << bit);
}
offset += IntSize;
}
}
public bool AnySet()
{
for (int i = 0; i < _masks.Length; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
return false;
}
public bool IsSet(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
return (_masks[wordIndex] & wordMask) != 0;
}
public bool IsSet(int start, int end)
{
if (start == end)
{
return IsSet(start);
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
return (_masks[startIndex] & startMask & endMask) != 0;
}
if ((_masks[startIndex] & startMask) != 0)
{
return true;
}
for (int i = startIndex + 1; i < endIndex; i++)
{
if (_masks[i] != 0)
{
return true;
}
}
if ((_masks[endIndex] & endMask) != 0)
{
return true;
}
return false;
}
public bool Set(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
if ((_masks[wordIndex] & wordMask) != 0)
{
return false;
}
_masks[wordIndex] |= wordMask;
return true;
}
public void Set(int bit, bool value)
{
if (value)
{
Set(bit);
}
else
{
Clear(bit);
}
}
public void SetRange(int start, int end)
{
if (start == end)
{
Set(start);
return;
}
int startIndex = start >> IntShift;
int startBit = start & IntMask;
long startMask = -1L << startBit;
int endIndex = end >> IntShift;
int endBit = end & IntMask;
long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
if (startIndex == endIndex)
{
_masks[startIndex] |= startMask & endMask;
}
else
{
_masks[startIndex] |= startMask;
for (int i = startIndex + 1; i < endIndex; i++)
{
_masks[i] |= -1L;
}
_masks[endIndex] |= endMask;
}
}
public BitMapStruct<T> Union(BitMapStruct<T> other)
{
var result = new BitMapStruct<T>();
ref var masks = ref _masks;
ref var otherMasks = ref other._masks;
ref var newMasks = ref result._masks;
for (int i = 0; i < masks.Length; i++)
{
newMasks[i] = masks[i] | otherMasks[i];
}
return result;
}
public void Clear(int bit)
{
int wordIndex = bit >> IntShift;
int wordBit = bit & IntMask;
long wordMask = 1L << wordBit;
_masks[wordIndex] &= ~wordMask;
}
public void Clear()
{
for (int i = 0; i < _masks.Length; i++)
{
_masks[i] = 0;
}
}
public void ClearInt(int start, int end)
{
for (int i = start; i <= end; i++)
{
_masks[i] = 0;
}
}
}
}

View File

@ -10,7 +10,7 @@ using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan
{
class BufferHolder : IDisposable
class BufferHolder : IDisposable, IMirrorable<DisposableBuffer>, IMirrorable<DisposableBufferView>
{
private const int MaxUpdateBufferSize = 0x10000;
@ -64,6 +64,11 @@ namespace Ryujinx.Graphics.Vulkan
private List<Action> _swapActions;
private byte[] _pendingData;
private BufferMirrorRangeList _pendingDataRanges;
private Dictionary<ulong, StagingBufferReserved> _mirrors;
private bool _useMirrors;
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
{
_gd = gd;
@ -71,7 +76,7 @@ namespace Ryujinx.Graphics.Vulkan
_allocation = allocation;
_allocationAuto = new Auto<MemoryAllocation>(allocation);
_waitable = new MultiFenceHolder(size);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
Size = size;
_map = allocation.HostPointer;
@ -81,6 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
DesiredType = currentType;
_flushLock = new ReaderWriterLock();
_useMirrors = gd.IsTBDR;
}
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto<MemoryAllocation> allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
@ -91,7 +97,7 @@ namespace Ryujinx.Graphics.Vulkan
_allocationAuto = allocation;
_allocationImported = true;
_waitable = new MultiFenceHolder(size);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
Size = size;
_map = _allocation.HostPointer + offset;
@ -110,7 +116,7 @@ namespace Ryujinx.Graphics.Vulkan
// Only swap if the buffer is not used in any queued command buffer.
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld && (_pendingData == null || cbs != null))
{
var currentAllocation = _allocationAuto;
var currentBuffer = _buffer;
@ -120,6 +126,11 @@ namespace Ryujinx.Graphics.Vulkan
if (buffer.Handle != 0)
{
if (cbs != null)
{
ClearMirrors(cbs.Value, 0, Size);
}
_flushLock.AcquireWriterLock(Timeout.Infinite);
ClearFlushFence();
@ -128,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
_allocation = allocation;
_allocationAuto = new Auto<MemoryAllocation>(allocation);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), this, _waitable, _allocationAuto);
_bufferHandle = buffer.Handle;
_map = allocation.HostPointer;
@ -257,7 +268,7 @@ namespace Ryujinx.Graphics.Vulkan
(_swapActions ??= new List<Action>()).Add(invalidateView);
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
}
public void InheritMetrics(BufferHolder other)
@ -302,6 +313,82 @@ namespace Ryujinx.Graphics.Vulkan
}
}
private static ulong ToMirrorKey(int offset, int size)
{
return ((ulong)offset << 32) | (uint)size;
}
private static (int offset, int size) FromMirrorKey(ulong key)
{
return ((int)(key >> 32), (int)key);
}
private unsafe bool TryGetMirror(CommandBufferScoped cbs, ref int offset, int size, out Auto<DisposableBuffer> buffer)
{
size = Math.Min(size, Size - offset);
// Does this binding need to be mirrored?
if (!_pendingDataRanges.OverlapsWith(offset, size))
{
buffer = null;
return false;
}
var key = ToMirrorKey(offset, size);
if (_mirrors.TryGetValue(key, out StagingBufferReserved reserved))
{
buffer = reserved.Buffer.GetBuffer();
offset = reserved.Offset;
return true;
}
// Is this mirror allowed to exist? Can't be used for write in any in-flight write.
if (_waitable.IsBufferRangeInUse(offset, size, true))
{
// Some of the data is not mirrorable, so upload the whole range.
ClearMirrors(cbs, offset, size);
buffer = null;
return false;
}
// Build data for the new mirror.
var baseData = new Span<byte>((void*)(_map + offset), size);
var modData = _pendingData.AsSpan(offset, size);
StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size, (int)_gd.Capabilities.MinResourceAlignment);
if (newMirror != null)
{
var mirror = newMirror.Value;
_pendingDataRanges.FillData(baseData, modData, offset, new Span<byte>((void*)(mirror.Buffer._map + mirror.Offset), size));
if (_mirrors.Count == 0)
{
_gd.PipelineInternal.RegisterActiveMirror(this);
}
_mirrors.Add(key, mirror);
buffer = mirror.Buffer.GetBuffer();
offset = mirror.Offset;
return true;
}
else
{
// Data could not be placed on the mirror, likely out of space. Force the data to flush.
ClearMirrors(cbs, offset, size);
buffer = null;
return false;
}
}
public Auto<DisposableBuffer> GetBuffer()
{
return _buffer;
@ -339,6 +426,86 @@ namespace Ryujinx.Graphics.Vulkan
return _buffer;
}
public Auto<DisposableBuffer> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
{
if (_pendingData != null && TryGetMirror(cbs, ref offset, size, out Auto<DisposableBuffer> result))
{
mirrored = true;
return result;
}
mirrored = false;
return _buffer;
}
Auto<DisposableBufferView> IMirrorable<DisposableBufferView>.GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
{
// Cannot mirror buffer views right now.
throw new NotImplementedException();
}
public void ClearMirrors()
{
// Clear mirrors without forcing a flush. This happens when the command buffer is switched,
// as all reserved areas on the staging buffer are released.
if (_pendingData != null)
{
_mirrors.Clear();
};
}
public void ClearMirrors(CommandBufferScoped cbs, int offset, int size)
{
// Clear mirrors in the given range, and submit overlapping pending data.
if (_pendingData != null)
{
bool hadMirrors = _mirrors.Count > 0 && RemoveOverlappingMirrors(offset, size);
if (_pendingDataRanges.Count() != 0)
{
UploadPendingData(cbs, offset, size);
}
if (hadMirrors)
{
_gd.PipelineInternal.Rebind(_buffer, offset, size);
}
};
}
public void UseMirrors()
{
_useMirrors = true;
}
private void UploadPendingData(CommandBufferScoped cbs, int offset, int size)
{
var ranges = _pendingDataRanges.FindOverlaps(offset, size);
if (ranges != null)
{
_pendingDataRanges.Remove(offset, size);
foreach (var range in ranges)
{
int rangeOffset = Math.Max(offset, range.Offset);
int rangeSize = Math.Min(offset + size, range.End) - rangeOffset;
if (_gd.PipelineInternal.CurrentCommandBuffer.CommandBuffer.Handle == cbs.CommandBuffer.Handle)
{
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPass, false);
}
else
{
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, null, false);
}
}
}
}
public void SignalWrite(int offset, int size)
{
ConsiderBackingSwap();
@ -472,7 +639,34 @@ namespace Ryujinx.Graphics.Vulkan
throw new InvalidOperationException("The buffer is not host mapped.");
}
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
public bool RemoveOverlappingMirrors(int offset, int size)
{
List<ulong> toRemove = null;
foreach (var key in _mirrors.Keys)
{
(int keyOffset, int keySize) = FromMirrorKey(key);
if (!(offset + size <= keyOffset || offset >= keyOffset + keySize))
{
toRemove ??= new List<ulong>();
toRemove.Add(key);
}
}
if (toRemove != null)
{
foreach (var key in toRemove)
{
_mirrors.Remove(key);
}
return true;
}
return false;
}
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true)
{
int dataSize = Math.Min(data.Length, Size - offset);
if (dataSize == 0)
@ -481,6 +675,7 @@ namespace Ryujinx.Graphics.Vulkan
}
_setCount++;
bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _currentType <= BufferAllocationType.HostMapped;
if (_map != IntPtr.Zero)
{
@ -488,7 +683,7 @@ namespace Ryujinx.Graphics.Vulkan
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
// If the buffer is rented, take a little more time and check if the use overlaps this handle.
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize);
bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false);
if (!needsFlush)
{
@ -496,12 +691,48 @@ namespace Ryujinx.Graphics.Vulkan
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
if (_pendingData != null)
{
bool removed = _pendingDataRanges.Remove(offset, dataSize);
if (RemoveOverlappingMirrors(offset, dataSize) || removed)
{
// If any mirrors were removed, rebind the buffer range.
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
}
}
SignalWrite(offset, dataSize);
return;
}
}
// If the buffer does not have an in-flight write (including an inline update), then upload data to a pendingCopy.
if (allowMirror && !_waitable.IsBufferRangeInUse(offset, dataSize, true))
{
if (_pendingData == null)
{
_pendingData = new byte[Size];
_mirrors = new Dictionary<ulong, StagingBufferReserved>();
}
data[..dataSize].CopyTo(_pendingData.AsSpan(offset, dataSize));
_pendingDataRanges.Add(offset, dataSize);
// Remove any overlapping mirrors.
RemoveOverlappingMirrors(offset, dataSize);
// Tell the graphics device to rebind any constant buffer that overlaps the newly modified range, as it should access a mirror.
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
return;
}
if (_pendingData != null)
{
_pendingDataRanges.Remove(offset, dataSize);
}
if (cbs != null &&
_gd.PipelineInternal.RenderPassActive &&
!(_buffer.HasCommandBufferDependency(cbs.Value) &&
@ -519,7 +750,37 @@ namespace Ryujinx.Graphics.Vulkan
data.Length > MaxUpdateBufferSize ||
!TryPushData(cbs.Value, endRenderPass, offset, data))
{
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
if (allowCbsWait)
{
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
}
else
{
bool rentCbs = cbs == null;
if (rentCbs)
{
cbs = _gd.CommandBufferPool.Rent();
}
if (!_gd.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data))
{
// Need to do a slow upload.
BufferHolder srcHolder = _gd.BufferManager.Create(_gd, dataSize, baseType: BufferAllocationType.HostMapped);
srcHolder.SetDataUnchecked(0, data);
var srcBuffer = srcHolder.GetBuffer();
var dstBuffer = this.GetBuffer(cbs.Value.CommandBuffer, true);
Copy(_gd, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
srcHolder.Dispose();
}
if (rentCbs)
{
cbs.Value.Dispose();
}
}
}
}
@ -558,7 +819,7 @@ namespace Ryujinx.Graphics.Vulkan
endRenderPass?.Invoke();
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value;
_writeCount--;
@ -608,7 +869,7 @@ namespace Ryujinx.Graphics.Vulkan
bool registerSrcUsage = true)
{
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
var dstBuffer = dst.Get(cbs, dstOffset, size, true).Value;
InsertBufferBarrier(
gd,

View File

@ -100,9 +100,10 @@ namespace Ryujinx.Graphics.Vulkan
VulkanRenderer gd,
int size,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default)
BufferHandle storageHint = default,
bool forceMirrors = false)
{
return CreateWithHandle(gd, size, out _, baseType, storageHint);
return CreateWithHandle(gd, size, out _, baseType, storageHint, forceMirrors);
}
public BufferHandle CreateWithHandle(
@ -110,7 +111,8 @@ namespace Ryujinx.Graphics.Vulkan
int size,
out BufferHolder holder,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
BufferHandle storageHint = default)
BufferHandle storageHint = default,
bool forceMirrors = false)
{
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
if (holder == null)
@ -118,6 +120,11 @@ namespace Ryujinx.Graphics.Vulkan
return BufferHandle.Null;
}
if (forceMirrors)
{
holder.UseMirrors();
}
BufferCount++;
ulong handle64 = (uint)_buffers.Add(holder);

View File

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
/// <summary>
/// A structure tracking pending upload ranges for buffers.
/// Where a range is present, pending data exists that can either be used to build mirrors
/// or upload directly to the buffer.
/// </summary>
struct BufferMirrorRangeList
{
internal readonly struct Range
{
public int Offset { get; }
public int Size { get; }
public int End => Offset + Size;
public Range(int offset, int size)
{
Offset = offset;
Size = size;
}
public bool OverlapsWith(int offset, int size)
{
return Offset < offset + size && offset < Offset + Size;
}
}
private List<Range> _ranges;
public readonly IEnumerable<Range> All()
{
return _ranges;
}
public readonly bool Remove(int offset, int size)
{
var list = _ranges;
bool removedAny = false;
if (list != null)
{
int overlapIndex = BinarySearch(list, offset, size);
if (overlapIndex >= 0)
{
// Overlaps with a range. Search back to find the first one it doesn't overlap with.
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
{
overlapIndex--;
}
int endOffset = offset + size;
int startIndex = overlapIndex;
var currentOverlap = list[overlapIndex];
// Orphan the start of the overlap.
if (currentOverlap.Offset < offset)
{
list[overlapIndex] = new Range(currentOverlap.Offset, offset - currentOverlap.Offset);
currentOverlap = new Range(offset, currentOverlap.End - offset);
list.Insert(++overlapIndex, currentOverlap);
startIndex++;
removedAny = true;
}
// Remove any middle overlaps.
while (currentOverlap.Offset < endOffset)
{
if (currentOverlap.End > endOffset)
{
// Update the end overlap instead of removing it, if it spans beyond the removed range.
list[overlapIndex] = new Range(endOffset, currentOverlap.End - endOffset);
removedAny = true;
break;
}
if (++overlapIndex >= list.Count)
{
break;
}
currentOverlap = list[overlapIndex];
}
int count = overlapIndex - startIndex;
list.RemoveRange(startIndex, count);
removedAny |= count > 0;
}
}
return removedAny;
}
public void Add(int offset, int size)
{
var list = _ranges;
if (list != null)
{
int overlapIndex = BinarySearch(list, offset, size);
if (overlapIndex >= 0)
{
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
{
overlapIndex--;
}
int endOffset = offset + size;
int startIndex = overlapIndex;
while (overlapIndex < list.Count && list[overlapIndex].OverlapsWith(offset, size))
{
var currentOverlap = list[overlapIndex];
var currentOverlapEndOffset = currentOverlap.Offset + currentOverlap.Size;
if (offset > currentOverlap.Offset)
{
offset = currentOverlap.Offset;
}
if (endOffset < currentOverlapEndOffset)
{
endOffset = currentOverlapEndOffset;
}
overlapIndex++;
size = endOffset - offset;
}
int count = overlapIndex - startIndex;
list.RemoveRange(startIndex, count);
overlapIndex = startIndex;
}
else
{
overlapIndex = ~overlapIndex;
}
list.Insert(overlapIndex, new Range(offset, size));
}
else
{
_ranges = new List<Range>
{
new Range(offset, size)
};
}
}
public readonly bool OverlapsWith(int offset, int size)
{
var list = _ranges;
if (list == null)
{
return false;
}
return BinarySearch(list, offset, size) >= 0;
}
public readonly List<Range> FindOverlaps(int offset, int size)
{
var list = _ranges;
if (list == null)
{
return null;
}
List<Range> result = null;
int index = BinarySearch(list, offset, size);
if (index >= 0)
{
while (index > 0 && list[index - 1].OverlapsWith(offset, size))
{
index--;
}
do
{
(result ??= new List<Range>()).Add(list[index++]);
}
while (index < list.Count && list[index].OverlapsWith(offset, size));
}
return result;
}
private static int BinarySearch(List<Range> list, int offset, int size)
{
int left = 0;
int right = list.Count - 1;
while (left <= right)
{
int range = right - left;
int middle = left + (range >> 1);
var item = list[middle];
if (item.OverlapsWith(offset, size))
{
return middle;
}
if (offset < item.Offset)
{
right = middle - 1;
}
else
{
left = middle + 1;
}
}
return ~left;
}
public readonly void FillData(Span<byte> baseData, Span<byte> modData, int offset, Span<byte> result)
{
int size = baseData.Length;
int endOffset = offset + size;
var list = _ranges;
if (list == null)
{
baseData.CopyTo(result);
}
int srcOffset = offset;
int dstOffset = 0;
bool activeRange = false;
for (int i = 0; i < list.Count; i++)
{
var range = list[i];
int rangeEnd = range.Offset + range.Size;
if (activeRange)
{
if (range.Offset >= endOffset)
{
break;
}
}
else
{
if (rangeEnd <= offset)
{
continue;
}
activeRange = true;
}
int baseSize = range.Offset - srcOffset;
if (baseSize > 0)
{
baseData.Slice(dstOffset, baseSize).CopyTo(result.Slice(dstOffset, baseSize));
srcOffset += baseSize;
dstOffset += baseSize;
}
int modSize = Math.Min(rangeEnd - srcOffset, endOffset - srcOffset);
if (modSize != 0)
{
modData.Slice(dstOffset, modSize).CopyTo(result.Slice(dstOffset, modSize));
srcOffset += modSize;
dstOffset += modSize;
}
}
int baseSizeEnd = endOffset - srcOffset;
if (baseSizeEnd > 0)
{
baseData.Slice(dstOffset, baseSizeEnd).CopyTo(result.Slice(dstOffset, baseSizeEnd));
}
}
public readonly int Count()
{
return _ranges?.Count ?? 0;
}
public void Clear()
{
_ranges = null;
}
}
}

View File

@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (_buffer != null)
{
var buffer = _buffer.Get(cbs, _offset, _size).Value;
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
}
@ -40,6 +40,11 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
{
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
}
public readonly void Dispose()
{
_buffer?.DecrementReferenceCount();

View File

@ -6,6 +6,7 @@
private readonly int _size;
private readonly int _granularity;
private readonly int _bits;
private readonly int _writeBitOffset;
private readonly int _intsPerCb;
private readonly int _bitsPerCb;
@ -14,7 +15,11 @@
{
_size = size;
_granularity = granularity;
_bits = (size + (granularity - 1)) / granularity;
// There are two sets of bits - one for read tracking, and the other for write.
int bits = (size + (granularity - 1)) / granularity;
_writeBitOffset = bits;
_bits = bits << 1;
_intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize;
_bitsPerCb = _intsPerCb * BitMap.IntSize;
@ -22,7 +27,7 @@
_bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
}
public void Add(int cbIndex, int offset, int size)
public void Add(int cbIndex, int offset, int size, bool write)
{
if (size == 0)
{
@ -35,32 +40,32 @@
size = _size - offset;
}
int cbBase = cbIndex * _bitsPerCb;
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
int start = cbBase + offset / _granularity;
int end = cbBase + (offset + size - 1) / _granularity;
_bitmap.SetRange(start, end);
}
public bool OverlapsWith(int cbIndex, int offset, int size)
public bool OverlapsWith(int cbIndex, int offset, int size, bool write = false)
{
if (size == 0)
{
return false;
}
int cbBase = cbIndex * _bitsPerCb;
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
int start = cbBase + offset / _granularity;
int end = cbBase + (offset + size - 1) / _granularity;
return _bitmap.IsSet(start, end);
}
public bool OverlapsWith(int offset, int size)
public bool OverlapsWith(int offset, int size, bool write)
{
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
{
if (OverlapsWith(i, offset, size))
if (OverlapsWith(i, offset, size, write))
{
return true;
}

View File

@ -1,9 +1,9 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Runtime.CompilerServices;
using Buffer = Silk.NET.Vulkan.Buffer;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
using Format = Ryujinx.Graphics.GAL.Format;
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
@ -12,13 +12,34 @@ namespace Ryujinx.Graphics.Vulkan
{
class DescriptorSetUpdater
{
private const ulong StorageBufferMaxMirrorable = 0x2000;
private record struct BufferRef
{
public Auto<DisposableBuffer> Buffer;
public int Offset;
public bool Write;
public BufferRef(Auto<DisposableBuffer> buffer)
{
Buffer = buffer;
Offset = 0;
Write = true;
}
public BufferRef(Auto<DisposableBuffer> buffer, ref BufferRange range)
{
Buffer = buffer;
Offset = range.Offset;
Write = range.Write;
}
}
private readonly VulkanRenderer _gd;
private readonly PipelineBase _pipeline;
private ShaderCollection _program;
private readonly Auto<DisposableBuffer>[] _uniformBufferRefs;
private readonly Auto<DisposableBuffer>[] _storageBufferRefs;
private readonly BufferRef[] _uniformBufferRefs;
private readonly BufferRef[] _storageBufferRefs;
private readonly Auto<DisposableImageView>[] _textureRefs;
private readonly Auto<DisposableSampler>[] _samplerRefs;
private readonly Auto<DisposableImageView>[] _imageRefs;
@ -33,8 +54,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BufferView[] _bufferTextures;
private readonly BufferView[] _bufferImages;
private readonly bool[] _uniformSet;
private readonly bool[] _storageSet;
private BitMapStruct<Array2<long>> _uniformSet;
private BitMapStruct<Array2<long>> _storageSet;
private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored;
[Flags]
private enum DirtyFlags
@ -61,8 +84,8 @@ namespace Ryujinx.Graphics.Vulkan
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and
// regular textures/images interleaved on the same descriptor set.
_uniformBufferRefs = new Auto<DisposableBuffer>[Constants.MaxUniformBufferBindings];
_storageBufferRefs = new Auto<DisposableBuffer>[Constants.MaxStorageBufferBindings];
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
@ -85,9 +108,6 @@ namespace Ryujinx.Graphics.Vulkan
_textures.AsSpan().Fill(initialImageInfo);
_images.AsSpan().Fill(initialImageInfo);
_uniformSet = new bool[Constants.MaxUniformBufferBindings];
_storageSet = new bool[Constants.MaxStorageBufferBindings];
if (gd.Capabilities.SupportsNullDescriptors)
{
// If null descriptors are supported, we can pass null as the handle.
@ -138,6 +158,63 @@ namespace Ryujinx.Graphics.Vulkan
_dummyTexture.SetData(dummyTextureData);
}
private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size)
{
return offset < bindingOffset + (int)info.Range && (offset + size) > bindingOffset;
}
internal void Rebind(Auto<DisposableBuffer> buffer, int offset, int size)
{
if (_program == null)
{
return;
}
// Check stage bindings
_uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) =>
{
for (int i = 0; i < count; i++)
{
ref BufferRef bufferRef = ref _uniformBufferRefs[binding];
if (bufferRef.Buffer == buffer)
{
ref DescriptorBufferInfo info = ref _uniformBuffers[binding];
int bindingOffset = bufferRef.Offset;
if (BindingOverlaps(ref info, bindingOffset, offset, size))
{
_uniformSet.Clear(binding);
SignalDirty(DirtyFlags.Uniform);
}
}
binding++;
}
});
_storageMirrored.Union(_storageSet).SignalSet((int binding, int count) =>
{
for (int i = 0; i < count; i++)
{
ref BufferRef bufferRef = ref _storageBufferRefs[binding];
if (bufferRef.Buffer == buffer)
{
ref DescriptorBufferInfo info = ref _storageBuffers[binding];
int bindingOffset = bufferRef.Offset;
if (BindingOverlaps(ref info, bindingOffset, offset, size))
{
_storageSet.Clear(binding);
SignalDirty(DirtyFlags.Storage);
}
}
binding++;
}
});
}
public void SetProgram(ShaderCollection program)
{
_program = program;
@ -180,22 +257,28 @@ namespace Ryujinx.Graphics.Vulkan
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true);
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
? null
: _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, buffer.Write, isSSBO: true);
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
DescriptorBufferInfo info = new()
{
Offset = (ulong)buffer.Offset,
Range = (ulong)buffer.Size,
};
var newRef = new BufferRef(vkBuffer, ref buffer);
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
{
_storageSet[index] = false;
_storageSet.Clear(index);
currentInfo = info;
currentVkBuffer = vkBuffer;
currentBufferRef = newRef;
}
}
@ -209,21 +292,24 @@ namespace Ryujinx.Graphics.Vulkan
var vkBuffer = buffers[i];
int index = first + i;
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
ref BufferRef currentBufferRef = ref _storageBufferRefs[index];
DescriptorBufferInfo info = new()
{
Offset = 0,
Range = Vk.WholeSize,
};
BufferRef newRef = new(vkBuffer);
ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
{
_storageSet[index] = false;
_storageSet.Clear(index);
currentInfo = info;
currentVkBuffer = vkBuffer;
currentBufferRef = newRef;
}
}
@ -288,22 +374,28 @@ namespace Ryujinx.Graphics.Vulkan
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];
Auto<DisposableBuffer> vkBuffer = buffer.Handle == BufferHandle.Null
? null
: _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref BufferRef currentBufferRef = ref _uniformBufferRefs[index];
DescriptorBufferInfo info = new()
{
Offset = (ulong)buffer.Offset,
Range = (ulong)buffer.Size,
};
BufferRef newRef = new(vkBuffer, ref buffer);
ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index];
if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
{
_uniformSet[index] = false;
_uniformSet.Clear(index);
currentInfo = info;
currentVkBuffer = vkBuffer;
currentBufferRef = newRef;
}
}
@ -353,13 +445,26 @@ namespace Ryujinx.Graphics.Vulkan
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UpdateBuffer(
private static bool UpdateBuffer(
CommandBufferScoped cbs,
ref DescriptorBufferInfo info,
Auto<DisposableBuffer> buffer,
Auto<DisposableBuffer> dummyBuffer)
ref BufferRef buffer,
Auto<DisposableBuffer> dummyBuffer,
bool mirrorable)
{
info.Buffer = buffer?.Get(cbs, (int)info.Offset, (int)info.Range).Value ?? default;
int offset = buffer.Offset;
bool mirrored = false;
if (mirrorable)
{
info.Buffer = buffer.Buffer?.GetMirrorable(cbs, ref offset, (int)info.Range, out mirrored).Value ?? default;
}
else
{
info.Buffer = buffer.Buffer?.Get(cbs, offset, (int)info.Range, buffer.Write).Value ?? default;
}
info.Offset = (ulong)offset;
// The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE.
if (info.Buffer.Handle == 0)
@ -368,6 +473,8 @@ namespace Ryujinx.Graphics.Vulkan
info.Offset = 0;
info.Range = Vk.WholeSize;
}
return mirrored;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -376,7 +483,7 @@ namespace Ryujinx.Graphics.Vulkan
var program = _program;
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0 && setIndex != PipelineBase.UniformSetIndex)
if (bindingSegments.Length == 0)
{
return;
}
@ -404,11 +511,13 @@ namespace Ryujinx.Graphics.Vulkan
{
int index = binding + i;
if (!_uniformSet[index])
if (_uniformSet.Set(index))
{
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
ref BufferRef buffer = ref _uniformBufferRefs[index];
_uniformSet[index] = true;
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
_uniformMirrored.Set(index, mirrored);
}
}
@ -421,11 +530,19 @@ namespace Ryujinx.Graphics.Vulkan
{
int index = binding + i;
if (!_storageSet[index])
{
UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
ref BufferRef buffer = ref _storageBufferRefs[index];
_storageSet[index] = true;
if (_storageSet.Set(index))
{
ref var info = ref _storageBuffers[index];
bool mirrored = UpdateBuffer(cbs,
ref info,
ref _storageBufferRefs[index],
dummyBuffer,
!buffer.Write && info.Range <= StorageBufferMaxMirrorable);
_storageMirrored.Set(index, mirrored);
}
}
@ -464,7 +581,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
@ -489,7 +606,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferImages[..count], DescriptorType.StorageTexelBuffer);
@ -546,10 +663,10 @@ namespace Ryujinx.Graphics.Vulkan
{
int index = binding + i;
if (!_uniformSet[index])
if (_uniformSet.Set(index))
{
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
_uniformSet[index] = true;
ref BufferRef buffer = ref _uniformBufferRefs[index];
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
doUpdate = true;
}
}
@ -582,17 +699,17 @@ namespace Ryujinx.Graphics.Vulkan
{
_dirty = DirtyFlags.All;
Array.Clear(_uniformSet);
Array.Clear(_storageSet);
_uniformSet.Clear();
_storageSet.Clear();
}
private static void SwapBuffer(Auto<DisposableBuffer>[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
{
for (int i = 0; i < list.Length; i++)
{
if (list[i] == from)
if (list[i].Buffer == from)
{
list[i] = to;
list[i].Buffer = to;
}
}
}

View File

@ -427,6 +427,7 @@ namespace Ryujinx.Graphics.Vulkan
return access switch
{
BufferAccess.FlushPersistent => BufferAllocationType.HostMapped,
BufferAccess.Stream => BufferAllocationType.HostMapped,
_ => BufferAllocationType.Auto,
};
}

View File

@ -52,6 +52,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly PortabilitySubsetFlags PortabilitySubset;
public readonly uint VertexBufferAlignment;
public readonly uint SubTexelPrecisionBits;
public readonly ulong MinResourceAlignment;
public HardwareCapabilities(
bool supportsIndexTypeUint8,
@ -89,7 +90,8 @@ namespace Ryujinx.Graphics.Vulkan
SampleCountFlags supportedSampleCounts,
PortabilitySubsetFlags portabilitySubset,
uint vertexBufferAlignment,
uint subTexelPrecisionBits)
uint subTexelPrecisionBits,
ulong minResourceAlignment)
{
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
SupportsCustomBorderColor = supportsCustomBorderColor;
@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.Vulkan
PortabilitySubset = portabilitySubset;
VertexBufferAlignment = vertexBufferAlignment;
SubTexelPrecisionBits = subTexelPrecisionBits;
MinResourceAlignment = minResourceAlignment;
}
}
}

View File

@ -5,6 +5,8 @@ namespace Ryujinx.Graphics.Vulkan
{
internal struct IndexBufferState
{
private const int IndexBufferMaxMirrorable = 0x20000;
public static IndexBufferState Null => new(BufferHandle.Null, 0, 0);
private readonly int _offset;
@ -37,6 +39,7 @@ namespace Ryujinx.Graphics.Vulkan
Auto<DisposableBuffer> autoBuffer;
int offset, size;
IndexType type = _type;
bool mirrorable = false;
if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8)
{
@ -56,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
autoBuffer = null;
}
mirrorable = _size < IndexBufferMaxMirrorable;
offset = _offset;
size = _size;
}
@ -64,7 +69,9 @@ namespace Ryujinx.Graphics.Vulkan
if (autoBuffer != null)
{
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, offset, size).Value, (ulong)offset, type);
DisposableBuffer buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, size, out _) : autoBuffer.Get(cbs, offset, size);
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, buffer.Value, (ulong)offset, type);
}
}
@ -155,5 +162,10 @@ namespace Ryujinx.Graphics.Vulkan
_buffer = to;
}
}
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
{
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
}
}
}

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
class MultiFenceHolder
{
private static readonly int _bufferUsageTrackingGranularity = 4096;
private const int BufferUsageTrackingGranularity = 4096;
private readonly FenceHolder[] _fences;
private readonly BufferUsageBitmap _bufferUsageBitmap;
@ -28,18 +28,24 @@ namespace Ryujinx.Graphics.Vulkan
public MultiFenceHolder(int size)
{
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
_bufferUsageBitmap = new BufferUsageBitmap(size, _bufferUsageTrackingGranularity);
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
}
/// <summary>
/// Adds buffer usage information to the uses list.
/// Adds read/write buffer usage information to the uses list.
/// </summary>
/// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
public void AddBufferUse(int cbIndex, int offset, int size)
/// <param name="write">Whether the access is a write or not</param>
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size);
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
if (write)
{
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
}
}
/// <summary>
@ -68,10 +74,11 @@ namespace Ryujinx.Graphics.Vulkan
/// </summary>
/// <param name="offset">Offset of the buffer being used</param>
/// <param name="size">Size of the buffer region being used, in bytes</param>
/// <param name="write">True if only write usages should count</param>
/// <returns>True if in use, false otherwise</returns>
public bool IsBufferRangeInUse(int offset, int size)
public bool IsBufferRangeInUse(int offset, int size, bool write)
{
return _bufferUsageBitmap.OverlapsWith(offset, size);
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
}
/// <summary>

View File

@ -193,7 +193,7 @@ namespace Ryujinx.Graphics.Vulkan
{
EndRenderPass();
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size).Value;
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size, true).Value;
BufferHolder.InsertBufferBarrier(
Gd,
@ -469,6 +469,10 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
UpdateIndexBufferPattern();
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
BeginRenderPass();
@ -498,10 +502,6 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
ResumeTransformFeedbackInternal();
Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
@ -515,15 +515,19 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
var countBuffer = Gd.BufferManager
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
UpdateIndexBufferPattern();
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
BeginRenderPass();
DrawCount++;
var countBuffer = Gd.BufferManager
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
if (_indexBufferPattern != null)
{
// Convert the index buffer into a supported topology.
@ -570,10 +574,6 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
ResumeTransformFeedbackInternal();
if (Gd.Capabilities.SupportsIndirectParameters)
@ -609,15 +609,15 @@ namespace Ryujinx.Graphics.Vulkan
// TODO: Support quads and other unsupported topologies.
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
BeginRenderPass();
ResumeTransformFeedbackInternal();
DrawCount++;
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
Gd.Api.CmdDrawIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
}
@ -634,6 +634,14 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
var countBuffer = Gd.BufferManager
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size, false).Value;
// TODO: Support quads and other unsupported topologies.
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
@ -641,14 +649,6 @@ namespace Ryujinx.Graphics.Vulkan
ResumeTransformFeedbackInternal();
DrawCount++;
var buffer = Gd.BufferManager
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
var countBuffer = Gd.BufferManager
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
CommandBuffer,
buffer,
@ -709,6 +709,26 @@ namespace Ryujinx.Graphics.Vulkan
return CommandBuffer.Handle == cb.Handle;
}
internal void Rebind(Auto<DisposableBuffer> buffer, int offset, int size)
{
_descriptorSetUpdater.Rebind(buffer, offset, size);
if (_indexBuffer.Overlaps(buffer, offset, size))
{
_indexBuffer.BindIndexBuffer(Gd, Cbs);
}
for (int i = 0; i < _vertexBuffers.Length; i++)
{
if (_vertexBuffers[i].Overlaps(buffer, offset, size))
{
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
}
}
_vertexBufferUpdater.Commit(Cbs);
}
#pragma warning disable CA1822 // Mark member as static
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Graphics.Vulkan
private CounterQueueEvent _activeConditionalRender;
private readonly List<BufferedQuery> _pendingQueryCopies;
private readonly List<BufferHolder> _activeBufferMirrors;
private ulong _byteWeight;
@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.Vulkan
_activeQueries = new List<(QueryPool, bool)>();
_pendingQueryCopies = new();
_backingSwaps = new();
_activeBufferMirrors = new();
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
}
@ -233,6 +235,12 @@ namespace Ryujinx.Graphics.Vulkan
Gd.RegisterFlush();
// Restore per-command buffer state.
foreach (BufferHolder buffer in _activeBufferMirrors)
{
buffer.ClearMirrors();
}
_activeBufferMirrors.Clear();
foreach ((var queryPool, var isOcclusion) in _activeQueries)
{
@ -249,6 +257,11 @@ namespace Ryujinx.Graphics.Vulkan
Restore();
}
public void RegisterActiveMirror(BufferHolder buffer)
{
_activeBufferMirrors.Add(buffer);
}
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool)
{
if (needsReset)

View File

@ -183,7 +183,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
public void PoolCopy(CommandBufferScoped cbs)
{
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value;
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long), true).Value;
QueryResultFlags flags = QueryResultFlags.ResultWaitBit;

View File

@ -1,12 +1,28 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Ryujinx.Graphics.Vulkan
{
readonly struct StagingBufferReserved
{
public readonly BufferHolder Buffer;
public readonly int Offset;
public readonly int Size;
public StagingBufferReserved(BufferHolder buffer, int offset, int size)
{
Buffer = buffer;
Offset = offset;
Size = size;
}
}
class StagingBuffer : IDisposable
{
private const int BufferSize = 16 * 1024 * 1024;
private const int BufferSize = 32 * 1024 * 1024;
private int _freeOffset;
private int _freeSize;
@ -130,13 +146,83 @@ namespace Ryujinx.Graphics.Vulkan
}
}
endRenderPass();
endRenderPass?.Invoke();
PushDataImpl(cbs, dst, dstOffset, data);
return true;
}
private StagingBufferReserved ReserveDataImpl(CommandBufferScoped cbs, int size, int alignment)
{
// Assumes the caller has already determined that there is enough space.
int offset = BitUtils.AlignUp(_freeOffset, alignment);
int padding = offset - _freeOffset;
int capacity = Math.Min(_freeSize, BufferSize - offset);
int reservedLength = size + padding;
if (capacity < size)
{
offset = 0; // Place at start.
reservedLength += capacity;
}
_freeOffset = (_freeOffset + reservedLength) & (BufferSize - 1);
_freeSize -= reservedLength;
Debug.Assert(_freeSize >= 0);
_pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), reservedLength));
return new StagingBufferReserved(_buffer, offset, size);
}
private int GetContiguousFreeSize(int alignment)
{
int alignedFreeOffset = BitUtils.AlignUp(_freeOffset, alignment);
int padding = alignedFreeOffset - _freeOffset;
// Free regions:
// - Aligned free offset to end (minimum free size - padding)
// - 0 to _freeOffset + freeSize wrapped (only if free area contains 0)
int endOffset = (_freeOffset + _freeSize) & (BufferSize - 1);
return Math.Max(
Math.Min(_freeSize - padding, BufferSize - alignedFreeOffset),
endOffset <= _freeOffset ? Math.Min(_freeSize, endOffset) : 0
);
}
/// <summary>
/// Reserve a range on the staging buffer for the current command buffer and upload data to it.
/// </summary>
/// <param name="cbs">Command buffer to reserve the data on</param>
/// <param name="data">The data to upload</param>
/// <param name="alignment">The required alignment for the buffer offset</param>
/// <returns>The reserved range of the staging buffer</returns>
public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size, int alignment)
{
if (size > BufferSize)
{
return null;
}
// Temporary reserved data cannot be fragmented.
if (GetContiguousFreeSize(alignment) < size)
{
FreeCompleted();
if (GetContiguousFreeSize(alignment) < size)
{
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Staging buffer out of space to reserve data of size {size}.");
return null;
}
}
return ReserveDataImpl(cbs, size, alignment);
}
private bool WaitFreeCompleted(CommandBufferPool cbp)
{
if (_pendingCopies.TryPeek(out var pc))

Some files were not shown because too many files have changed in this diff Show More