mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-07-17 03:36:29 +02:00
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
This commit is contained in:
parent
93d78f9ac4
commit
f09bba82b9
65 changed files with 3912 additions and 593 deletions
|
@ -14,6 +14,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// </summary>
|
||||
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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
@ -140,6 +140,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||
/// </summary>
|
||||
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>
|
||||
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
_reservedConstantBuffers = 1; // For the support buffer.
|
||||
_reservedStorageBuffers = !context.Capabilities.SupportsTransformFeedback && tfEnabled ? 5 : 0;
|
||||
/// <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 = 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)
|
||||
|
|
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
20
src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null);
|
||||
if (geometryToCompute && translatorContexts[4] != null)
|
||||
{
|
||||
translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
ShaderProgramInfo info = translatorContexts[4].Translate().Info;
|
||||
|
||||
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.
|
||||
|
||||
for (int i = 3; i >= 1; i--)
|
||||
{
|
||||
if (translatorContexts[i] != null)
|
||||
{
|
||||
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
|
||||
translatorContexts[i].SetLastInVertexPipeline();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
translatorContexts[4] = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If any stage after the vertex stage is converted to compute,
|
||||
// we need to convert vertex to compute too.
|
||||
return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue