diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2023-08-29 21:10:34 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-08-29 21:10:34 -0300 |
| commit | f09bba82b9366e5912b639a610ae89cbb1cf352c (patch) | |
| tree | 4811ffa52206eed7cf8aa200c64deb7410e5c56b /src/Ryujinx.Graphics.Gpu/Shader | |
| parent | 93d78f9ac4a37a50f0cc2e57addd330d072af742 (diff) | |
Geometry shader emulation for macOS (#5551)
* Implement vertex and geometry shader conversion to compute
* Call InitializeReservedCounts for compute too
* PR feedback
* Set clip distance mask for geometry and tessellation shaders too
* Transform feedback emulation only for vertex
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Shader')
11 files changed, 317 insertions, 81 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs index ff9c39a1..600c8a98 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs @@ -15,6 +15,16 @@ namespace Ryujinx.Graphics.Gpu.Shader public IProgram HostProgram { get; } /// <summary> + /// Optional vertex shader converted to compute. + /// </summary> + public ShaderAsCompute VertexAsCompute { get; } + + /// <summary> + /// Optional geometry shader converted to compute. + /// </summary> + public ShaderAsCompute GeometryAsCompute { get; } + + /// <summary> /// GPU state used to create this version of the shader. /// </summary> public ShaderSpecializationState SpecializationState { get; } @@ -45,12 +55,25 @@ namespace Ryujinx.Graphics.Gpu.Shader Bindings = new CachedShaderBindings(shaders.Length == 1, shaders); } + public CachedShaderProgram( + IProgram hostProgram, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + ShaderSpecializationState specializationState, + CachedShaderStage[] shaders) : this(hostProgram, specializationState, shaders) + { + VertexAsCompute = vertexAsCompute; + GeometryAsCompute = geometryAsCompute; + } + /// <summary> /// Dispose of the host shader resources. /// </summary> public void Dispose() { HostProgram.Dispose(); + VertexAsCompute?.HostProgram.Dispose(); + GeometryAsCompute?.HostProgram.Dispose(); } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index 93d293f6..de6432bc 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ShaderSpecializationState oldSpecState, ShaderSpecializationState newSpecState, ResourceCounts counts, - int stageIndex) : base(context, counts, stageIndex, oldSpecState.TransformFeedbackDescriptors != null) + int stageIndex) : base(context, counts, stageIndex) { _data = data; _cb1Data = cb1Data; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 08096bd5..99ef89ed 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5609; + private const uint CodeGenVersion = 5551; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; @@ -141,6 +141,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache public ShaderStage Stage; /// <summary> + /// Number of vertices that each output primitive has on a geometry shader. + /// </summary> + public byte GeometryVerticesPerPrimitive; + + /// <summary> + /// Maximum number of vertices that a geometry shader may generate. + /// </summary> + public ushort GeometryMaxOutputVertices; + + /// <summary> + /// Number of invocations per primitive on tessellation or geometry shaders. + /// </summary> + public ushort ThreadsPerInputPrimitive; + + /// <summary> /// Indicates if the fragment shader accesses the fragment coordinate built-in variable. /// </summary> public bool UsesFragCoord; @@ -783,9 +798,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache sBuffers, textures, images, - ShaderIdentification.None, - 0, dataInfo.Stage, + dataInfo.GeometryVerticesPerPrimitive, + dataInfo.GeometryMaxOutputVertices, + dataInfo.ThreadsPerInputPrimitive, dataInfo.UsesFragCoord, dataInfo.UsesInstanceId, dataInfo.UsesDrawParameters, @@ -813,6 +829,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache TexturesCount = (ushort)info.Textures.Count, ImagesCount = (ushort)info.Images.Count, Stage = info.Stage, + GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, + GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, + ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, UsesFragCoord = info.UsesFragCoord, UsesInstanceId = info.UsesInstanceId, UsesDrawParameters = info.UsesDrawParameters, diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index 8c2108bf..153fc442 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -595,6 +595,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ResourceCounts counts = new(); + DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages]; TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; @@ -626,14 +627,22 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); } + gpuAccessors[stageIndex] = gpuAccessor; translatorContexts[stageIndex + 1] = currentStage; nextStage = currentStage; } } - if (!_context.Capabilities.SupportsGeometryShader) + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + + // We don't support caching shader stages that have been converted to compute currently, + // so just eliminate them if they exist in the cache. + if (vertexToCompute) { - ShaderCache.TryRemoveGeometryStage(translatorContexts); + return; } CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; @@ -647,6 +656,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (currentStage != null) { + gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute); + ShaderProgram program; byte[] guestCode = guestShaders[stageIndex + 1].Value.Code; @@ -701,6 +712,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ResourceCounts counts = new(); ShaderSpecializationState newSpecState = new(ref specState.ComputeState); DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index b4f4a439..1d84d0e4 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -25,11 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="channel">GPU channel</param> /// <param name="state">Current GPU state</param> /// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param> - public GpuAccessor( - GpuContext context, - GpuChannel channel, - GpuAccessorState state, - int stageIndex) : base(context, state.ResourceCounts, stageIndex, state.TransformFeedbackDescriptors != null) + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context, state.ResourceCounts, stageIndex) { _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _channel = channel; @@ -49,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="context">GPU context</param> /// <param name="channel">GPU channel</param> /// <param name="state">Current GPU state</param> - public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0, false) + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0) { _channel = channel; _state = state; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 52193940..9d030cd6 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -15,8 +15,10 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly ResourceCounts _resourceCounts; private readonly int _stageIndex; - private readonly int _reservedConstantBuffers; - private readonly int _reservedStorageBuffers; + private int _reservedConstantBuffers; + private int _reservedStorageBuffers; + private int _reservedTextures; + private int _reservedImages; /// <summary> /// Creates a new GPU accessor. @@ -24,15 +26,26 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="context">GPU context</param> /// <param name="resourceCounts">Counter of GPU resources used by the shader</param> /// <param name="stageIndex">Index of the shader stage, 0 for compute</param> - /// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param> - public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex, bool tfEnabled) + public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex) { _context = context; _resourceCounts = resourceCounts; _stageIndex = stageIndex; + } + + /// <summary> + /// Initializes counts for bindings that will be reserved for emulator use. + /// </summary> + /// <param name="tfEnabled">Indicates if the current graphics shader is used with transform feedback enabled</param> + /// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param> + public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute) + { + ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); - _reservedConstantBuffers = 1; // For the support buffer. - _reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0; + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; } public int QueryBindingConstantBuffer(int index) @@ -69,6 +82,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public int QueryBindingTexture(int index, bool isBuffer) { + int binding; + if (_context.Capabilities.Api == TargetApi.Vulkan) { if (isBuffer) @@ -76,16 +91,20 @@ namespace Ryujinx.Graphics.Gpu.Shader index += (int)_context.Capabilities.MaximumTexturesPerStage; } - return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); } else { - return _resourceCounts.TexturesCount++; + binding = _resourceCounts.TexturesCount++; } + + return binding + _reservedTextures; } public int QueryBindingImage(int index, bool isBuffer) { + int binding; + if (_context.Capabilities.Api == TargetApi.Vulkan) { if (isBuffer) @@ -93,12 +112,14 @@ namespace Ryujinx.Graphics.Gpu.Shader index += (int)_context.Capabilities.MaximumImagesPerStage; } - return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); } else { - return _resourceCounts.ImagesCount++; + binding = _resourceCounts.ImagesCount++; } + + return binding + _reservedImages; } private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs new file mode 100644 index 00000000..71540a13 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + class ShaderAsCompute + { + public IProgram HostProgram { get; } + public ShaderProgramInfo Info { get; } + public ResourceReservations Reservations { get; } + + public ShaderAsCompute(IProgram hostProgram, ShaderProgramInfo info, ResourceReservations reservations) + { + HostProgram = hostProgram; + Info = info; + Reservations = reservations; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 97d7a720..38be262a 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -215,9 +215,10 @@ namespace Ryujinx.Graphics.Gpu.Shader ShaderSpecializationState specState = new(ref computeState); GpuAccessorState gpuAccessorState = new(poolState, computeState, default, specState); GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa); - TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode); + TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false); ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) }; ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info); @@ -321,6 +322,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); + GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages]; TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; @@ -345,22 +347,31 @@ namespace Ryujinx.Graphics.Gpu.Shader translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); } + gpuAccessors[stageIndex] = gpuAccessor; translatorContexts[stageIndex + 1] = currentStage; nextStage = currentStage; } } - if (!_context.Capabilities.SupportsGeometryShader) - { - TryRemoveGeometryStage(translatorContexts); - } + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore); CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; List<ShaderSource> shaderSources = new(); TranslatorContext previousStage = null; + ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute); + + if (geometryToCompute && translatorContexts[4] != null) + { + translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]); + } - ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null); + ShaderAsCompute vertexAsCompute = null; + ShaderAsCompute geometryAsCompute = null; for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) { @@ -368,8 +379,12 @@ namespace Ryujinx.Graphics.Gpu.Shader if (currentStage != null) { + gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute); + ShaderProgram program; + bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute); + if (stageIndex == 0 && translatorContexts[0] != null) { TranslatedShaderVertexPair translatedShader = TranslateShader( @@ -378,7 +393,8 @@ namespace Ryujinx.Graphics.Gpu.Shader currentStage, translatorContexts[0], cachedGuestCode.VertexACode, - cachedGuestCode.VertexBCode); + cachedGuestCode.VertexBCode, + asCompute); shaders[0] = translatedShader.VertexA; shaders[1] = translatedShader.VertexB; @@ -388,12 +404,31 @@ namespace Ryujinx.Graphics.Gpu.Shader { byte[] code = cachedGuestCode.GetByIndex(stageIndex); - TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code); + TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute); shaders[stageIndex + 1] = translatedShader.Shader; program = translatedShader.Program; } + if (asCompute) + { + bool tfEnabled = transformFeedbackDescriptors != null; + + if (stageIndex == 0) + { + vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + + TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage; + + program = lastInVertexPipeline.GenerateVertexPassthroughForCompute(); + } + else + { + geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + program = null; + } + } + if (program != null) { shaderSources.Add(CreateShaderSource(program)); @@ -418,46 +453,81 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info); - gpShaders = new CachedShaderProgram(hostProgram, specState, shaders); + gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders); _graphicsShaderCache.Add(gpShaders); - EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + + // We don't currently support caching shaders that have been converted to compute. + if (vertexAsCompute == null) + { + EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + } + _gpPrograms[addresses] = gpShaders; return gpShaders; } /// <summary> - /// Tries to eliminate the geometry stage from the array of translator contexts. + /// Checks if a vertex shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. /// </summary> - /// <param name="translatorContexts">Array of translator contexts</param> - public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts) + /// <param name="context">GPU context of the shader</param> + /// <param name="vertexHasStore">Whether the vertex shader has image or storage buffer store operations</param> + /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param> + /// <param name="hasGeometryShader">Whether a geometry shader exists</param> + /// <returns>True if the vertex shader should be converted to compute, false otherwise</returns> + public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader) { - if (translatorContexts[4] != null) + // If the host does not support store operations on vertex, + // we need to emulate it on a compute shader. + if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore) { - // We have a geometry shader, but geometry shaders are not supported. - // Try to eliminate the geometry shader. + return true; + } - ShaderProgramInfo info = translatorContexts[4].Translate().Info; + // If any stage after the vertex stage is converted to compute, + // we need to convert vertex to compute too. + return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore); + } - if (info.Identification == ShaderIdentification.GeometryLayerPassthrough) - { - // We managed to identify that this geometry shader is only used to set the output Layer value, - // we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it. + /// <summary> + /// Checks if a geometry shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. + /// </summary> + /// <param name="context">GPU context of the shader</param> + /// <param name="geometryHasStore">Whether the geometry shader has image or storage buffer store operations, if one exists</param> + /// <returns>True if the geometry shader should be converted to compute, false otherwise</returns> + public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore) + { + return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) || + !context.Capabilities.SupportsGeometryShader; + } - for (int i = 3; i >= 1; i--) - { - if (translatorContexts[i] != null) - { - translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute); - translatorContexts[i].SetLastInVertexPipeline(); - break; - } - } + /// <summary> + /// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute, + /// based on the supported host features. + /// </summary> + /// <param name="capabilities">Host capabilities</param> + /// <returns>True if the possibility of a shader being converted to compute exists, false otherwise</returns> + public static bool MayConvertVtgToCompute(ref Capabilities capabilities) + { + return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader; + } - translatorContexts[4] = null; - } - } + /// <summary> + /// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute. + /// </summary> + /// <param name="program">Shader program</param> + /// <param name="context">Translation context of the shader</param> + /// <param name="tfEnabled">Whether transform feedback is enabled</param> + /// <returns>Compute shader</returns> + private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled) + { + ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language); + ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled); + + return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations()); } /// <summary> @@ -573,9 +643,16 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + bool vertexAsCompute = gpShaders.VertexAsCompute != null; bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false; - return gpShaders.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true); + return gpShaders.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true); } /// <summary> @@ -636,6 +713,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="vertexA">Optional translator context of the shader that should be combined</param> /// <param name="codeA">Optional Maxwell binary code of the Vertex A shader, if present</param> /// <param name="codeB">Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache</param> + /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param> /// <returns>Compiled graphics shader code</returns> private static TranslatedShaderVertexPair TranslateShader( ShaderDumper dumper, @@ -643,7 +721,8 @@ namespace Ryujinx.Graphics.Gpu.Shader TranslatorContext currentStage, TranslatorContext vertexA, byte[] codeA, - byte[] codeB) + byte[] codeB, + bool asCompute) { ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1); @@ -663,7 +742,7 @@ namespace Ryujinx.Graphics.Gpu.Shader pathsB = dumper.Dump(codeB, compute: false); } - ShaderProgram program = currentStage.Translate(vertexA); + ShaderProgram program = currentStage.Translate(vertexA, asCompute); pathsB.Prepend(program); pathsA.Prepend(program); @@ -681,8 +760,9 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="channel">GPU channel using the shader</param> /// <param name="context">Translator context of the stage to be translated</param> /// <param name="code">Optional Maxwell binary code of the current stage shader, if present on cache</param> + /// <param name="asCompute">Indicates that the vertex shader should be converted to a compute shader</param> /// <returns>Compiled graphics shader code</returns> - private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code) + private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute) { var memoryManager = channel.MemoryManager; @@ -694,7 +774,7 @@ namespace Ryujinx.Graphics.Gpu.Shader code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray(); ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default; - ShaderProgram program = context.Translate(); + ShaderProgram program = context.Translate(asCompute); paths.Prepend(program); diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs index af1e1ee3..bea916a6 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -33,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly int _reservedConstantBuffers; private readonly int _reservedStorageBuffers; + private readonly int _reservedTextures; + private readonly int _reservedImages; private readonly List<ResourceDescriptor>[] _resourceDescriptors; private readonly List<ResourceUsage>[] _resourceUsages; @@ -42,7 +44,8 @@ namespace Ryujinx.Graphics.Gpu.Shader /// </summary> /// <param name="context">GPU context that owns the shaders that will be added to the builder</param> /// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param> - public ShaderInfoBuilder(GpuContext context, bool tfEnabled) + /// <param name="vertexAsCompute">Indicates that the vertex shader will be emulated on a compute shader</param> + public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false) { _context = context; @@ -60,27 +63,34 @@ namespace Ryujinx.Graphics.Gpu.Shader AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1); AddUsage(SupportBufferStages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 0, 1); - _reservedConstantBuffers = 1; // For the support buffer. + ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); - if (!context.Capabilities.SupportsTransformFeedback && tfEnabled) - { - _reservedStorageBuffers = 5; + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; - AddDescriptor(VtgStages, ResourceType.StorageBuffer, StorageSetIndex, 0, 5); - AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Read, StorageSetIndex, 0, 1); - AddUsage(VtgStages, ResourceType.StorageBuffer, ResourceAccess.Write, StorageSetIndex, 1, 4); - } - else - { - _reservedStorageBuffers = 0; - } + // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; + + PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, ResourceAccess.Read, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); + PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, ResourceAccess.ReadWrite, StorageSetIndex, 0, rrc.ReservedStorageBuffers); + PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, ResourceAccess.Read, TextureSetIndex, 0, rrc.ReservedTextures); + PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ResourceAccess.ReadWrite, ImageSetIndex, 0, rrc.ReservedImages); + } + + private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, ResourceAccess access, int setIndex, int start, int count) + { + AddDescriptor(stages, type, setIndex, start, count); + AddUsage(stages, type, access, setIndex, start, count); } /// <summary> /// Adds information from a given shader stage. /// </summary> /// <param name="info">Shader stage information</param> - public void AddStageInfo(ShaderProgramInfo info) + /// <param name="vertexAsCompute">True if the shader stage has been converted into a compute shader</param> + public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false) { if (info.Stage == ShaderStage.Fragment) { @@ -96,7 +106,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _ => 0, }); - ResourceStages stages = info.Stage switch + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch { ShaderStage.Compute => ResourceStages.Compute, ShaderStage.Vertex => ResourceStages.Vertex, @@ -114,8 +124,8 @@ namespace Ryujinx.Graphics.Gpu.Shader int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage; int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage; - int textureBinding = stageIndex * texturesPerStage * 2; - int imageBinding = stageIndex * imagesPerStage * 2; + int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2; + int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2; AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage); AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage); @@ -285,11 +295,28 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <returns>Shader information</returns> public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false) { - ShaderInfoBuilder builder = new(context, tfEnabled: false); + ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false); builder.AddStageInfo(info); return builder.Build(null, fromCache); } + + /// <summary> + /// Builds shader information for a vertex or geometry shader thas was converted to compute shader. + /// </summary> + /// <param name="context">GPU context that owns the shader</param> + /// <param name="info">Compute shader information</param> + /// <param name="tfEnabled">Indicates if the graphics shader is used with transform feedback enabled</param> + /// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param> + /// <returns>Shader information</returns> + public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false) + { + ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true); + + builder.AddStageInfo(info, vertexAsCompute: true); + + return builder.Build(null, fromCache); + } } } diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs index e57e1df1..3c2f0b9b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs @@ -35,9 +35,16 @@ namespace Ryujinx.Graphics.Gpu.Shader { foreach (var entry in _entries) { + bool vertexAsCompute = entry.VertexAsCompute != null; bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false; - if (entry.SpecializationState.MatchesGraphics(channel, ref poolState, ref graphicsState, usesDrawParameters, true)) + if (entry.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true)) { program = entry; return true; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index fcd95375..a41f761b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -457,6 +457,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="channel">GPU channel</param> /// <param name="poolState">Texture pool state</param> /// <param name="graphicsState">Graphics state</param> + /// <param name="vertexAsCompute">Indicates that the vertex shader has been converted into a compute shader</param> /// <param name="usesDrawParameters">Indicates whether the vertex shader accesses draw parameters</param> /// <param name="checkTextures">Indicates whether texture descriptors should be checked</param> /// <returns>True if the state matches, false otherwise</returns> @@ -464,6 +465,7 @@ namespace Ryujinx.Graphics.Gpu.Shader GpuChannel channel, ref GpuChannelPoolState poolState, ref GpuChannelGraphicsState graphicsState, + bool vertexAsCompute, bool usesDrawParameters, bool checkTextures) { @@ -497,9 +499,25 @@ namespace Ryujinx.Graphics.Gpu.Shader return false; } - if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan())) + if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute) { - return false; + for (int index = 0; index < graphicsState.AttributeTypes.Length; index++) + { + AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]); + AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]); + + if (lType != rType) + { + return false; + } + } + } + else + { + if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan())) + { + return false; + } } if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters) @@ -530,6 +548,19 @@ namespace Ryujinx.Graphics.Gpu.Shader return Matches(channel, ref poolState, checkTextures, isCompute: false); } + private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) + { + type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); + + if (channel.Capabilities.SupportsScaledVertexFormats && + (type == AttributeType.Sscaled || type == AttributeType.Uscaled)) + { + type = AttributeType.Float; + } + + return type; + } + /// <summary> /// Checks if the recorded state matches the current GPU compute engine state. /// </summary> |
