mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-03-14 10:20:17 +00:00
Merge branch 'Ryujinx:master' into master
This commit is contained in:
commit
e6b4b4f96e
122 changed files with 7635 additions and 2787 deletions
|
@ -59,7 +59,7 @@ namespace ARMeilleure.CodeGen.Optimizations
|
|||
BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null;
|
||||
|
||||
// If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps.
|
||||
if (from.Operations.Count == 0 && fromPred != null)
|
||||
if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1)
|
||||
{
|
||||
for (int i = 0; i < fromPred.SuccessorsCount; i++)
|
||||
{
|
||||
|
|
|
@ -191,7 +191,7 @@ namespace ARMeilleure.Signal
|
|||
// Is the fault address within this tracked region?
|
||||
Operand inRange = context.BitwiseAnd(
|
||||
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.Less)
|
||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
|
||||
);
|
||||
|
||||
// Only call tracking if in range.
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
|
|||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 3193; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 3267; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||
using Ryujinx.Audio.Renderer.Utils.Math;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
@ -45,7 +47,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
|
@ -63,9 +65,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
||||
{
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
|
@ -78,133 +85,148 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
float input = inputBuffer[i] * 64;
|
||||
float delayLineValue = state.DelayLines[0].Read();
|
||||
|
||||
float lowPassResult = (input * inGain + delayLineValue * feedbackGain) * state.LowPassBaseGain + state.LowPassZ[0] * state.LowPassFeedbackGain;
|
||||
float temp = input * inGain + delayLineValue * feedbackGain;
|
||||
|
||||
state.LowPassZ[0] = lowPassResult;
|
||||
|
||||
state.DelayLines[0].Update(lowPassResult);
|
||||
state.UpdateLowPassFilter(ref temp, 1);
|
||||
|
||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayStereo(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 2;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain , delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector2 channelInput = new Vector2
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + delayLineValues[1] * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + delayLineValues[0] * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector2 delayLineValues = new Vector2()
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 4;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
|
||||
delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain,
|
||||
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain,
|
||||
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector4 channelInput = new Vector4
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
Z = *((float*)inputBuffers[2] + i) * 64,
|
||||
W = *((float*)inputBuffers[3] + i) * 64
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[0] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector4 delayLineValues = new Vector4()
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
Z = state.DelayLines[2].Read(),
|
||||
W = state.DelayLines[3].Read()
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
|
||||
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private unsafe void ProcessDelaySurround(ref DelayState state, Span<IntPtr> outputBuffers, ReadOnlySpan<IntPtr> inputBuffers, uint sampleCount)
|
||||
{
|
||||
const ushort channelCount = 6;
|
||||
|
||||
Span<float> channelInput = stackalloc float[channelCount];
|
||||
Span<float> delayLineValues = stackalloc float[channelCount];
|
||||
Span<float> temp = stackalloc float[channelCount];
|
||||
|
||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||
float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
|
||||
float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
|
||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||
|
||||
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain , 0.0f , 0.0f , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain,
|
||||
0.0f , delayFeedbackBaseGain , 0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f ,
|
||||
delayFeedbackCrossGain, 0.0f , delayFeedbackBaseGain , delayFeedbackCrossGain, 0.0f , 0.0f ,
|
||||
0.0f , delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain , 0.0f , 0.0f ,
|
||||
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f , 0.0f , delayFeedbackBaseGain , 0.0f ,
|
||||
0.0f , 0.0f , 0.0f , 0.0f , 0.0f , feedbackGain);
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector6 channelInput = new Vector6
|
||||
{
|
||||
channelInput[j] = *((float*)inputBuffers[j] + i) * 64;
|
||||
delayLineValues[j] = state.DelayLines[j].Read();
|
||||
}
|
||||
X = *((float*)inputBuffers[0] + i) * 64,
|
||||
Y = *((float*)inputBuffers[1] + i) * 64,
|
||||
Z = *((float*)inputBuffers[2] + i) * 64,
|
||||
W = *((float*)inputBuffers[3] + i) * 64,
|
||||
V = *((float*)inputBuffers[4] + i) * 64,
|
||||
U = *((float*)inputBuffers[5] + i) * 64
|
||||
};
|
||||
|
||||
temp[0] = channelInput[0] * inGain + (delayLineValues[2] + delayLineValues[4]) * delayFeedbackCrossGain + delayLineValues[0] * delayFeedbackBaseGain;
|
||||
temp[1] = channelInput[1] * inGain + (delayLineValues[4] + delayLineValues[3]) * delayFeedbackCrossGain + delayLineValues[1] * delayFeedbackBaseGain;
|
||||
temp[2] = channelInput[2] * inGain + (delayLineValues[3] + delayLineValues[0]) * delayFeedbackCrossGain + delayLineValues[2] * delayFeedbackBaseGain;
|
||||
temp[3] = channelInput[3] * inGain + (delayLineValues[1] + delayLineValues[2]) * delayFeedbackCrossGain + delayLineValues[3] * delayFeedbackBaseGain;
|
||||
temp[4] = channelInput[4] * inGain + (delayLineValues[0] + delayLineValues[1]) * delayFeedbackCrossGain + delayLineValues[4] * delayFeedbackBaseGain;
|
||||
temp[5] = channelInput[5] * inGain + delayLineValues[5] * delayFeedbackBaseGain;
|
||||
|
||||
for (int j = 0; j < channelCount; j++)
|
||||
Vector6 delayLineValues = new Vector6
|
||||
{
|
||||
float lowPassResult = state.LowPassFeedbackGain * state.LowPassZ[j] + temp[j] * state.LowPassBaseGain;
|
||||
X = state.DelayLines[0].Read(),
|
||||
Y = state.DelayLines[1].Read(),
|
||||
Z = state.DelayLines[2].Read(),
|
||||
W = state.DelayLines[3].Read(),
|
||||
V = state.DelayLines[4].Read(),
|
||||
U = state.DelayLines[5].Read()
|
||||
};
|
||||
|
||||
state.LowPassZ[j] = lowPassResult;
|
||||
state.DelayLines[j].Update(lowPassResult);
|
||||
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
||||
|
||||
*((float*)outputBuffers[j] + i) = (channelInput[j] * dryGain + delayLineValues[j] * outGain) / 64;
|
||||
}
|
||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
|
||||
|
||||
*((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
|
||||
*((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
|
||||
*((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
|
||||
*((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
|
||||
*((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64;
|
||||
*((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
@ -80,6 +80,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -194,7 +199,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
if (isSurround)
|
||||
{
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
|
@ -85,6 +85,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
}
|
||||
|
||||
IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -214,7 +219,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
|
|
|
@ -445,5 +445,39 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||
ToIntSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (!isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort backLeft = bufferIndices[2];
|
||||
ushort backRight = bufferIndices[3];
|
||||
ushort frontCenter = bufferIndices[4];
|
||||
ushort lowFrequency = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = frontCenter;
|
||||
bufferIndices[3] = lowFrequency;
|
||||
bufferIndices[4] = backLeft;
|
||||
bufferIndices[5] = backRight;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort frontCenter = bufferIndices[2];
|
||||
ushort lowFrequency = bufferIndices[3];
|
||||
ushort backLeft = bufferIndices[4];
|
||||
ushort backRight = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = backLeft;
|
||||
bufferIndices[3] = backRight;
|
||||
bufferIndices[4] = frontCenter;
|
||||
bufferIndices[5] = lowFrequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
{
|
||||
|
@ -43,7 +44,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
{
|
||||
DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax);
|
||||
DelayLines[i].SetDelay(parameter.DelayTime);
|
||||
LowPassZ[0] = 0;
|
||||
}
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
|
@ -69,5 +69,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision);
|
||||
LowPassBaseGain = 1.0f - LowPassFeedbackGain;
|
||||
}
|
||||
|
||||
public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount)
|
||||
{
|
||||
for (int i = 0; i < channelCount; i++)
|
||||
{
|
||||
float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain;
|
||||
|
||||
LowPassZ[i] = lowPassResult;
|
||||
DelayLines[i].Update(lowPassResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
public DecayDelay[] DecayDelays1 { get; }
|
||||
public DecayDelay[] DecayDelays2 { get; }
|
||||
public IDelayLine PreDelayLine { get; }
|
||||
public IDelayLine BackLeftDelayLine { get; }
|
||||
public IDelayLine FrontCenterDelayLine { get; }
|
||||
public float DryGain { get; private set; }
|
||||
public uint[] EarlyDelayTime { get; private set; }
|
||||
public float PreviousPreDelayValue { get; set; }
|
||||
|
@ -69,7 +69,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
}
|
||||
|
||||
PreDelayLine = new DelayLine3d(sampleRate, 400);
|
||||
BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
FrontCenterDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
public DelayLine[] FdnDelayLines { get; }
|
||||
public DecayDelay[] DecayDelays { get; }
|
||||
public DelayLine PreDelayLine { get; }
|
||||
public DelayLine BackLeftDelayLine { get; }
|
||||
public DelayLine FrontCenterDelayLine { get; }
|
||||
public uint[] EarlyDelayTime { get; }
|
||||
public float[] EarlyGain { get; }
|
||||
public uint PreDelayLineDelayTime { get; private set; }
|
||||
|
@ -149,7 +149,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
|||
}
|
||||
|
||||
PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
|
||||
BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
|
|
@ -363,6 +363,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
case 4:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
case 5:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
|
||||
}
|
||||
|
|
|
@ -107,10 +107,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <remarks>This was added in system update 13.0.0</remarks>
|
||||
public const int Revision10 = 10 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV11:
|
||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 14.0.0</remarks>
|
||||
public const int Revision11 = 11 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// Last revision supported by the implementation.
|
||||
/// </summary>
|
||||
public const int LastRevision = Revision10;
|
||||
public const int LastRevision = Revision11;
|
||||
|
||||
/// <summary>
|
||||
/// Target revision magic supported by the implementation.
|
||||
|
@ -366,12 +374,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer support new channel resource mapping for 5.1.</returns>
|
||||
public bool IsNewEffectChannelMappingSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||
/// </summary>
|
||||
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
|
||||
public int GetCommandProcessingTimeEstimatorVersion()
|
||||
{
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
|
||||
{
|
||||
return 4;
|
||||
|
|
|
@ -336,11 +336,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="isLongSizePreDelaySupported">If set to true, the long size pre-delay is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
@ -357,11 +358,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
@ -379,11 +381,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
|
|
@ -483,31 +483,31 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId)
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Delay);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported)
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId)
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb3d);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
|
||||
|
@ -650,13 +650,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId);
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported);
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId);
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
|
||||
|
|
|
@ -198,7 +198,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
return (uint)1853.2f;
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
public virtual uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
|
@ -272,7 +272,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
public virtual uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
|
@ -346,7 +346,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
}
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
public virtual uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
//
|
||||
// Copyright (c) 2019-2022 Ryujinx
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 5. (added with REV11)
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4
|
||||
{
|
||||
public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
|
||||
|
||||
public override uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 8929;
|
||||
case 2:
|
||||
return 25501;
|
||||
case 4:
|
||||
return 47760;
|
||||
case 6:
|
||||
return 82203;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)1295.20f;
|
||||
case 2:
|
||||
return (uint)1213.60f;
|
||||
case 4:
|
||||
return (uint)942.03f;
|
||||
case 6:
|
||||
return (uint)1001.6f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 11941;
|
||||
case 2:
|
||||
return 37197;
|
||||
case 4:
|
||||
return 69750;
|
||||
case 6:
|
||||
return 12004;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)997.67f;
|
||||
case 2:
|
||||
return (uint)977.63f;
|
||||
case 4:
|
||||
return (uint)792.31f;
|
||||
case 6:
|
||||
return (uint)875.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 81475;
|
||||
case 2:
|
||||
return 84975;
|
||||
case 4:
|
||||
return 91625;
|
||||
case 6:
|
||||
return 95332;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)536.30f;
|
||||
case 2:
|
||||
return (uint)588.80f;
|
||||
case 4:
|
||||
return (uint)643.70f;
|
||||
case 6:
|
||||
return (uint)706.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 120170;
|
||||
case 2:
|
||||
return 125260;
|
||||
case 4:
|
||||
return 135750;
|
||||
case 6:
|
||||
return 141130;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)617.64f;
|
||||
case 2:
|
||||
return (uint)659.54f;
|
||||
case 4:
|
||||
return (uint)711.44f;
|
||||
case 6:
|
||||
return (uint)778.07f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 116750;
|
||||
case 2:
|
||||
return 125910;
|
||||
case 4:
|
||||
return 146340;
|
||||
case 6:
|
||||
return 165810;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 735;
|
||||
case 2:
|
||||
return (uint)766.62f;
|
||||
case 4:
|
||||
return (uint)834.07f;
|
||||
case 6:
|
||||
return (uint)875.44f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 170290;
|
||||
case 2:
|
||||
return 183880;
|
||||
case 4:
|
||||
return 214700;
|
||||
case 6:
|
||||
return 243850;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)508.47f;
|
||||
case 2:
|
||||
return (uint)582.45f;
|
||||
case 4:
|
||||
return (uint)626.42f;
|
||||
case 6:
|
||||
return (uint)682.47f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs
Normal file
71
Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Matrix2x2
|
||||
{
|
||||
public float M11;
|
||||
public float M12;
|
||||
public float M21;
|
||||
public float M22;
|
||||
|
||||
public Matrix2x2(float m11, float m12,
|
||||
float m21, float m22)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
|
||||
M21 = m21;
|
||||
M22 = m22;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 + value2.M11;
|
||||
m.M12 = value1.M12 + value2.M12;
|
||||
m.M21 = value1.M21 + value2.M21;
|
||||
m.M22 = value1.M22 + value2.M22;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator -(Matrix2x2 value1, float value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 - value2;
|
||||
m.M12 = value1.M12 - value2;
|
||||
m.M21 = value1.M21 - value2;
|
||||
m.M22 = value1.M22 - value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator *(Matrix2x2 value1, float value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
m.M11 = value1.M11 * value2;
|
||||
m.M12 = value1.M12 * value2;
|
||||
m.M21 = value1.M21 * value2;
|
||||
m.M22 = value1.M22 * value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2)
|
||||
{
|
||||
Matrix2x2 m;
|
||||
|
||||
// First row
|
||||
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
97
Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs
Normal file
97
Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Matrix6x6
|
||||
{
|
||||
public float M11;
|
||||
public float M12;
|
||||
public float M13;
|
||||
public float M14;
|
||||
public float M15;
|
||||
public float M16;
|
||||
|
||||
public float M21;
|
||||
public float M22;
|
||||
public float M23;
|
||||
public float M24;
|
||||
public float M25;
|
||||
public float M26;
|
||||
|
||||
public float M31;
|
||||
public float M32;
|
||||
public float M33;
|
||||
public float M34;
|
||||
public float M35;
|
||||
public float M36;
|
||||
|
||||
public float M41;
|
||||
public float M42;
|
||||
public float M43;
|
||||
public float M44;
|
||||
public float M45;
|
||||
public float M46;
|
||||
|
||||
public float M51;
|
||||
public float M52;
|
||||
public float M53;
|
||||
public float M54;
|
||||
public float M55;
|
||||
public float M56;
|
||||
|
||||
public float M61;
|
||||
public float M62;
|
||||
public float M63;
|
||||
public float M64;
|
||||
public float M65;
|
||||
public float M66;
|
||||
|
||||
public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16,
|
||||
float m21, float m22, float m23, float m24, float m25, float m26,
|
||||
float m31, float m32, float m33, float m34, float m35, float m36,
|
||||
float m41, float m42, float m43, float m44, float m45, float m46,
|
||||
float m51, float m52, float m53, float m54, float m55, float m56,
|
||||
float m61, float m62, float m63, float m64, float m65, float m66)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
M13 = m13;
|
||||
M14 = m14;
|
||||
M15 = m15;
|
||||
M16 = m16;
|
||||
|
||||
M21 = m21;
|
||||
M22 = m22;
|
||||
M23 = m23;
|
||||
M24 = m24;
|
||||
M25 = m25;
|
||||
M26 = m26;
|
||||
|
||||
M31 = m31;
|
||||
M32 = m32;
|
||||
M33 = m33;
|
||||
M34 = m34;
|
||||
M35 = m35;
|
||||
M36 = m36;
|
||||
|
||||
M41 = m41;
|
||||
M42 = m42;
|
||||
M43 = m43;
|
||||
M44 = m44;
|
||||
M45 = m45;
|
||||
M46 = m46;
|
||||
|
||||
M51 = m51;
|
||||
M52 = m52;
|
||||
M53 = m53;
|
||||
M54 = m54;
|
||||
M55 = m55;
|
||||
M56 = m56;
|
||||
|
||||
M61 = m61;
|
||||
M62 = m62;
|
||||
M63 = m63;
|
||||
M64 = m64;
|
||||
M65 = m65;
|
||||
M66 = m66;
|
||||
}
|
||||
}
|
||||
}
|
45
Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs
Normal file
45
Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using Ryujinx.Audio.Renderer.Utils.Math;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp
|
||||
{
|
||||
static class MatrixHelper
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2)
|
||||
{
|
||||
return new Vector6
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U,
|
||||
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U,
|
||||
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U,
|
||||
V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U,
|
||||
U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U,
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2)
|
||||
{
|
||||
return new Vector4
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W,
|
||||
Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W,
|
||||
W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2)
|
||||
{
|
||||
return new Vector2
|
||||
{
|
||||
X = value2.M11 * value1.X + value2.M12 * value1.Y,
|
||||
Y = value2.M21 * value1.X + value2.M22 * value1.Y,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
56
Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs
Normal file
56
Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Utils.Math
|
||||
{
|
||||
record struct Vector6
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
public float Z;
|
||||
public float W;
|
||||
public float V;
|
||||
public float U;
|
||||
|
||||
public Vector6(float value) : this(value, value, value, value, value, value)
|
||||
{
|
||||
}
|
||||
|
||||
public Vector6(float x, float y, float z, float w, float v, float u)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
W = w;
|
||||
V = v;
|
||||
U = u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator +(Vector6 left, Vector6 right)
|
||||
{
|
||||
return new Vector6(left.X + right.X,
|
||||
left.Y + right.Y,
|
||||
left.Z + right.Z,
|
||||
left.W + right.W,
|
||||
left.V + right.V,
|
||||
left.U + right.U);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator *(Vector6 left, Vector6 right)
|
||||
{
|
||||
return new Vector6(left.X * right.X,
|
||||
left.Y * right.Y,
|
||||
left.Z * right.Z,
|
||||
left.W * right.W,
|
||||
left.V * right.V,
|
||||
left.U * right.U);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector6 operator *(Vector6 left, float right)
|
||||
{
|
||||
return left * new Vector6(right);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
|
@ -11,6 +12,23 @@ namespace Ryujinx.Common.System
|
|||
[DllImport("user32.dll")]
|
||||
private static extern bool SetProcessDPIAware();
|
||||
|
||||
private const string X11LibraryName = "libX11.so.6";
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern IntPtr XOpenDisplay(string display);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern IntPtr XGetDefault(IntPtr display, string program, string option);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XDisplayWidth(IntPtr display, int screenNumber);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XDisplayWidthMM(IntPtr display, int screenNumber);
|
||||
|
||||
[DllImport(X11LibraryName)]
|
||||
private static extern int XCloseDisplay(IntPtr display);
|
||||
|
||||
private static readonly double _standardDpiScale = 96.0;
|
||||
private static readonly double _maxScaleFactor = 1.25;
|
||||
|
||||
|
@ -36,9 +54,29 @@ namespace Ryujinx.Common.System
|
|||
{
|
||||
userDpiScale = Graphics.FromHwnd(IntPtr.Zero).DpiX;
|
||||
}
|
||||
else
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// TODO: Linux support
|
||||
string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower();
|
||||
|
||||
if (xdgSessionType == null || xdgSessionType == "x11")
|
||||
{
|
||||
IntPtr display = XOpenDisplay(null);
|
||||
string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi"));
|
||||
if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale))
|
||||
{
|
||||
userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0);
|
||||
}
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
else if (xdgSessionType == "wayland")
|
||||
{
|
||||
// TODO
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct Capabilities
|
||||
{
|
||||
public readonly TargetApi Api;
|
||||
public readonly string VendorName;
|
||||
|
||||
public readonly bool HasFrontFacingBug;
|
||||
public readonly bool HasVectorIndexingBug;
|
||||
|
||||
|
@ -24,6 +29,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
public readonly int StorageBufferOffsetAlignment;
|
||||
|
||||
public Capabilities(
|
||||
TargetApi api,
|
||||
string vendorName,
|
||||
bool hasFrontFacingBug,
|
||||
bool hasVectorIndexingBug,
|
||||
bool supportsAstcCompression,
|
||||
|
@ -43,6 +50,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
float maximumSupportedAnisotropy,
|
||||
int storageBufferOffsetAlignment)
|
||||
{
|
||||
Api = api;
|
||||
VendorName = vendorName;
|
||||
HasFrontFacingBug = hasFrontFacingBug;
|
||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||
SupportsAstcCompression = supportsAstcCompression;
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct DepthStencilState
|
||||
{
|
||||
public bool DepthTestEnable { get; }
|
||||
public bool DepthWriteEnable { get; }
|
||||
public bool StencilTestEnable { get; }
|
||||
|
||||
public CompareOp DepthFunc { get; }
|
||||
public CompareOp StencilFrontFunc { get; }
|
||||
public StencilOp StencilFrontSFail { get; }
|
||||
public StencilOp StencilFrontDpPass { get; }
|
||||
public StencilOp StencilFrontDpFail { get; }
|
||||
public CompareOp StencilBackFunc { get; }
|
||||
public StencilOp StencilBackSFail { get; }
|
||||
public StencilOp StencilBackDpPass { get; }
|
||||
public StencilOp StencilBackDpFail { get; }
|
||||
|
||||
public DepthStencilState(
|
||||
bool depthTestEnable,
|
||||
bool depthWriteEnable,
|
||||
bool stencilTestEnable,
|
||||
CompareOp depthFunc,
|
||||
CompareOp stencilFrontFunc,
|
||||
StencilOp stencilFrontSFail,
|
||||
StencilOp stencilFrontDpPass,
|
||||
StencilOp stencilFrontDpFail,
|
||||
CompareOp stencilBackFunc,
|
||||
StencilOp stencilBackSFail,
|
||||
StencilOp stencilBackDpPass,
|
||||
StencilOp stencilBackDpFail)
|
||||
{
|
||||
DepthTestEnable = depthTestEnable;
|
||||
DepthWriteEnable = depthWriteEnable;
|
||||
StencilTestEnable = stencilTestEnable;
|
||||
DepthFunc = depthFunc;
|
||||
StencilFrontFunc = stencilFrontFunc;
|
||||
StencilFrontSFail = stencilFrontSFail;
|
||||
StencilFrontDpPass = stencilFrontDpPass;
|
||||
StencilFrontDpFail = stencilFrontDpFail;
|
||||
StencilBackFunc = stencilBackFunc;
|
||||
StencilBackSFail = stencilBackSFail;
|
||||
StencilBackDpPass = stencilBackDpPass;
|
||||
StencilBackDpFail = stencilBackDpFail;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,11 +16,9 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||
|
||||
IShader CompileShader(ShaderStage stage, string code);
|
||||
|
||||
BufferHandle CreateBuffer(int size);
|
||||
|
||||
IProgram CreateProgram(IShader[] shaders, ShaderInfo info);
|
||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||
|
||||
ISampler CreateSampler(SamplerCreateInfo info);
|
||||
ITexture CreateTexture(TextureCreateInfo info, float scale);
|
||||
|
|
|
@ -4,7 +4,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
|
@ -53,8 +52,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
{
|
||||
_lookup[(int)CommandType.Action] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CompileShader] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
CompileShaderCommand.Run(ref GetCommand<CompileShaderCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateBuffer] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateProgram] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
|
@ -98,9 +95,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
_lookup[(int)CommandType.SamplerDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.ShaderDispose] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
ShaderDisposeCommand.Run(ref GetCommand<ShaderDisposeCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.TextureCopyTo] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToScaled] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
enum CommandType : byte
|
||||
{
|
||||
Action,
|
||||
CompileShader,
|
||||
CreateBuffer,
|
||||
CreateProgram,
|
||||
CreateSampler,
|
||||
|
@ -29,8 +28,6 @@
|
|||
|
||||
SamplerDispose,
|
||||
|
||||
ShaderDispose,
|
||||
|
||||
TextureCopyTo,
|
||||
TextureCopyToScaled,
|
||||
TextureCopyToSlice,
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CompileShaderCommand : IGALCommand
|
||||
{
|
||||
public CommandType CommandType => CommandType.CompileShader;
|
||||
private TableRef<ThreadedShader> _shader;
|
||||
|
||||
public void Set(TableRef<ThreadedShader> shader)
|
||||
{
|
||||
_shader = shader;
|
||||
}
|
||||
|
||||
public static void Run(ref CompileShaderCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ThreadedShader shader = command._shader.Get(threaded);
|
||||
shader.EnsureCreated();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct CreateBufferCommand : IGALCommand
|
||||
{
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
||||
{
|
||||
struct PreFrameCommand : IGALCommand
|
||||
{
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Shader
|
||||
{
|
||||
struct ShaderDisposeCommand : IGALCommand
|
||||
{
|
||||
public CommandType CommandType => CommandType.ShaderDispose;
|
||||
private TableRef<ThreadedShader> _shader;
|
||||
|
||||
public void Set(TableRef<ThreadedShader> shader)
|
||||
{
|
||||
_shader = shader;
|
||||
}
|
||||
|
||||
public static void Run(ref ShaderDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
command._shader.Get(threaded).Base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
|
|||
{
|
||||
public ThreadedProgram Threaded { get; set; }
|
||||
|
||||
private IShader[] _shaders;
|
||||
private ShaderSource[] _shaders;
|
||||
private ShaderInfo _info;
|
||||
|
||||
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, ShaderInfo info)
|
||||
public SourceProgramRequest(ThreadedProgram program, ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
Threaded = program;
|
||||
|
||||
|
@ -19,14 +19,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
|
|||
|
||||
public IProgram Create(IRenderer renderer)
|
||||
{
|
||||
IShader[] shaders = _shaders.Select(shader =>
|
||||
{
|
||||
var threaded = (ThreadedShader)shader;
|
||||
threaded?.EnsureCreated();
|
||||
return threaded?.Base;
|
||||
}).ToArray();
|
||||
|
||||
return renderer.CreateProgram(shaders, _info);
|
||||
return renderer.CreateProgram(_shaders, _info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Shader;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||
{
|
||||
class ThreadedShader : IShader
|
||||
{
|
||||
private ThreadedRenderer _renderer;
|
||||
private ShaderStage _stage;
|
||||
private string _code;
|
||||
|
||||
public IShader Base;
|
||||
|
||||
public ThreadedShader(ThreadedRenderer renderer, ShaderStage stage, string code)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
||||
_stage = stage;
|
||||
_code = code;
|
||||
}
|
||||
|
||||
internal void EnsureCreated()
|
||||
{
|
||||
if (_code != null && Base == null)
|
||||
{
|
||||
Base = _renderer.BaseRenderer.CompileShader(_stage, _code);
|
||||
_code = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderer.New<ShaderDisposeCommand>().Set(new TableRef<ThreadedShader>(_renderer, this));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
|
|
|
@ -250,15 +250,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
}
|
||||
}
|
||||
|
||||
public IShader CompileShader(ShaderStage stage, string code)
|
||||
{
|
||||
var shader = new ThreadedShader(this, stage, code);
|
||||
New<CompileShaderCommand>().Set(Ref(shader));
|
||||
QueueCommand();
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
{
|
||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||
|
@ -268,7 +259,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||
return handle;
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
|
||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
var program = new ThreadedProgram(this);
|
||||
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
|
||||
|
|
29
Ryujinx.Graphics.GAL/ShaderSource.cs
Normal file
29
Ryujinx.Graphics.GAL/ShaderSource.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct ShaderSource
|
||||
{
|
||||
public string Code { get; }
|
||||
public byte[] BinaryCode { get; }
|
||||
public ShaderStage Stage { get; }
|
||||
public TargetLanguage Language { get; }
|
||||
|
||||
public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
|
||||
{
|
||||
Code = code;
|
||||
BinaryCode = binaryCode;
|
||||
Stage = stage;
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
|
||||
{
|
||||
}
|
||||
|
||||
public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.GAL
|
|||
Texture2DArray,
|
||||
Texture2DMultisample,
|
||||
Texture2DMultisampleArray,
|
||||
Rectangle,
|
||||
Cubemap,
|
||||
CubemapArray,
|
||||
TextureBuffer
|
||||
|
|
|
@ -124,24 +124,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
|
||||
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
|
||||
|
||||
GpuAccessorState gas = new GpuAccessorState(
|
||||
GpuChannelPoolState poolState = new GpuChannelPoolState(
|
||||
texturePoolGpuVa,
|
||||
_state.State.SetTexHeaderPoolCMaximumIndex,
|
||||
_state.State.SetBindlessTextureConstantBufferSlotSelect,
|
||||
false,
|
||||
PrimitiveTopology.Points,
|
||||
default);
|
||||
_state.State.SetBindlessTextureConstantBufferSlotSelect);
|
||||
|
||||
ShaderBundle cs = memoryManager.Physical.ShaderCache.GetComputeShader(
|
||||
_channel,
|
||||
gas,
|
||||
shaderGpuVa,
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
qmd.CtaThreadDimension0,
|
||||
qmd.CtaThreadDimension1,
|
||||
qmd.CtaThreadDimension2,
|
||||
localMemorySize,
|
||||
sharedMemorySize);
|
||||
|
||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
_channel.TextureManager.SetComputeSamplerPool(samplerPoolGpuVa, _state.State.SetTexSamplerPoolCMaximumIndex, qmd.SamplerIndex);
|
||||
|
|
|
@ -525,7 +525,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
int scissorW = screenScissorState.Width;
|
||||
int scissorH = screenScissorState.Height;
|
||||
|
||||
if (clearAffectedByScissor)
|
||||
if (clearAffectedByScissor && _state.State.ScissorState[0].Enable)
|
||||
{
|
||||
ref var scissorState = ref _state.State.ScissorState[0];
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Shader;
|
|||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
|
@ -20,6 +19,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
public const int RasterizerStateIndex = 1;
|
||||
public const int ScissorStateIndex = 2;
|
||||
public const int VertexBufferStateIndex = 3;
|
||||
public const int PrimitiveRestartStateIndex = 4;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
@ -29,11 +29,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
private readonly StateUpdateTracker<ThreedClassState> _updateTracker;
|
||||
|
||||
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
||||
private ShaderSpecializationState _shaderSpecState;
|
||||
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
|
||||
private bool _prevDrawIndexed;
|
||||
private IndexType _prevIndexType;
|
||||
private uint _prevFirstVertex;
|
||||
private bool _prevTfEnable;
|
||||
|
||||
/// <summary>
|
||||
|
@ -75,6 +78,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
|
@ -140,8 +147,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
nameof(ThreedClassState.PointSpriteEnable),
|
||||
nameof(ThreedClassState.PointCoordReplace)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
||||
nameof(ThreedClassState.IndexBufferState),
|
||||
nameof(ThreedClassState.IndexBufferCount)),
|
||||
|
@ -190,6 +195,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update()
|
||||
{
|
||||
// If any state that the shader depends on changed,
|
||||
// then we may need to compile/bind a different version
|
||||
// of the shader for the new state.
|
||||
if (_shaderSpecState != null)
|
||||
{
|
||||
if (!_shaderSpecState.MatchesGraphics(_channel, GetPoolState()))
|
||||
{
|
||||
ForceShaderUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// The vertex buffer size is calculated using a different
|
||||
// method when doing indexed draws, so we need to make sure
|
||||
// to update the vertex buffers if we are doing a regular
|
||||
|
@ -197,9 +213,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
if (_drawState.DrawIndexed != _prevDrawIndexed)
|
||||
{
|
||||
_updateTracker.ForceDirty(VertexBufferStateIndex);
|
||||
|
||||
// If PrimitiveRestartDrawArrays is false and this is a non-indexed draw, we need to ensure primitive restart is disabled.
|
||||
// If PrimitiveRestartDrawArrays is false and this is a indexed draw, we need to ensure primitive restart enable matches GPU state.
|
||||
// If PrimitiveRestartDrawArrays is true, then primitive restart enable should always match GPU state.
|
||||
// That is because "PrimitiveRestartDrawArrays" is not configurable on the backend, it is always
|
||||
// true on OpenGL and always false on Vulkan.
|
||||
if (!_state.State.PrimitiveRestartDrawArrays && _state.State.PrimitiveRestartState.Enable)
|
||||
{
|
||||
_updateTracker.ForceDirty(PrimitiveRestartStateIndex);
|
||||
}
|
||||
|
||||
_prevDrawIndexed = _drawState.DrawIndexed;
|
||||
}
|
||||
|
||||
// In some cases, the index type is also used to guess the
|
||||
// vertex buffer size, so we must update it if the type changed too.
|
||||
if (_drawState.DrawIndexed &&
|
||||
(_prevIndexType != _state.State.IndexBufferState.Type ||
|
||||
_prevFirstVertex != _state.State.FirstVertex))
|
||||
{
|
||||
_updateTracker.ForceDirty(VertexBufferStateIndex);
|
||||
_prevIndexType = _state.State.IndexBufferState.Type;
|
||||
_prevFirstVertex = _state.State.FirstVertex;
|
||||
}
|
||||
|
||||
bool tfEnable = _state.State.TfEnable;
|
||||
|
||||
if (!tfEnable && _prevTfEnable)
|
||||
|
@ -816,8 +854,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
private void UpdatePrimitiveRestartState()
|
||||
{
|
||||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(primitiveRestart.Enable, primitiveRestart.Index);
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -852,6 +891,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
private void UpdateVertexBufferState()
|
||||
{
|
||||
IndexType indexType = _state.State.IndexBufferState.Type;
|
||||
bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort;
|
||||
|
||||
_drawState.IsAnyVbInstanced = false;
|
||||
|
||||
for (int index = 0; index < Constants.TotalVertexBuffers; index++)
|
||||
|
@ -883,12 +925,27 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
{
|
||||
// This size may be (much) larger than the real vertex buffer size.
|
||||
// Avoid calculating it this way, unless we don't have any other option.
|
||||
|
||||
size = endAddress.Pack() - address + 1;
|
||||
|
||||
if (stride > 0 && indexTypeSmall)
|
||||
{
|
||||
// If the index type is a small integer type, then we might be still able
|
||||
// to reduce the vertex buffer size based on the maximum possible index value.
|
||||
|
||||
ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL;
|
||||
|
||||
maxVertexBufferSize += _state.State.FirstVertex;
|
||||
maxVertexBufferSize *= (uint)stride;
|
||||
|
||||
size = Math.Min(size, maxVertexBufferSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-indexed draws, we can guess the size from the vertex count
|
||||
// and stride.
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
|
@ -1019,108 +1076,127 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
/// </summary>
|
||||
private void UpdateShaderState()
|
||||
{
|
||||
var shaderCache = _channel.MemoryManager.Physical.ShaderCache;
|
||||
|
||||
_vtgWritesRtLayer = false;
|
||||
|
||||
ShaderAddresses addresses = new ShaderAddresses();
|
||||
|
||||
Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
|
||||
|
||||
Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
|
||||
Span<ulong> addressesSpan = addresses.AsSpan();
|
||||
|
||||
ulong baseAddress = _state.State.ShaderBaseAddress.Pack();
|
||||
|
||||
for (int index = 0; index < 6; index++)
|
||||
{
|
||||
var shader = _state.State.ShaderState[index];
|
||||
|
||||
if (!shader.UnpackEnable() && index != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addressesArray[index] = baseAddress + shader.Offset;
|
||||
addressesSpan[index] = baseAddress + shader.Offset;
|
||||
}
|
||||
|
||||
GpuAccessorState gas = new GpuAccessorState(
|
||||
_state.State.TexturePoolState.Address.Pack(),
|
||||
_state.State.TexturePoolState.MaximumId,
|
||||
(int)_state.State.TextureBufferIndex,
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode);
|
||||
GpuChannelPoolState poolState = GetPoolState();
|
||||
GpuChannelGraphicsState graphicsState = GetGraphicsState();
|
||||
|
||||
ShaderBundle gs = _channel.MemoryManager.Physical.ShaderCache.GetGraphicsShader(ref _state.State, _channel, gas, addresses);
|
||||
CachedShaderProgram gs = shaderCache.GetGraphicsShader(ref _state.State, _channel, poolState, graphicsState, addresses);
|
||||
|
||||
_shaderSpecState = gs.SpecializationState;
|
||||
|
||||
byte oldVsClipDistancesWritten = _vsClipDistancesWritten;
|
||||
|
||||
_drawState.VsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
|
||||
_vsClipDistancesWritten = gs.Shaders[0]?.Info.ClipDistancesWritten ?? 0;
|
||||
_vtgWritesRtLayer = false;
|
||||
_drawState.VsUsesInstanceId = gs.Shaders[1]?.Info.UsesInstanceId ?? false;
|
||||
_vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0;
|
||||
|
||||
if (oldVsClipDistancesWritten != _vsClipDistancesWritten)
|
||||
{
|
||||
UpdateUserClipState();
|
||||
}
|
||||
|
||||
for (int stage = 0; stage < Constants.ShaderStages; stage++)
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
ShaderProgramInfo info = gs.Shaders[stage]?.Info;
|
||||
|
||||
_currentProgramInfo[stage] = info;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
_channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
|
||||
_channel.TextureManager.RentGraphicsImageBindings(stage, 0);
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
UpdateStageBindings(stageIndex, gs.Shaders[stageIndex + 1]?.Info);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||
}
|
||||
|
||||
private void UpdateStageBindings(int stage, ShaderProgramInfo info)
|
||||
{
|
||||
_currentProgramInfo[stage] = info;
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
_channel.TextureManager.RentGraphicsTextureBindings(stage, 0);
|
||||
_channel.TextureManager.RentGraphicsImageBindings(stage, 0);
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Span<TextureBindingInfo> textureBindings = _channel.TextureManager.RentGraphicsTextureBindings(stage, info.Textures.Count);
|
||||
|
||||
if (info.UsesRtLayer)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var descriptor = info.Textures[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
|
||||
textureBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
TextureBindingInfo[] imageBindings = _channel.TextureManager.RentGraphicsImageBindings(stage, info.Images.Count);
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var descriptor = info.Images[index];
|
||||
|
||||
Target target = ShaderTexture.GetTarget(descriptor.Type);
|
||||
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
||||
|
||||
imageBindings[index] = new TextureBindingInfo(
|
||||
target,
|
||||
format,
|
||||
descriptor.Binding,
|
||||
descriptor.CbufSlot,
|
||||
descriptor.HandleIndex,
|
||||
descriptor.Flags);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
||||
_channel.BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
||||
}
|
||||
|
||||
private GpuChannelPoolState GetPoolState()
|
||||
{
|
||||
return new GpuChannelPoolState(
|
||||
_state.State.TexturePoolState.Address.Pack(),
|
||||
_state.State.TexturePoolState.MaximumId,
|
||||
(int)_state.State.TextureBufferIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GPU channel state for shader creation or compatibility verification.
|
||||
/// </summary>
|
||||
/// <returns>Current GPU channel state</returns>
|
||||
private GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
return new GpuChannelGraphicsState(
|
||||
_state.State.EarlyZForce,
|
||||
_drawState.Topology,
|
||||
_state.State.TessMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the shaders to be rebound on the next draw.
|
||||
/// </summary>
|
||||
|
|
|
@ -730,7 +730,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||
public int PatchVertices;
|
||||
public fixed uint ReservedDD0[4];
|
||||
public uint TextureBarrier;
|
||||
public fixed uint ReservedDE4[7];
|
||||
public uint WatchdogTimer;
|
||||
public Boolean32 PrimitiveRestartDrawArrays;
|
||||
public fixed uint ReservedDEC[5];
|
||||
public Array16<ScissorState> ScissorState;
|
||||
public fixed uint ReservedF00[21];
|
||||
public StencilBackMasks StencilBackMasks;
|
||||
|
|
|
@ -238,13 +238,13 @@ namespace Ryujinx.Graphics.Gpu
|
|||
/// <summary>
|
||||
/// Initialize the GPU shader cache.
|
||||
/// </summary>
|
||||
public void InitializeShaderCache()
|
||||
public void InitializeShaderCache(CancellationToken cancellationToken)
|
||||
{
|
||||
HostInitalized.WaitOne();
|
||||
|
||||
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
|
||||
{
|
||||
physicalMemory.ShaderCache.Initialize();
|
||||
physicalMemory.ShaderCache.Initialize(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1136,17 +1136,33 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// <param name="range">Texture view physical memory ranges</param>
|
||||
/// <param name="layerSize">Layer size on the given texture</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <param name="allowMs">Indicates that multisample textures are allowed to match non-multisample requested textures</param>
|
||||
/// <param name="firstLayer">Texture view initial layer on this texture</param>
|
||||
/// <param name="firstLevel">Texture view first mipmap level on this texture</param>
|
||||
/// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
|
||||
public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, bool allowMs, out int firstLayer, out int firstLevel)
|
||||
{
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
|
||||
if (result != TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
bool msTargetCompatible = false;
|
||||
|
||||
if (allowMs)
|
||||
{
|
||||
msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
|
||||
}
|
||||
|
||||
if (!msTargetCompatible)
|
||||
{
|
||||
result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
|
||||
{
|
||||
|
@ -1156,11 +1172,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
|
||||
{
|
||||
result = TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
firstLayer = 0;
|
||||
|
|
|
@ -542,7 +542,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
for (int index = 0; index < overlapsCount; index++)
|
||||
{
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(
|
||||
info,
|
||||
range.Value,
|
||||
sizeInfo.LayerSize,
|
||||
_context.Capabilities,
|
||||
flags.HasFlag(TextureSearchFlags.ForCopy),
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
if (overlapCompatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
|
@ -650,7 +657,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Texture overlap = _textureOverlaps[index];
|
||||
bool overlapInCache = overlap.CacheNode != null;
|
||||
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, _context.Capabilities, out int firstLayer, out int firstLevel);
|
||||
TextureViewCompatibility compatibility = texture.IsViewCompatible(
|
||||
overlap.Info,
|
||||
overlap.Range,
|
||||
overlap.LayerSize,
|
||||
_context.Capabilities,
|
||||
false,
|
||||
out int firstLayer,
|
||||
out int firstLevel);
|
||||
|
||||
if (overlap.IsView && compatibility == TextureViewCompatibility.Full)
|
||||
{
|
||||
|
@ -1000,20 +1014,34 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
depthOrLayers = info.DepthOrLayers;
|
||||
}
|
||||
|
||||
// 2D and 2D multisample textures are not considered compatible.
|
||||
// This specific case is required for copies, where the source texture might be multisample.
|
||||
// In this case, we inherit the parent texture multisample state.
|
||||
Target target = info.Target;
|
||||
int samplesInX = info.SamplesInX;
|
||||
int samplesInY = info.SamplesInY;
|
||||
|
||||
if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample)
|
||||
{
|
||||
target = Target.Texture2DMultisample;
|
||||
samplesInX = parent.Info.SamplesInX;
|
||||
samplesInY = parent.Info.SamplesInY;
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
info.GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
info.Levels,
|
||||
info.SamplesInX,
|
||||
info.SamplesInY,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
info.Stride,
|
||||
info.IsLinear,
|
||||
info.GobBlocksInY,
|
||||
info.GobBlocksInZ,
|
||||
info.GobBlocksInTileX,
|
||||
info.Target,
|
||||
target,
|
||||
info.FormatInfo,
|
||||
info.DepthStencilMode,
|
||||
info.SwizzleR,
|
||||
|
|
|
@ -115,6 +115,73 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only span of data from GPU mapped memory, up to the entire range specified,
|
||||
/// or the last mapped page if the range is not fully mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address where the data is located</param>
|
||||
/// <param name="size">Size of the data</param>
|
||||
/// <param name="tracked">True if read tracking is triggered on the span</param>
|
||||
/// <returns>The span of the data at the specified memory location</returns>
|
||||
public ReadOnlySpan<byte> GetSpanMapped(ulong va, int size, bool tracked = false)
|
||||
{
|
||||
bool isContiguous = true;
|
||||
int mappedSize;
|
||||
|
||||
if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va)))
|
||||
{
|
||||
ulong endVa = va + (ulong)size;
|
||||
ulong endVaAligned = (endVa + PageMask) & ~PageMask;
|
||||
ulong currentVa = va & ~PageMask;
|
||||
|
||||
int pages = (int)((endVaAligned - currentVa) / PageSize);
|
||||
|
||||
for (int page = 0; page < pages - 1; page++)
|
||||
{
|
||||
ulong nextVa = currentVa + PageSize;
|
||||
ulong nextPa = Translate(nextVa);
|
||||
|
||||
if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (Translate(currentVa) + PageSize != nextPa)
|
||||
{
|
||||
isContiguous = false;
|
||||
}
|
||||
|
||||
currentVa += PageSize;
|
||||
}
|
||||
|
||||
currentVa += PageSize;
|
||||
|
||||
if (currentVa > endVa)
|
||||
{
|
||||
currentVa = endVa;
|
||||
}
|
||||
|
||||
mappedSize = (int)(currentVa - va);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
}
|
||||
|
||||
if (isContiguous)
|
||||
{
|
||||
return Physical.GetSpan(Translate(va), mappedSize, tracked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> data = new byte[mappedSize];
|
||||
|
||||
ReadImpl(va, data, tracked);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a possibly non-contiguous region of GPU mapped memory.
|
||||
/// </summary>
|
||||
|
|
|
@ -341,9 +341,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given address is mapped on CPU memory.
|
||||
/// Checks if a given memory page is mapped.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the page to check</param>
|
||||
/// <param name="address">CPU virtual address of the page</param>
|
||||
/// <returns>True if mapped, false otherwise</returns>
|
||||
public bool IsMapped(ulong address)
|
||||
{
|
||||
|
|
|
@ -2,11 +2,8 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -20,70 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// </summary>
|
||||
static class CacheHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Try to read the manifest header from a given file path.
|
||||
/// </summary>
|
||||
/// <param name="manifestPath">The path to the manifest file</param>
|
||||
/// <param name="header">The manifest header read</param>
|
||||
/// <returns>Return true if the manifest header was read</returns>
|
||||
public static bool TryReadManifestHeader(string manifestPath, out CacheManifestHeader header)
|
||||
{
|
||||
header = default;
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||
|
||||
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to read the manifest from a given file path.
|
||||
/// </summary>
|
||||
/// <param name="manifestPath">The path to the manifest file</param>
|
||||
/// <param name="graphicsApi">The graphics api used by the cache</param>
|
||||
/// <param name="hashType">The hash type of the cache</param>
|
||||
/// <param name="header">The manifest header read</param>
|
||||
/// <param name="entries">The entries read from the cache manifest</param>
|
||||
/// <returns>Return true if the manifest was read</returns>
|
||||
public static bool TryReadManifestFile(string manifestPath, CacheGraphicsApi graphicsApi, CacheHashType hashType, out CacheManifestHeader header, out HashSet<Hash128> entries)
|
||||
{
|
||||
header = default;
|
||||
entries = new HashSet<Hash128>();
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||
|
||||
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||
{
|
||||
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
||||
|
||||
bool isValid = header.IsValid(graphicsApi, hashType, hashTableRaw.Span);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
|
||||
|
||||
foreach (Hash128 hash in hashTable)
|
||||
{
|
||||
entries.Add(hash);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a cache manifest from runtime data.
|
||||
/// </summary>
|
||||
|
@ -246,82 +179,23 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the guest program code for usage while dumping to disk or hash.
|
||||
/// </summary>
|
||||
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||
/// <param name="tfd">The transform feedback descriptors</param>
|
||||
/// <param name="forHashCompute">Used to determine if the guest program code is generated for hashing</param>
|
||||
/// <returns>The guest program code for usage while dumping to disk or hash</returns>
|
||||
private static byte[] ComputeGuestProgramCode(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd, bool forHashCompute = false)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
foreach (GuestShaderCacheEntry cachedShaderEntry in cachedShaderEntries)
|
||||
{
|
||||
if (cachedShaderEntry != null)
|
||||
{
|
||||
// Code (and Code A if present)
|
||||
stream.Write(cachedShaderEntry.Code);
|
||||
|
||||
if (forHashCompute)
|
||||
{
|
||||
// Guest GPU accessor header (only write this for hashes, already present in the header for dumps)
|
||||
writer.WriteStruct(cachedShaderEntry.Header.GpuAccessorHeader);
|
||||
}
|
||||
|
||||
// Texture descriptors
|
||||
foreach (GuestTextureDescriptor textureDescriptor in cachedShaderEntry.TextureDescriptors.Values)
|
||||
{
|
||||
writer.WriteStruct(textureDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform feedback
|
||||
if (tfd != null)
|
||||
{
|
||||
foreach (TransformFeedbackDescriptor transform in tfd)
|
||||
{
|
||||
writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
|
||||
writer.Write(transform.VaryingLocations);
|
||||
}
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a guest hash from shader entries.
|
||||
/// </summary>
|
||||
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||
/// <param name="tfd">The optional transform feedback descriptors</param>
|
||||
/// <returns>A guest hash from shader entries</returns>
|
||||
public static Hash128 ComputeGuestHashFromCache(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd = null)
|
||||
{
|
||||
return XXHash128.ComputeHash(ComputeGuestProgramCode(cachedShaderEntries, tfd, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read transform feedback descriptors from guest.
|
||||
/// </summary>
|
||||
/// <param name="data">The raw guest transform feedback descriptors</param>
|
||||
/// <param name="header">The guest shader program header</param>
|
||||
/// <returns>The transform feedback descriptors read from guest</returns>
|
||||
public static TransformFeedbackDescriptor[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
|
||||
public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
|
||||
{
|
||||
if (header.TransformFeedbackCount != 0)
|
||||
{
|
||||
TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
|
||||
TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
|
||||
|
||||
result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
|
||||
result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
|
||||
|
||||
data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
|
||||
}
|
||||
|
@ -332,205 +206,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds gpu state flags using information from the given gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>The gpu state flags</returns>
|
||||
private static GuestGpuStateFlags GetGpuStateFlags(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
GuestGpuStateFlags flags = 0;
|
||||
|
||||
if (gpuAccessor.QueryEarlyZForce())
|
||||
{
|
||||
flags |= GuestGpuStateFlags.EarlyZForce;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs the tessellation parameters from the gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>The packed tessellation parameters</returns>
|
||||
private static byte GetTessellationModePacked(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
byte value;
|
||||
|
||||
value = (byte)((int)gpuAccessor.QueryTessPatchType() & 3);
|
||||
value |= (byte)(((int)gpuAccessor.QueryTessSpacing() & 3) << 2);
|
||||
|
||||
if (gpuAccessor.QueryTessCw())
|
||||
{
|
||||
value |= 0x10;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
|
||||
/// </summary>
|
||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||
/// <returns>A new instance of <see cref="GuestGpuAccessorHeader"/></returns>
|
||||
public static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
|
||||
{
|
||||
return new GuestGpuAccessorHeader
|
||||
{
|
||||
ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
|
||||
ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
|
||||
ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
|
||||
ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
|
||||
ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
|
||||
PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
|
||||
TessellationModePacked = GetTessellationModePacked(gpuAccessor),
|
||||
StateFlags = GetGpuStateFlags(gpuAccessor)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create guest shader cache entries from the runtime contexts.
|
||||
/// </summary>
|
||||
/// <param name="channel">The GPU channel in use</param>
|
||||
/// <param name="shaderContexts">The runtime contexts</param>
|
||||
/// <returns>Guest shader cahe entries from the runtime contexts</returns>
|
||||
public static GuestShaderCacheEntry[] CreateShaderCacheEntries(GpuChannel channel, ReadOnlySpan<TranslatorContext> shaderContexts)
|
||||
{
|
||||
MemoryManager memoryManager = channel.MemoryManager;
|
||||
|
||||
int startIndex = shaderContexts.Length > 1 ? 1 : 0;
|
||||
|
||||
GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length - startIndex];
|
||||
|
||||
for (int i = startIndex; i < shaderContexts.Length; i++)
|
||||
{
|
||||
TranslatorContext context = shaderContexts[i];
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GpuAccessor gpuAccessor = context.GpuAccessor as GpuAccessor;
|
||||
|
||||
ulong cb1DataAddress;
|
||||
int cb1DataSize = gpuAccessor?.Cb1DataSize ?? 0;
|
||||
|
||||
if (context.Stage == ShaderStage.Compute)
|
||||
{
|
||||
cb1DataAddress = channel.BufferManager.GetComputeUniformBufferAddress(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int stageIndex = context.Stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
ShaderStage.TessellationEvaluation => 2,
|
||||
ShaderStage.Geometry => 3,
|
||||
ShaderStage.Fragment => 4,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, 1);
|
||||
}
|
||||
|
||||
int size = context.Size;
|
||||
|
||||
TranslatorContext translatorContext2 = i == 1 ? shaderContexts[0] : null;
|
||||
|
||||
int sizeA = translatorContext2 != null ? translatorContext2.Size : 0;
|
||||
|
||||
byte[] code = new byte[size + cb1DataSize + sizeA];
|
||||
|
||||
memoryManager.GetSpan(context.Address, size).CopyTo(code);
|
||||
|
||||
if (cb1DataAddress != 0 && cb1DataSize != 0)
|
||||
{
|
||||
memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize));
|
||||
}
|
||||
|
||||
if (translatorContext2 != null)
|
||||
{
|
||||
memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA));
|
||||
}
|
||||
|
||||
GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);
|
||||
|
||||
if (gpuAccessor != null)
|
||||
{
|
||||
gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count;
|
||||
}
|
||||
|
||||
GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader(
|
||||
context.Stage,
|
||||
size + cb1DataSize,
|
||||
sizeA,
|
||||
cb1DataSize,
|
||||
gpuAccessorHeader);
|
||||
|
||||
GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code);
|
||||
|
||||
if (gpuAccessor != null)
|
||||
{
|
||||
foreach (int textureHandle in context.TextureHandlesForCache)
|
||||
{
|
||||
GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle, -1)).ToCache();
|
||||
|
||||
textureDescriptor.Handle = (uint)textureHandle;
|
||||
|
||||
entry.TextureDescriptors.Add(textureHandle, textureDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
entries[i - startIndex] = entry;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a guest shader program.
|
||||
/// </summary>
|
||||
/// <param name="shaderCacheEntries">The entries composing the guest program dump</param>
|
||||
/// <param name="tfd">The transform feedback descriptors in use</param>
|
||||
/// <returns>The resulting guest shader program</returns>
|
||||
public static byte[] CreateGuestProgramDump(GuestShaderCacheEntry[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd = null)
|
||||
{
|
||||
using (MemoryStream resultStream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
|
||||
|
||||
byte transformFeedbackCount = 0;
|
||||
|
||||
if (tfd != null)
|
||||
{
|
||||
transformFeedbackCount = (byte)tfd.Length;
|
||||
}
|
||||
|
||||
// Header
|
||||
resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
|
||||
|
||||
// Write all entries header
|
||||
foreach (GuestShaderCacheEntry entry in shaderCacheEntries)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
resultStreamWriter.WriteStruct(new GuestShaderCacheEntryHeader());
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStreamWriter.WriteStruct(entry.Header);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, write all program code and all transform feedback information.
|
||||
resultStreamWriter.Write(ComputeGuestProgramCode(shaderCacheEntries, tfd));
|
||||
|
||||
return resultStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save temporary files not in archive.
|
||||
/// </summary>
|
||||
|
|
|
@ -47,8 +47,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
|
||||
|
||||
CacheMigration.Run(baseCacheDirectory, graphicsApi, hashType, shaderProvider);
|
||||
|
||||
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
|
||||
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
|
||||
}
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class handling shader cache migrations.
|
||||
/// </summary>
|
||||
static class CacheMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if the given cache version need to recompute its hash.
|
||||
/// </summary>
|
||||
/// <param name="version">The version in use</param>
|
||||
/// <param name="newVersion">The new version after migration</param>
|
||||
/// <returns>True if a hash recompute is needed</returns>
|
||||
public static bool NeedHashRecompute(ulong version, out ulong newVersion)
|
||||
{
|
||||
const ulong TargetBrokenVersion = 1717;
|
||||
const ulong TargetFixedVersion = 1759;
|
||||
|
||||
newVersion = TargetFixedVersion;
|
||||
|
||||
if (version == TargetBrokenVersion)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class StreamZipEntryDataSource : IStaticDataSource
|
||||
{
|
||||
private readonly ZipFile Archive;
|
||||
private readonly ZipEntry Entry;
|
||||
public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
|
||||
{
|
||||
Archive = archive;
|
||||
Entry = entry;
|
||||
}
|
||||
|
||||
public Stream GetSource()
|
||||
{
|
||||
return Archive.GetInputStream(Entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a file with the name of a given hash to another in the cache archive.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive in use</param>
|
||||
/// <param name="oldKey">The old key</param>
|
||||
/// <param name="newKey">The new key</param>
|
||||
private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
|
||||
{
|
||||
ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
|
||||
|
||||
if (oldGuestEntry != null)
|
||||
{
|
||||
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
|
||||
archive.Delete(oldGuestEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recompute all the hashes of a given cache.
|
||||
/// </summary>
|
||||
/// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
|
||||
/// <param name="hostBaseCacheDirectory">The host cache directory path</param>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="hashType">The hash type in use</param>
|
||||
/// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
|
||||
private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion)
|
||||
{
|
||||
string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory);
|
||||
string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory);
|
||||
|
||||
if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries))
|
||||
{
|
||||
CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration...");
|
||||
|
||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||
|
||||
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
|
||||
|
||||
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
|
||||
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
|
||||
|
||||
int programIndex = 0;
|
||||
|
||||
HashSet<Hash128> newEntries = new HashSet<Hash128>();
|
||||
|
||||
foreach (Hash128 oldHash in guestEntries)
|
||||
{
|
||||
byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})");
|
||||
|
||||
if (guestProgram != null)
|
||||
{
|
||||
ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
|
||||
|
||||
TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
|
||||
|
||||
Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd);
|
||||
|
||||
if (newHash != oldHash)
|
||||
{
|
||||
MoveEntry(guestArchive, oldHash, newHash);
|
||||
MoveEntry(hostArchive, oldHash, newHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}");
|
||||
}
|
||||
|
||||
newEntries.Add(newHash);
|
||||
}
|
||||
|
||||
programIndex++;
|
||||
}
|
||||
|
||||
byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries);
|
||||
byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries);
|
||||
|
||||
File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
|
||||
File.WriteAllBytes(hostManifestPath, newHostManifestContent);
|
||||
|
||||
guestArchive.CommitUpdate();
|
||||
hostArchive.CommitUpdate();
|
||||
|
||||
guestArchive.Close();
|
||||
hostArchive.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check and run cache migration if needed.
|
||||
/// </summary>
|
||||
/// <param name="baseCacheDirectory">The base path of the cache</param>
|
||||
/// <param name="graphicsApi">The graphics api in use</param>
|
||||
/// <param name="hashType">The hash type in use</param>
|
||||
/// <param name="shaderProvider">The shader provider name of the cache</param>
|
||||
public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider)
|
||||
{
|
||||
string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
|
||||
string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host");
|
||||
|
||||
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
|
||||
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
|
||||
|
||||
bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
|
||||
|
||||
if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
|
||||
{
|
||||
if (NeedHashRecompute(header.Version, out ulong newVersion))
|
||||
{
|
||||
RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,6 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
|||
SBuffers,
|
||||
Textures,
|
||||
Images,
|
||||
default,
|
||||
Header.UseFlags.HasFlag(UseFlags.InstanceId),
|
||||
Header.UseFlags.HasFlag(UseFlags.RtLayer),
|
||||
Header.ClipDistancesWritten,
|
||||
|
@ -160,7 +161,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
|||
/// <param name="programCode">The host shader program</param>
|
||||
/// <param name="codeHolders">The shaders code holder</param>
|
||||
/// <returns>Raw data of a new host shader cache file</returns>
|
||||
internal static byte[] Create(ReadOnlySpan<byte> programCode, ShaderCodeHolder[] codeHolders)
|
||||
internal static byte[] Create(ReadOnlySpan<byte> programCode, CachedShaderStage[] codeHolders)
|
||||
{
|
||||
HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length);
|
||||
|
||||
|
|
255
Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
Normal file
255
Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs
Normal file
|
@ -0,0 +1,255 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Class handling shader cache migrations.
|
||||
/// </summary>
|
||||
static class Migration
|
||||
{
|
||||
// Last codegen version before the migration to the new cache.
|
||||
private const ulong ShaderCodeGenVersion = 3054;
|
||||
|
||||
/// <summary>
|
||||
/// Migrates from the old cache format to the new one.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param>
|
||||
/// <returns>Number of migrated shaders</returns>
|
||||
public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage)
|
||||
{
|
||||
string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId);
|
||||
string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
|
||||
|
||||
// If the directory does not exist, we have no old cache.
|
||||
// Exist early as the CacheManager constructor will create the directories.
|
||||
if (!Directory.Exists(cacheDirectory))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
|
||||
{
|
||||
CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
|
||||
|
||||
bool isReadOnly = cacheManager.IsReadOnly;
|
||||
|
||||
HashSet<Hash128> invalidEntries = null;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidEntries = new HashSet<Hash128>();
|
||||
}
|
||||
|
||||
ReadOnlySpan<Hash128> guestProgramList = cacheManager.GetGuestProgramList();
|
||||
|
||||
for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
|
||||
{
|
||||
Hash128 key = guestProgramList[programIndex];
|
||||
|
||||
byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key);
|
||||
|
||||
if (guestProgram == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
|
||||
|
||||
ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
|
||||
|
||||
if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == 1);
|
||||
|
||||
GuestShaderCacheEntry entry = cachedShaderEntries[0];
|
||||
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
ShaderStage.Compute,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeX,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeY,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalSizeZ,
|
||||
entry.Header.GpuAccessorHeader.ComputeLocalMemorySize,
|
||||
entry.Header.GpuAccessorHeader.ComputeSharedMemorySize);
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(computeState);
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
0,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
|
||||
CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data);
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shader);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
|
||||
|
||||
TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
|
||||
|
||||
GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
|
||||
|
||||
GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader;
|
||||
|
||||
TessMode tessMode = new TessMode();
|
||||
|
||||
int tessPatchType = accessorHeader.TessellationModePacked & 3;
|
||||
int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3;
|
||||
bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0;
|
||||
|
||||
tessMode.Packed = (uint)tessPatchType;
|
||||
tessMode.Packed |= (uint)(tessSpacing << 4);
|
||||
|
||||
if (tessCw)
|
||||
{
|
||||
tessMode.Packed |= 0x100;
|
||||
}
|
||||
|
||||
PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch
|
||||
{
|
||||
InputTopology.Lines => PrimitiveTopology.Lines,
|
||||
InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
|
||||
InputTopology.Triangles => PrimitiveTopology.Triangles,
|
||||
InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
|
||||
_ => PrimitiveTopology.Points
|
||||
};
|
||||
|
||||
GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState(
|
||||
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
|
||||
topology,
|
||||
tessMode);
|
||||
|
||||
TransformFeedbackDescriptor[] tfdNew = null;
|
||||
|
||||
if (tfd != null)
|
||||
{
|
||||
tfdNew = new TransformFeedbackDescriptor[tfd.Length];
|
||||
|
||||
for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++)
|
||||
{
|
||||
Array32<uint> varyingLocations = new Array32<uint>();
|
||||
Span<byte> varyingLocationsSpan = MemoryMarshal.Cast<uint, byte>(varyingLocations.ToSpan());
|
||||
tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length));
|
||||
|
||||
tfdNew[tfIndex] = new TransformFeedbackDescriptor(
|
||||
tfd[tfIndex].BufferIndex,
|
||||
tfd[tfIndex].Stride,
|
||||
tfd[tfIndex].VaryingLocations.Length,
|
||||
ref varyingLocations);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew);
|
||||
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
GuestShaderCacheEntry entry = entries[i];
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderProgramInfo info = new ShaderProgramInfo(
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<BufferDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
Array.Empty<TextureDescriptor>(),
|
||||
(ShaderStage)(i + 1),
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
// NOTE: Vertex B comes first in the shader cache.
|
||||
byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray();
|
||||
byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null;
|
||||
|
||||
Span<byte> codeSpan = entry.Code;
|
||||
byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray();
|
||||
|
||||
shaders[i + 1] = new CachedShaderStage(info, code, cb1Data);
|
||||
|
||||
if (code2 != null)
|
||||
{
|
||||
shaders[0] = new CachedShaderStage(null, code2, cb1Data);
|
||||
}
|
||||
|
||||
foreach (var td in entry.TextureDescriptors)
|
||||
{
|
||||
var handle = td.Key;
|
||||
var data = td.Value;
|
||||
|
||||
specState.RegisterTexture(
|
||||
i,
|
||||
handle,
|
||||
-1,
|
||||
data.UnpackFormat(),
|
||||
data.UnpackSrgb(),
|
||||
data.UnpackTextureTarget(),
|
||||
data.UnpackTextureCoordNormalized());
|
||||
}
|
||||
}
|
||||
|
||||
CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders);
|
||||
|
||||
hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
return guestProgramList.Length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
struct TransformFeedbackDescriptorOld
|
||||
{
|
||||
public int BufferIndex { get; }
|
||||
public int Stride { get; }
|
||||
|
||||
public byte[] VaryingLocations { get; }
|
||||
|
||||
public TransformFeedbackDescriptorOld(int bufferIndex, int stride, byte[] varyingLocations)
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
class CachedGpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _data;
|
||||
private readonly ReadOnlyMemory<byte> _cb1Data;
|
||||
private readonly GuestGpuAccessorHeader _header;
|
||||
private readonly Dictionary<int, GuestTextureDescriptor> _textureDescriptors;
|
||||
private readonly TransformFeedbackDescriptor[] _tfd;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the cached GPU state accessor for shader translation.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="data">The data of the shader</param>
|
||||
/// <param name="cb1Data">The constant buffer 1 data of the shader</param>
|
||||
/// <param name="header">The cache of the GPU accessor</param>
|
||||
/// <param name="guestTextureDescriptors">The cache of the texture descriptors</param>
|
||||
public CachedGpuAccessor(
|
||||
GpuContext context,
|
||||
ReadOnlyMemory<byte> data,
|
||||
ReadOnlyMemory<byte> cb1Data,
|
||||
GuestGpuAccessorHeader header,
|
||||
IReadOnlyDictionary<int, GuestTextureDescriptor> guestTextureDescriptors,
|
||||
TransformFeedbackDescriptor[] tfd) : base(context)
|
||||
{
|
||||
_data = data;
|
||||
_cb1Data = cb1Data;
|
||||
_header = header;
|
||||
_textureDescriptors = new Dictionary<int, GuestTextureDescriptor>();
|
||||
|
||||
foreach (KeyValuePair<int, GuestTextureDescriptor> guestTextureDescriptor in guestTextureDescriptors)
|
||||
{
|
||||
_textureDescriptors.Add(guestTextureDescriptor.Key, guestTextureDescriptor.Value);
|
||||
}
|
||||
|
||||
_tfd = tfd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the constant buffer 1.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset in bytes to read from</param>
|
||||
/// <returns>Value at the given offset</returns>
|
||||
public uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, uint>(_cb1Data.Span.Slice(offset))[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a log message.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to print</param>
|
||||
public void Log(string message)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span of the specified memory location, containing shader code.
|
||||
/// </summary>
|
||||
/// <param name="address">GPU virtual address of the data</param>
|
||||
/// <param name="minimumSize">Minimum size that the returned span may have</param>
|
||||
/// <returns>Span of the memory location</returns>
|
||||
public override ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, ulong>(_data.Span.Slice((int)address));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given memory address is mapped.
|
||||
/// </summary>
|
||||
/// <param name="address">GPU virtual address to be checked</param>
|
||||
/// <returns>True if the address is mapped, false otherwise</returns>
|
||||
public bool MemoryMapped(ulong address)
|
||||
{
|
||||
return address < (ulong)_data.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size X for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size X</returns>
|
||||
public int QueryComputeLocalSizeX()
|
||||
{
|
||||
return _header.ComputeLocalSizeX;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Y for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Y</returns>
|
||||
public int QueryComputeLocalSizeY()
|
||||
{
|
||||
return _header.ComputeLocalSizeY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Z for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Z</returns>
|
||||
public int QueryComputeLocalSizeZ()
|
||||
{
|
||||
return _header.ComputeLocalSizeZ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Memory size in bytes</returns>
|
||||
public int QueryComputeLocalMemorySize()
|
||||
{
|
||||
return _header.ComputeLocalMemorySize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Shared Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Shared Memory size in bytes</returns>
|
||||
public int QueryComputeSharedMemorySize()
|
||||
{
|
||||
return _header.ComputeSharedMemorySize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries current primitive topology for geometry shaders.
|
||||
/// </summary>
|
||||
/// <returns>Current primitive topology</returns>
|
||||
public InputTopology QueryPrimitiveTopology()
|
||||
{
|
||||
return _header.PrimitiveTopology;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader primitive winding order.
|
||||
/// </summary>
|
||||
/// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
return (_header.TessellationModePacked & 0x10) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader abstract patch type.
|
||||
/// </summary>
|
||||
/// <returns>Abstract patch type</returns>
|
||||
public TessPatchType QueryTessPatchType()
|
||||
{
|
||||
return (TessPatchType)(_header.TessellationModePacked & 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
|
||||
/// </summary>
|
||||
/// <returns>Spacing between tessellated vertices of the patch</returns>
|
||||
public TessSpacing QueryTessSpacing()
|
||||
{
|
||||
return (TessSpacing)((_header.TessellationModePacked >> 2) & 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture on the pool.
|
||||
/// </summary>
|
||||
/// <param name="handle">Index of the texture (this is the word offset of the handle in the constant buffer)</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>Texture descriptor</returns>
|
||||
public override Image.ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
|
||||
{
|
||||
if (!_textureDescriptors.TryGetValue(handle, out GuestTextureDescriptor textureDescriptor))
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
return textureDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries transform feedback enable state.
|
||||
/// </summary>
|
||||
/// <returns>True if the shader uses transform feedback, false otherwise</returns>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
return _tfd != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the varying locations that should be written to the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Varying locations for the specified buffer</returns>
|
||||
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
|
||||
{
|
||||
return _tfd[bufferIndex].VaryingLocations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Stride for the specified buffer</returns>
|
||||
public int QueryTransformFeedbackStride(int bufferIndex)
|
||||
{
|
||||
return _tfd[bufferIndex].Stride;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries if host state forces early depth testing.
|
||||
/// </summary>
|
||||
/// <returns>True if early depth testing is forced</returns>
|
||||
public bool QueryEarlyZForce()
|
||||
{
|
||||
return (_header.StateFlags & GuestGpuStateFlags.EarlyZForce) != 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,26 +7,33 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// Represents a program composed of one or more shader stages (for graphics shaders),
|
||||
/// or a single shader (for compute shaders).
|
||||
/// </summary>
|
||||
class ShaderBundle : IDisposable
|
||||
class CachedShaderProgram : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Host shader program object.
|
||||
/// </summary>
|
||||
public IProgram HostProgram { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU state used to create this version of the shader.
|
||||
/// </summary>
|
||||
public ShaderSpecializationState SpecializationState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Compiled shader for each shader stage.
|
||||
/// </summary>
|
||||
public ShaderCodeHolder[] Shaders { get; }
|
||||
public CachedShaderStage[] Shaders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader bundle.
|
||||
/// </summary>
|
||||
/// <param name="hostProgram">Host program with all the shader stages</param>
|
||||
/// <param name="specializationState">GPU state used to create this version of the shader</param>
|
||||
/// <param name="shaders">Shaders</param>
|
||||
public ShaderBundle(IProgram hostProgram, params ShaderCodeHolder[] shaders)
|
||||
public CachedShaderProgram(IProgram hostProgram, ShaderSpecializationState specializationState, params CachedShaderStage[] shaders)
|
||||
{
|
||||
HostProgram = hostProgram;
|
||||
SpecializationState = specializationState;
|
||||
Shaders = shaders;
|
||||
}
|
||||
|
||||
|
@ -36,11 +43,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public void Dispose()
|
||||
{
|
||||
HostProgram.Dispose();
|
||||
|
||||
foreach (ShaderCodeHolder holder in Shaders)
|
||||
{
|
||||
holder?.HostShader?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs
Normal file
38
Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader code for a single shader stage.
|
||||
/// </summary>
|
||||
class CachedShaderStage
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader program information.
|
||||
/// </summary>
|
||||
public ShaderProgramInfo Info { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maxwell binary shader code.
|
||||
/// </summary>
|
||||
public byte[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer 1 data accessed by the shader.
|
||||
/// </summary>
|
||||
public byte[] Cb1Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader code holder.
|
||||
/// </summary>
|
||||
/// <param name="info">Shader program information</param>
|
||||
/// <param name="code">Maxwell binary shader code</param>
|
||||
/// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param>
|
||||
public CachedShaderStage(ShaderProgramInfo info, byte[] code, byte[] cb1Data)
|
||||
{
|
||||
Info = info;
|
||||
Code = code;
|
||||
Cb1Data = cb1Data;
|
||||
}
|
||||
}
|
||||
}
|
68
Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs
Normal file
68
Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using Ryujinx.Graphics.Gpu.Shader.HashTable;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute shader cache hash table.
|
||||
/// </summary>
|
||||
class ComputeShaderCacheHashTable
|
||||
{
|
||||
private readonly PartitionedHashTable<ShaderSpecializationList> _cache;
|
||||
private readonly List<CachedShaderProgram> _shaderPrograms;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new compute shader cache hash table.
|
||||
/// </summary>
|
||||
public ComputeShaderCacheHashTable()
|
||||
{
|
||||
_cache = new PartitionedHashTable<ShaderSpecializationList>();
|
||||
_shaderPrograms = new List<CachedShaderProgram>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a program to the cache.
|
||||
/// </summary>
|
||||
/// <param name="program">Program to be added</param>
|
||||
public void Add(CachedShaderProgram program)
|
||||
{
|
||||
var specList = _cache.GetOrAdd(program.Shaders[0].Code, new ShaderSpecializationList());
|
||||
specList.Add(program);
|
||||
_shaderPrograms.Add(program);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a cached program.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="gpuVa">GPU virtual address of the compute shader</param>
|
||||
/// <param name="program">Cached host program for the given state, if found</param>
|
||||
/// <param name="cachedGuestCode">Cached guest code, if any found</param>
|
||||
/// <returns>True if a cached host program was found, false otherwise</returns>
|
||||
public bool TryFind(
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
ulong gpuVa,
|
||||
out CachedShaderProgram program,
|
||||
out byte[] cachedGuestCode)
|
||||
{
|
||||
program = null;
|
||||
ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(channel.MemoryManager, gpuVa);
|
||||
bool hasSpecList = _cache.TryFindItem(codeAccessor, out var specList, out cachedGuestCode);
|
||||
return hasSpecList && specList.TryFindForCompute(channel, poolState, out program);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all programs that have been added to the table.
|
||||
/// </summary>
|
||||
/// <returns>Programs added to the table</returns>
|
||||
public IEnumerable<CachedShaderProgram> GetPrograms()
|
||||
{
|
||||
foreach (var program in _shaderPrograms)
|
||||
{
|
||||
yield return program;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a background disk cache writer.
|
||||
/// </summary>
|
||||
class BackgroundDiskCacheWriter : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>.
|
||||
/// </summary>
|
||||
private enum CacheFileOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Operation to add a shader to the cache.
|
||||
/// </summary>
|
||||
AddShader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an operation to perform on the <see cref="_fileWriterWorkerQueue"/>.
|
||||
/// </summary>
|
||||
private struct CacheFileOperationTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of operation to perform.
|
||||
/// </summary>
|
||||
public readonly CacheFileOperation Type;
|
||||
|
||||
/// <summary>
|
||||
/// The data associated to this operation or null.
|
||||
/// </summary>
|
||||
public readonly object Data;
|
||||
|
||||
public CacheFileOperationTask(CacheFileOperation type, object data)
|
||||
{
|
||||
Type = type;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Background shader cache write information.
|
||||
/// </summary>
|
||||
private struct AddShaderData
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader program.
|
||||
/// </summary>
|
||||
public readonly CachedShaderProgram Program;
|
||||
|
||||
/// <summary>
|
||||
/// Binary host code.
|
||||
/// </summary>
|
||||
public readonly byte[] HostCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new background shader cache write information.
|
||||
/// </summary>
|
||||
/// <param name="program">Cached shader program</param>
|
||||
/// <param name="hostCode">Binary host code</param>
|
||||
public AddShaderData(CachedShaderProgram program, byte[] hostCode)
|
||||
{
|
||||
Program = program;
|
||||
HostCode = hostCode;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly DiskCacheHostStorage _hostStorage;
|
||||
private readonly AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new background disk cache writer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostStorage">Disk cache host storage</param>
|
||||
public BackgroundDiskCacheWriter(GpuContext context, DiskCacheHostStorage hostStorage)
|
||||
{
|
||||
_context = context;
|
||||
_hostStorage = hostStorage;
|
||||
_fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a shader cache background operation.
|
||||
/// </summary>
|
||||
/// <param name="task">Task to process</param>
|
||||
private void ProcessTask(CacheFileOperationTask task)
|
||||
{
|
||||
switch (task.Type)
|
||||
{
|
||||
case CacheFileOperation.AddShader:
|
||||
AddShaderData data = (AddShaderData)task.Data;
|
||||
try
|
||||
{
|
||||
_hostStorage.AddShader(_context, data.Program, data.HostCode);
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {diskCacheLoadException.Message}");
|
||||
}
|
||||
catch (IOException ioException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {ioException.Message}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a shader program to be cached in the background.
|
||||
/// </summary>
|
||||
/// <param name="program">Shader program to cache</param>
|
||||
/// <param name="hostCode">Host binary code of the program</param>
|
||||
public void AddShader(CachedShaderProgram program, byte[] hostCode)
|
||||
{
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask(CacheFileOperation.AddShader, new AddShaderData(program, hostCode)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_fileWriterWorkerQueue.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
216
Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
Normal file
216
Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Binary data serializer.
|
||||
/// </summary>
|
||||
struct BinarySerializer
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private Stream _activeStream;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new binary serializer.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to read from or write into</param>
|
||||
public BinarySerializer(Stream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
_activeStream = stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data</typeparam>
|
||||
/// <param name="data">Data read</param>
|
||||
public void Read<T>(ref T data) where T : unmanaged
|
||||
{
|
||||
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||||
for (int offset = 0; offset < buffer.Length;)
|
||||
{
|
||||
offset += _activeStream.Read(buffer.Slice(offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to read data from the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data</typeparam>
|
||||
/// <param name="data">Data read</param>
|
||||
/// <returns>True if the read was successful, false otherwise</returns>
|
||||
public bool TryRead<T>(ref T data) where T : unmanaged
|
||||
{
|
||||
// Length is unknown on compressed streams.
|
||||
if (_activeStream == _stream)
|
||||
{
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
if (_activeStream.Length - _activeStream.Position < size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Read(ref data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data prefixed with a magic and size from the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data</typeparam>
|
||||
/// <param name="data">Data read</param>
|
||||
/// <param name="magic">Expected magic value, for validation</param>
|
||||
public void ReadWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
|
||||
{
|
||||
uint actualMagic = 0;
|
||||
int size = 0;
|
||||
Read(ref actualMagic);
|
||||
Read(ref size);
|
||||
|
||||
if (actualMagic != magic)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic);
|
||||
}
|
||||
|
||||
// Structs are expected to expand but not shrink between versions.
|
||||
if (size > Unsafe.SizeOf<T>())
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength);
|
||||
}
|
||||
|
||||
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1)).Slice(0, size);
|
||||
for (int offset = 0; offset < buffer.Length;)
|
||||
{
|
||||
offset += _activeStream.Read(buffer.Slice(offset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data into the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data</typeparam>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write<T>(ref T data) where T : unmanaged
|
||||
{
|
||||
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||||
_activeStream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data prefixed with a magic and size into the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the data</typeparam>
|
||||
/// <param name="data">Data to write</param>
|
||||
/// <param name="magic">Magic value to write</param>
|
||||
public void WriteWithMagicAndSize<T>(ref T data, uint magic) where T : unmanaged
|
||||
{
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
Write(ref magic);
|
||||
Write(ref size);
|
||||
Span<byte> buffer = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref data, 1));
|
||||
_activeStream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all data that will be read from the stream has been compressed.
|
||||
/// </summary>
|
||||
public void BeginCompression()
|
||||
{
|
||||
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
|
||||
Read(ref algorithm);
|
||||
|
||||
if (algorithm == CompressionAlgorithm.Deflate)
|
||||
{
|
||||
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all data that will be written into the stream should be compressed.
|
||||
/// </summary>
|
||||
/// <param name="algorithm">Compression algorithm that should be used</param>
|
||||
public void BeginCompression(CompressionAlgorithm algorithm)
|
||||
{
|
||||
Write(ref algorithm);
|
||||
|
||||
if (algorithm == CompressionAlgorithm.Deflate)
|
||||
{
|
||||
_activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the end of a compressed chunck.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any data written after this will not be compressed unless <see cref="BeginCompression(CompressionAlgorithm)"/> is called again.
|
||||
/// Any data read after this will be assumed to be uncompressed unless <see cref="BeginCompression"/> is called again.
|
||||
/// </remarks>
|
||||
public void EndCompression()
|
||||
{
|
||||
if (_activeStream != _stream)
|
||||
{
|
||||
_activeStream.Dispose();
|
||||
_activeStream = _stream;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads compressed data from the stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <paramref name="data"/> must have the exact length of the uncompressed data,
|
||||
/// otherwise decompression will fail.
|
||||
/// </remarks>
|
||||
/// <param name="stream">Stream to read from</param>
|
||||
/// <param name="data">Buffer to write the uncompressed data into</param>
|
||||
public static void ReadCompressed(Stream stream, Span<byte> data)
|
||||
{
|
||||
CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte();
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case CompressionAlgorithm.None:
|
||||
stream.Read(data);
|
||||
break;
|
||||
case CompressionAlgorithm.Deflate:
|
||||
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
|
||||
for (int offset = 0; offset < data.Length;)
|
||||
{
|
||||
offset += stream.Read(data.Slice(offset));
|
||||
}
|
||||
stream.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses and writes the compressed data into the stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to write into</param>
|
||||
/// <param name="data">Data to compress</param>
|
||||
/// <param name="algorithm">Compression algorithm to be used</param>
|
||||
public static void WriteCompressed(Stream stream, ReadOnlySpan<byte> data, CompressionAlgorithm algorithm)
|
||||
{
|
||||
stream.WriteByte((byte)algorithm);
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case CompressionAlgorithm.None:
|
||||
stream.Write(data);
|
||||
break;
|
||||
case CompressionAlgorithm.Deflate:
|
||||
stream = new DeflateStream(stream, CompressionLevel.SmallestSize, true);
|
||||
stream.Write(data);
|
||||
stream.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Algorithm used to compress the cache.
|
||||
/// </summary>
|
||||
enum CompressionAlgorithm : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No compression, the data is stored as-is.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Deflate compression (RFC 1951).
|
||||
/// </summary>
|
||||
Deflate
|
||||
}
|
||||
}
|
57
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
Normal file
57
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Common disk cache utility methods.
|
||||
/// </summary>
|
||||
static class DiskCacheCommon
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens a file for read or write.
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base path of the file (should not include the file name)</param>
|
||||
/// <param name="fileName">Name of the file</param>
|
||||
/// <param name="writable">Indicates if the file will be read or written</param>
|
||||
/// <returns>File stream</returns>
|
||||
public static FileStream OpenFile(string basePath, string fileName, bool writable)
|
||||
{
|
||||
string fullPath = Path.Combine(basePath, fileName);
|
||||
|
||||
FileMode mode;
|
||||
FileAccess access;
|
||||
|
||||
if (writable)
|
||||
{
|
||||
mode = FileMode.OpenOrCreate;
|
||||
access = FileAccess.ReadWrite;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = FileMode.Open;
|
||||
access = FileAccess.Read;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new FileStream(fullPath, mode, access, FileShare.Read);
|
||||
}
|
||||
catch (IOException ioException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Could not access file \"{fullPath}\". {ioException.Message}");
|
||||
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.NoAccess);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compression algorithm that should be used when writing the disk cache.
|
||||
/// </summary>
|
||||
/// <returns>Compression algorithm</returns>
|
||||
public static CompressionAlgorithm GetCompressionAlgorithm()
|
||||
{
|
||||
return CompressionAlgorithm.Deflate;
|
||||
}
|
||||
}
|
||||
}
|
202
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
Normal file
202
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GPU state and memory accessor.
|
||||
/// </summary>
|
||||
class DiskCacheGpuAccessor : GpuAccessorBase, IGpuAccessor
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _data;
|
||||
private readonly ReadOnlyMemory<byte> _cb1Data;
|
||||
private readonly ShaderSpecializationState _oldSpecState;
|
||||
private readonly ShaderSpecializationState _newSpecState;
|
||||
private readonly int _stageIndex;
|
||||
private ResourceCounts _resourceCounts;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the cached GPU state accessor for shader translation.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="data">The data of the shader</param>
|
||||
/// <param name="cb1Data">The constant buffer 1 data of the shader</param>
|
||||
/// <param name="oldSpecState">Shader specialization state of the cached shader</param>
|
||||
/// <param name="newSpecState">Shader specialization state of the recompiled shader</param>
|
||||
/// <param name="stageIndex">Shader stage index</param>
|
||||
public DiskCacheGpuAccessor(
|
||||
GpuContext context,
|
||||
ReadOnlyMemory<byte> data,
|
||||
ReadOnlyMemory<byte> cb1Data,
|
||||
ShaderSpecializationState oldSpecState,
|
||||
ShaderSpecializationState newSpecState,
|
||||
ResourceCounts counts,
|
||||
int stageIndex) : base(context)
|
||||
{
|
||||
_data = data;
|
||||
_cb1Data = cb1Data;
|
||||
_oldSpecState = oldSpecState;
|
||||
_newSpecState = newSpecState;
|
||||
_stageIndex = stageIndex;
|
||||
_resourceCounts = counts;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
if (offset + sizeof(uint) > _cb1Data.Length)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.InvalidCb1DataLength);
|
||||
}
|
||||
|
||||
return MemoryMarshal.Cast<byte, uint>(_cb1Data.Span.Slice(offset))[0];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Log(string message)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, ulong>(_data.Span.Slice((int)address));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
{
|
||||
return _resourceCounts.UniformBuffersCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
return _resourceCounts.StorageBuffersCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
{
|
||||
return _resourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _resourceCounts.ImagesCount++;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeY() => _oldSpecState.ComputeState.LocalSizeY;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeZ() => _oldSpecState.ComputeState.LocalSizeZ;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalMemorySize() => _oldSpecState.ComputeState.LocalMemorySize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeSharedMemorySize() => _oldSpecState.ComputeState.SharedMemorySize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint QueryConstantBufferUse()
|
||||
{
|
||||
_newSpecState.RecordConstantBufferUse(_stageIndex, _oldSpecState.ConstantBufferUse[_stageIndex]);
|
||||
return _oldSpecState.ConstantBufferUse[_stageIndex];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public InputTopology QueryPrimitiveTopology()
|
||||
{
|
||||
_newSpecState.RecordPrimitiveTopology();
|
||||
return ConvertToInputTopology(_oldSpecState.GraphicsState.Topology, _oldSpecState.GraphicsState.TessellationMode);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
_newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot);
|
||||
(uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
|
||||
return ConvertToTextureFormat(format, formatSrgb);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SamplerType QuerySamplerType(int handle, int cbufSlot)
|
||||
{
|
||||
_newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
|
||||
return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
_newSpecState.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
return _oldSpecState.TransformFeedbackDescriptors != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
|
||||
{
|
||||
return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].AsSpan();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryTransformFeedbackStride(int bufferIndex)
|
||||
{
|
||||
return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryEarlyZForce()
|
||||
{
|
||||
_newSpecState.RecordEarlyZForce();
|
||||
return _oldSpecState.GraphicsState.EarlyZForce;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterTexture(int handle, int cbufSlot)
|
||||
{
|
||||
if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot))
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureDescriptor);
|
||||
}
|
||||
|
||||
(uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot);
|
||||
TextureTarget target = _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot);
|
||||
bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
_newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized);
|
||||
}
|
||||
}
|
||||
}
|
459
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
Normal file
459
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
Normal file
|
@ -0,0 +1,459 @@
|
|||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// On-disk shader cache storage for guest code.
|
||||
/// </summary>
|
||||
class DiskCacheGuestStorage
|
||||
{
|
||||
private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24);
|
||||
|
||||
private const ushort VersionMajor = 1;
|
||||
private const ushort VersionMinor = 0;
|
||||
private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor;
|
||||
|
||||
private const string TocFileName = "guest.toc";
|
||||
private const string DataFileName = "guest.data";
|
||||
|
||||
private readonly string _basePath;
|
||||
|
||||
/// <summary>
|
||||
/// TOC (Table of contents) file header.
|
||||
/// </summary>
|
||||
private struct TocHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Magic value, for validation and identification purposes.
|
||||
/// </summary>
|
||||
public uint Magic;
|
||||
|
||||
/// <summary>
|
||||
/// File format version.
|
||||
/// </summary>
|
||||
public uint Version;
|
||||
|
||||
/// <summary>
|
||||
/// Header padding.
|
||||
/// </summary>
|
||||
public uint Padding;
|
||||
|
||||
/// <summary>
|
||||
/// Number of modifications to the file, also the shaders count.
|
||||
/// </summary>
|
||||
public uint ModificationsCount;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// </summary>
|
||||
public ulong Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// </summary>
|
||||
public ulong Reserved2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TOC (Table of contents) file entry.
|
||||
/// </summary>
|
||||
private struct TocEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset of the data on the data file.
|
||||
/// </summary>
|
||||
public uint Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Code size.
|
||||
/// </summary>
|
||||
public uint CodeSize;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer 1 data size.
|
||||
/// </summary>
|
||||
public uint Cb1DataSize;
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the code and constant buffer data.
|
||||
/// </summary>
|
||||
public uint Hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TOC (Table of contents) memory cache entry.
|
||||
/// </summary>
|
||||
private struct TocMemoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset of the data on the data file.
|
||||
/// </summary>
|
||||
public uint Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Code size.
|
||||
/// </summary>
|
||||
public uint CodeSize;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer 1 data size.
|
||||
/// </summary>
|
||||
public uint Cb1DataSize;
|
||||
|
||||
/// <summary>
|
||||
/// Index of the shader on the cache.
|
||||
/// </summary>
|
||||
public readonly int Index;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new TOC memory entry.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the data on the data file</param>
|
||||
/// <param name="codeSize">Code size</param>
|
||||
/// <param name="cb1DataSize">Constant buffer 1 data size</param>
|
||||
/// <param name="index">Index of the shader on the cache</param>
|
||||
public TocMemoryEntry(uint offset, uint codeSize, uint cb1DataSize, int index)
|
||||
{
|
||||
Offset = offset;
|
||||
CodeSize = codeSize;
|
||||
Cb1DataSize = cb1DataSize;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<uint, List<TocMemoryEntry>> _toc;
|
||||
private uint _tocModificationsCount;
|
||||
|
||||
private (byte[], byte[])[] _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new disk cache guest storage.
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base path of the disk shader cache</param>
|
||||
public DiskCacheGuestStorage(string basePath)
|
||||
{
|
||||
_basePath = basePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the TOC (table of contents) file for the guest cache exists.
|
||||
/// </summary>
|
||||
/// <returns>True if the file exists, false otherwise</returns>
|
||||
public bool TocFileExists()
|
||||
{
|
||||
return File.Exists(Path.Combine(_basePath, TocFileName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the data file for the guest cache exists.
|
||||
/// </summary>
|
||||
/// <returns>True if the file exists, false otherwise</returns>
|
||||
public bool DataFileExists()
|
||||
{
|
||||
return File.Exists(Path.Combine(_basePath, DataFileName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the guest cache TOC (table of contents) file.
|
||||
/// </summary>
|
||||
/// <returns>File stream</returns>
|
||||
public Stream OpenTocFileStream()
|
||||
{
|
||||
return DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the guest cache data file.
|
||||
/// </summary>
|
||||
/// <returns>File stream</returns>
|
||||
public Stream OpenDataFileStream()
|
||||
{
|
||||
return DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all content from the guest cache files.
|
||||
/// </summary>
|
||||
public void ClearCache()
|
||||
{
|
||||
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
|
||||
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
|
||||
|
||||
tocFileStream.SetLength(0);
|
||||
dataFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the guest cache from file or memory cache.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||
/// <param name="dataFileStream">Guest data file stream</param>
|
||||
/// <param name="index">Guest shader index</param>
|
||||
/// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns>
|
||||
public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index)
|
||||
{
|
||||
if (_cache == null || index >= _cache.Length)
|
||||
{
|
||||
_cache = new (byte[], byte[])[Math.Max(index + 1, GetShadersCountFromLength(tocFileStream.Length))];
|
||||
}
|
||||
|
||||
(byte[] guestCode, byte[] cb1Data) = _cache[index];
|
||||
|
||||
if (guestCode == null || cb1Data == null)
|
||||
{
|
||||
BinarySerializer tocReader = new BinarySerializer(tocFileStream);
|
||||
tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + index * Unsafe.SizeOf<TocEntry>(), SeekOrigin.Begin);
|
||||
|
||||
TocEntry entry = new TocEntry();
|
||||
tocReader.Read(ref entry);
|
||||
|
||||
guestCode = new byte[entry.CodeSize];
|
||||
cb1Data = new byte[entry.Cb1DataSize];
|
||||
|
||||
if (entry.Offset >= (ulong)dataFileStream.Length)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||
dataFileStream.Read(cb1Data);
|
||||
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
|
||||
|
||||
_cache[index] = (guestCode, cb1Data);
|
||||
}
|
||||
|
||||
return (guestCode, cb1Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears guest code memory cache, forcing future loads to be from file.
|
||||
/// </summary>
|
||||
public void ClearMemoryCache()
|
||||
{
|
||||
_cache = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the guest shaders count from the TOC file length.
|
||||
/// </summary>
|
||||
/// <param name="length">TOC file length</param>
|
||||
/// <returns>Shaders count</returns>
|
||||
private static int GetShadersCountFromLength(long length)
|
||||
{
|
||||
return (int)((length - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a guest shader to the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the shader is already on the cache, the existing index will be returned and nothing will be written.
|
||||
/// </remarks>
|
||||
/// <param name="data">Guest code</param>
|
||||
/// <param name="cb1Data">Constant buffer 1 data accessed by the code</param>
|
||||
/// <returns>Index of the shader on the cache</returns>
|
||||
public int AddShader(ReadOnlySpan<byte> data, ReadOnlySpan<byte> cb1Data)
|
||||
{
|
||||
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true);
|
||||
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true);
|
||||
|
||||
TocHeader header = new TocHeader();
|
||||
|
||||
LoadOrCreateToc(tocFileStream, ref header);
|
||||
|
||||
uint hash = CalcHash(data, cb1Data);
|
||||
|
||||
if (_toc.TryGetValue(hash, out var list))
|
||||
{
|
||||
foreach (var entry in list)
|
||||
{
|
||||
if (data.Length != entry.CodeSize || cb1Data.Length != entry.Cb1DataSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||
byte[] cachedCode = new byte[entry.CodeSize];
|
||||
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
|
||||
dataFileStream.Read(cachedCb1Data);
|
||||
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
|
||||
|
||||
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
|
||||
{
|
||||
return entry.Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WriteNewEntry(tocFileStream, dataFileStream, ref header, data, cb1Data, hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the guest cache TOC file, or create a new one if not present.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||
/// <param name="header">Set to the TOC file header</param>
|
||||
private void LoadOrCreateToc(Stream tocFileStream, ref TocHeader header)
|
||||
{
|
||||
BinarySerializer reader = new BinarySerializer(tocFileStream);
|
||||
|
||||
if (!reader.TryRead(ref header) || header.Magic != TocMagic || header.Version != VersionPacked)
|
||||
{
|
||||
CreateToc(tocFileStream, ref header);
|
||||
}
|
||||
|
||||
if (_toc == null || header.ModificationsCount != _tocModificationsCount)
|
||||
{
|
||||
if (!LoadTocEntries(tocFileStream, ref reader))
|
||||
{
|
||||
CreateToc(tocFileStream, ref header);
|
||||
}
|
||||
|
||||
_tocModificationsCount = header.ModificationsCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new guest cache TOC file.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||
/// <param name="header">Set to the TOC header</param>
|
||||
private void CreateToc(Stream tocFileStream, ref TocHeader header)
|
||||
{
|
||||
BinarySerializer writer = new BinarySerializer(tocFileStream);
|
||||
|
||||
header.Magic = TocMagic;
|
||||
header.Version = VersionPacked;
|
||||
header.Padding = 0;
|
||||
header.ModificationsCount = 0;
|
||||
header.Reserved = 0;
|
||||
header.Reserved2 = 0;
|
||||
|
||||
if (tocFileStream.Length > 0)
|
||||
{
|
||||
tocFileStream.Seek(0, SeekOrigin.Begin);
|
||||
tocFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
writer.Write(ref header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the entries on the guest TOC file.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">Guest TOC file stream</param>
|
||||
/// <param name="reader">TOC file reader</param>
|
||||
/// <returns>True if the operation was successful, false otherwise</returns>
|
||||
private bool LoadTocEntries(Stream tocFileStream, ref BinarySerializer reader)
|
||||
{
|
||||
_toc = new Dictionary<uint, List<TocMemoryEntry>>();
|
||||
|
||||
TocEntry entry = new TocEntry();
|
||||
int index = 0;
|
||||
|
||||
while (tocFileStream.Position < tocFileStream.Length)
|
||||
{
|
||||
if (!reader.TryRead(ref entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddTocMemoryEntry(entry.Offset, entry.CodeSize, entry.Cb1DataSize, entry.Hash, index++);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a new guest code entry into the file.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">TOC file stream</param>
|
||||
/// <param name="dataFileStream">Data file stream</param>
|
||||
/// <param name="header">TOC header, to be updated with the new count</param>
|
||||
/// <param name="data">Guest code</param>
|
||||
/// <param name="cb1Data">Constant buffer 1 data accessed by the guest code</param>
|
||||
/// <param name="hash">Code and constant buffer data hash</param>
|
||||
/// <returns>Entry index</returns>
|
||||
private int WriteNewEntry(
|
||||
Stream tocFileStream,
|
||||
Stream dataFileStream,
|
||||
ref TocHeader header,
|
||||
ReadOnlySpan<byte> data,
|
||||
ReadOnlySpan<byte> cb1Data,
|
||||
uint hash)
|
||||
{
|
||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||
|
||||
dataFileStream.Seek(0, SeekOrigin.End);
|
||||
uint dataOffset = checked((uint)dataFileStream.Position);
|
||||
uint codeSize = (uint)data.Length;
|
||||
uint cb1DataSize = (uint)cb1Data.Length;
|
||||
dataFileStream.Write(cb1Data);
|
||||
BinarySerializer.WriteCompressed(dataFileStream, data, DiskCacheCommon.GetCompressionAlgorithm());
|
||||
|
||||
_tocModificationsCount = ++header.ModificationsCount;
|
||||
tocFileStream.Seek(0, SeekOrigin.Begin);
|
||||
tocWriter.Write(ref header);
|
||||
|
||||
TocEntry entry = new TocEntry()
|
||||
{
|
||||
Offset = dataOffset,
|
||||
CodeSize = codeSize,
|
||||
Cb1DataSize = cb1DataSize,
|
||||
Hash = hash
|
||||
};
|
||||
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
int index = (int)((tocFileStream.Position - Unsafe.SizeOf<TocHeader>()) / Unsafe.SizeOf<TocEntry>());
|
||||
|
||||
tocWriter.Write(ref entry);
|
||||
|
||||
AddTocMemoryEntry(dataOffset, codeSize, cb1DataSize, hash, index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entry to the memory TOC cache. This can be used to avoid reading the TOC file all the time.
|
||||
/// </summary>
|
||||
/// <param name="dataOffset">Offset of the code and constant buffer data in the data file</param>
|
||||
/// <param name="codeSize">Code size</param>
|
||||
/// <param name="cb1DataSize">Constant buffer 1 data size</param>
|
||||
/// <param name="hash">Code and constant buffer data hash</param>
|
||||
/// <param name="index">Index of the data on the cache</param>
|
||||
private void AddTocMemoryEntry(uint dataOffset, uint codeSize, uint cb1DataSize, uint hash, int index)
|
||||
{
|
||||
if (!_toc.TryGetValue(hash, out var list))
|
||||
{
|
||||
_toc.Add(hash, list = new List<TocMemoryEntry>());
|
||||
}
|
||||
|
||||
list.Add(new TocMemoryEntry(dataOffset, codeSize, cb1DataSize, index));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash for a data pair.
|
||||
/// </summary>
|
||||
/// <param name="data">Data 1</param>
|
||||
/// <param name="data2">Data 2</param>
|
||||
/// <returns>Hash of both data</returns>
|
||||
private static uint CalcHash(ReadOnlySpan<byte> data, ReadOnlySpan<byte> data2)
|
||||
{
|
||||
return CalcHash(data2) * 23 ^ CalcHash(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash for data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be hashed</param>
|
||||
/// <returns>Hash of the data</returns>
|
||||
private static uint CalcHash(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return (uint)XXHash128.ComputeHash(data).Low;
|
||||
}
|
||||
}
|
||||
}
|
763
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
Normal file
763
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
Normal file
|
@ -0,0 +1,763 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// On-disk shader cache storage for host code.
|
||||
/// </summary>
|
||||
class DiskCacheHostStorage
|
||||
{
|
||||
private const uint TocsMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'S' << 24);
|
||||
private const uint TochMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'H' << 24);
|
||||
private const uint ShdiMagic = (byte)'S' | ((byte)'H' << 8) | ((byte)'D' << 16) | ((byte)'I' << 24);
|
||||
private const uint BufdMagic = (byte)'B' | ((byte)'U' << 8) | ((byte)'F' << 16) | ((byte)'D' << 24);
|
||||
private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24);
|
||||
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 1;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 0;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
||||
private readonly string _basePath;
|
||||
|
||||
public bool CacheEnabled => !string.IsNullOrEmpty(_basePath);
|
||||
|
||||
/// <summary>
|
||||
/// TOC (Table of contents) file header.
|
||||
/// </summary>
|
||||
private struct TocHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Magic value, for validation and identification.
|
||||
/// </summary>
|
||||
public uint Magic;
|
||||
|
||||
/// <summary>
|
||||
/// File format version.
|
||||
/// </summary>
|
||||
public uint FormatVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Generated shader code version.
|
||||
/// </summary>
|
||||
public uint CodeGenVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Header padding.
|
||||
/// </summary>
|
||||
public uint Padding;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// </summary>
|
||||
public ulong Reserved;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved space, to be used in the future. Write as zero.
|
||||
/// </summary>
|
||||
public ulong Reserved2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offset and size pair.
|
||||
/// </summary>
|
||||
private struct OffsetAndSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Offset.
|
||||
/// </summary>
|
||||
public ulong Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Size.
|
||||
/// </summary>
|
||||
public uint Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-stage data entry.
|
||||
/// </summary>
|
||||
private struct DataEntryPerStage
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of the guest code on the guest code cache TOC file.
|
||||
/// </summary>
|
||||
public int GuestCodeIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-program data entry.
|
||||
/// </summary>
|
||||
private struct DataEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Bit mask where each bit set is a used shader stage. Should be zero for compute shaders.
|
||||
/// </summary>
|
||||
public uint StagesBitMask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-stage shader information, returned by the translator.
|
||||
/// </summary>
|
||||
private struct DataShaderInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Total constant buffers used.
|
||||
/// </summary>
|
||||
public ushort CBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total storage buffers used.
|
||||
/// </summary>
|
||||
public ushort SBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total textures used.
|
||||
/// </summary>
|
||||
public ushort TexturesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total images used.
|
||||
/// </summary>
|
||||
public ushort ImagesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Shader stage.
|
||||
/// </summary>
|
||||
public ShaderStage Stage;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the shader accesses the Instance ID built-in variable.
|
||||
/// </summary>
|
||||
public bool UsesInstanceId;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the shader modifies the Layer built-in variable.
|
||||
/// </summary>
|
||||
public bool UsesRtLayer;
|
||||
|
||||
/// <summary>
|
||||
/// Bit mask with the clip distances written on the vertex stage.
|
||||
/// </summary>
|
||||
public byte ClipDistancesWritten;
|
||||
|
||||
/// <summary>
|
||||
/// Bit mask of the render target components written by the fragment stage.
|
||||
/// </summary>
|
||||
public int FragmentOutputMap;
|
||||
}
|
||||
|
||||
private readonly DiskCacheGuestStorage _guestStorage;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a disk cache host storage.
|
||||
/// </summary>
|
||||
/// <param name="basePath">Base path of the shader cache</param>
|
||||
public DiskCacheHostStorage(string basePath)
|
||||
{
|
||||
_basePath = basePath;
|
||||
_guestStorage = new DiskCacheGuestStorage(basePath);
|
||||
|
||||
if (CacheEnabled)
|
||||
{
|
||||
Directory.CreateDirectory(basePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total of host programs on the cache.
|
||||
/// </summary>
|
||||
/// <returns>Host programs count</returns>
|
||||
public int GetProgramCount()
|
||||
{
|
||||
string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
|
||||
|
||||
if (!File.Exists(tocFilePath))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Guest the name of the host program cache file, with extension.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <returns>Name of the file, without extension</returns>
|
||||
private static string GetHostFileName(GpuContext context)
|
||||
{
|
||||
string apiName = context.Capabilities.Api.ToString().ToLowerInvariant();
|
||||
string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant());
|
||||
return $"{apiName}_{vendorName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid path characters and spaces from a file name.
|
||||
/// </summary>
|
||||
/// <param name="fileName">File name</param>
|
||||
/// <returns>Filtered file name</returns>
|
||||
private static string RemoveInvalidCharacters(string fileName)
|
||||
{
|
||||
int indexOfSpace = fileName.IndexOf(' ');
|
||||
if (indexOfSpace >= 0)
|
||||
{
|
||||
fileName = fileName.Substring(0, indexOfSpace);
|
||||
}
|
||||
|
||||
return string.Concat(fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the TOC host file.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <returns>File name</returns>
|
||||
private static string GetHostTocFileName(GpuContext context)
|
||||
{
|
||||
return GetHostFileName(context) + ".toc";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the data host file.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <returns>File name</returns>
|
||||
private static string GetHostDataFileName(GpuContext context)
|
||||
{
|
||||
return GetHostFileName(context) + ".data";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a disk cache exists for the current application.
|
||||
/// </summary>
|
||||
/// <returns>True if a disk cache exists, false otherwise</returns>
|
||||
public bool CacheExists()
|
||||
{
|
||||
string tocFilePath = Path.Combine(_basePath, SharedTocFileName);
|
||||
string dataFilePath = Path.Combine(_basePath, SharedDataFileName);
|
||||
|
||||
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath) || !_guestStorage.TocFileExists() || !_guestStorage.DataFileExists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all shaders from the cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="loader">Parallel disk cache loader</param>
|
||||
public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader)
|
||||
{
|
||||
if (!CacheExists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Stream hostTocFileStream = null;
|
||||
Stream hostDataFileStream = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false);
|
||||
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false);
|
||||
|
||||
using var guestTocFileStream = _guestStorage.OpenTocFileStream();
|
||||
using var guestDataFileStream = _guestStorage.OpenDataFileStream();
|
||||
|
||||
BinarySerializer tocReader = new BinarySerializer(tocFileStream);
|
||||
BinarySerializer dataReader = new BinarySerializer(dataFileStream);
|
||||
|
||||
TocHeader header = new TocHeader();
|
||||
|
||||
if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
if (header.FormatVersion != FileFormatVersionPacked)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion);
|
||||
}
|
||||
|
||||
bool loadHostCache = header.CodeGenVersion == CodeGenVersion;
|
||||
|
||||
int programIndex = 0;
|
||||
|
||||
DataEntry entry = new DataEntry();
|
||||
|
||||
while (tocFileStream.Position < tocFileStream.Length && loader.Active)
|
||||
{
|
||||
ulong dataOffset = 0;
|
||||
tocReader.Read(ref dataOffset);
|
||||
|
||||
if ((ulong)dataOffset >= (ulong)dataFileStream.Length)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin);
|
||||
|
||||
dataReader.BeginCompression();
|
||||
dataReader.Read(ref entry);
|
||||
uint stagesBitMask = entry.StagesBitMask;
|
||||
|
||||
if ((stagesBitMask & ~0x3fu) != 0)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
bool isCompute = stagesBitMask == 0;
|
||||
if (isCompute)
|
||||
{
|
||||
stagesBitMask = 1;
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];
|
||||
|
||||
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
||||
|
||||
while (stagesBitMask != 0)
|
||||
{
|
||||
int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask);
|
||||
|
||||
dataReader.Read(ref stageEntry);
|
||||
|
||||
ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null;
|
||||
|
||||
(byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
|
||||
guestTocFileStream,
|
||||
guestDataFileStream,
|
||||
stageEntry.GuestCodeIndex);
|
||||
|
||||
shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);
|
||||
|
||||
stagesBitMask &= ~(1u << stageIndex);
|
||||
}
|
||||
|
||||
ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader);
|
||||
dataReader.EndCompression();
|
||||
|
||||
if (loadHostCache)
|
||||
{
|
||||
byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);
|
||||
|
||||
if (hostCode != null)
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
||||
IProgram hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
|
||||
|
||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||
|
||||
loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadHostCache = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadHostCache)
|
||||
{
|
||||
loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
|
||||
}
|
||||
|
||||
loader.CheckCompilation();
|
||||
programIndex++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_guestStorage.ClearMemoryCache();
|
||||
|
||||
hostTocFileStream?.Dispose();
|
||||
hostDataFileStream?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the host code for a given shader, if existent.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
|
||||
/// <param name="dataFileStream">Host data file stream, initialized if needed</param>
|
||||
/// <param name="programIndex">Index of the program on the cache</param>
|
||||
/// <returns>Host binary code, or null if not found</returns>
|
||||
private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex)
|
||||
{
|
||||
if (tocFileStream == null && dataFileStream == null)
|
||||
{
|
||||
string tocFilePath = Path.Combine(_basePath, GetHostTocFileName(context));
|
||||
string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context));
|
||||
|
||||
if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
|
||||
dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);
|
||||
}
|
||||
|
||||
int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>();
|
||||
if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((ulong)offset >= (ulong)dataFileStream.Length)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
tocFileStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
BinarySerializer tocReader = new BinarySerializer(tocFileStream);
|
||||
|
||||
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
||||
tocReader.Read(ref offsetAndSize);
|
||||
|
||||
if (offsetAndSize.Offset >= (ulong)dataFileStream.Length)
|
||||
{
|
||||
throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
|
||||
}
|
||||
|
||||
dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] hostCode = new byte[offsetAndSize.Size];
|
||||
|
||||
BinarySerializer.ReadCompressed(dataFileStream, hostCode);
|
||||
|
||||
return hostCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets output streams for the disk cache, for faster batch writing.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context, used to determine the host disk cache</param>
|
||||
/// <returns>A collection of disk cache output streams</returns>
|
||||
public DiskCacheOutputStreams GetOutputStreams(GpuContext context)
|
||||
{
|
||||
var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
||||
var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
||||
|
||||
var hostTocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
||||
var hostDataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
||||
|
||||
return new DiskCacheOutputStreams(tocFileStream, dataFileStream, hostTocFileStream, hostDataFileStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a shader to the cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="program">Cached program</param>
|
||||
/// <param name="hostCode">Optional host binary code</param>
|
||||
/// <param name="streams">Output streams to use</param>
|
||||
public void AddShader(GpuContext context, CachedShaderProgram program, ReadOnlySpan<byte> hostCode, DiskCacheOutputStreams streams = null)
|
||||
{
|
||||
uint stagesBitMask = 0;
|
||||
|
||||
for (int index = 0; index < program.Shaders.Length; index++)
|
||||
{
|
||||
var shader = program.Shaders[index];
|
||||
if (shader == null || (shader.Info != null && shader.Info.Stage == ShaderStage.Compute))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stagesBitMask |= 1u << index;
|
||||
}
|
||||
|
||||
var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
||||
var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
||||
|
||||
if (tocFileStream.Length == 0)
|
||||
{
|
||||
TocHeader header = new TocHeader();
|
||||
CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion);
|
||||
}
|
||||
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
dataFileStream.Seek(0, SeekOrigin.End);
|
||||
|
||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||
BinarySerializer dataWriter = new BinarySerializer(dataFileStream);
|
||||
|
||||
ulong dataOffset = (ulong)dataFileStream.Position;
|
||||
tocWriter.Write(ref dataOffset);
|
||||
|
||||
DataEntry entry = new DataEntry();
|
||||
|
||||
entry.StagesBitMask = stagesBitMask;
|
||||
|
||||
dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm());
|
||||
dataWriter.Write(ref entry);
|
||||
|
||||
DataEntryPerStage stageEntry = new DataEntryPerStage();
|
||||
|
||||
for (int index = 0; index < program.Shaders.Length; index++)
|
||||
{
|
||||
var shader = program.Shaders[index];
|
||||
if (shader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data);
|
||||
|
||||
dataWriter.Write(ref stageEntry);
|
||||
|
||||
WriteShaderProgramInfo(ref dataWriter, shader.Info);
|
||||
}
|
||||
|
||||
program.SpecializationState.Write(ref dataWriter);
|
||||
dataWriter.EndCompression();
|
||||
|
||||
if (streams == null)
|
||||
{
|
||||
tocFileStream.Dispose();
|
||||
dataFileStream.Dispose();
|
||||
}
|
||||
|
||||
if (hostCode.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteHostCode(context, hostCode, -1, streams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all content from the guest cache files.
|
||||
/// </summary>
|
||||
public void ClearGuestCache()
|
||||
{
|
||||
_guestStorage.ClearCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all content from the shared cache files.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public void ClearSharedCache()
|
||||
{
|
||||
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true);
|
||||
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true);
|
||||
|
||||
tocFileStream.SetLength(0);
|
||||
dataFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all content from the host cache files.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public void ClearHostCache(GpuContext context)
|
||||
{
|
||||
using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
||||
using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
||||
|
||||
tocFileStream.SetLength(0);
|
||||
dataFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a host binary shader to the host cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only modifies the host cache. The shader must already exist in the other caches.
|
||||
/// This method should only be used for rebuilding the host cache after a clear.
|
||||
/// </remarks>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostCode">Host binary code</param>
|
||||
/// <param name="programIndex">Index of the program in the cache</param>
|
||||
public void AddHostShader(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex)
|
||||
{
|
||||
WriteHostCode(context, hostCode, programIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the host binary code on the host cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="hostCode">Host binary code</param>
|
||||
/// <param name="programIndex">Index of the program in the cache</param>
|
||||
/// <param name="streams">Output streams to use</param>
|
||||
private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null)
|
||||
{
|
||||
var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true);
|
||||
var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true);
|
||||
|
||||
if (tocFileStream.Length == 0)
|
||||
{
|
||||
TocHeader header = new TocHeader();
|
||||
CreateToc(tocFileStream, ref header, TochMagic, 0);
|
||||
}
|
||||
|
||||
if (programIndex == -1)
|
||||
{
|
||||
tocFileStream.Seek(0, SeekOrigin.End);
|
||||
}
|
||||
else
|
||||
{
|
||||
tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
dataFileStream.Seek(0, SeekOrigin.End);
|
||||
|
||||
BinarySerializer tocWriter = new BinarySerializer(tocFileStream);
|
||||
|
||||
OffsetAndSize offsetAndSize = new OffsetAndSize();
|
||||
offsetAndSize.Offset = (ulong)dataFileStream.Position;
|
||||
offsetAndSize.Size = (uint)hostCode.Length;
|
||||
tocWriter.Write(ref offsetAndSize);
|
||||
|
||||
BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm());
|
||||
|
||||
if (streams == null)
|
||||
{
|
||||
tocFileStream.Dispose();
|
||||
dataFileStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TOC file for the host or shared cache.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">TOC file stream</param>
|
||||
/// <param name="header">Set to the TOC file header</param>
|
||||
/// <param name="magic">Magic value to be written</param>
|
||||
/// <param name="codegenVersion">Shader codegen version, only valid for the host file</param>
|
||||
private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion)
|
||||
{
|
||||
BinarySerializer writer = new BinarySerializer(tocFileStream);
|
||||
|
||||
header.Magic = magic;
|
||||
header.FormatVersion = FileFormatVersionPacked;
|
||||
header.CodeGenVersion = codegenVersion;
|
||||
header.Padding = 0;
|
||||
header.Reserved = 0;
|
||||
header.Reserved2 = 0;
|
||||
|
||||
if (tocFileStream.Length > 0)
|
||||
{
|
||||
tocFileStream.Seek(0, SeekOrigin.Begin);
|
||||
tocFileStream.SetLength(0);
|
||||
}
|
||||
|
||||
writer.Write(ref header);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the shader program info from the cache.
|
||||
/// </summary>
|
||||
/// <param name="dataReader">Cache data reader</param>
|
||||
/// <returns>Shader program info</returns>
|
||||
private static ShaderProgramInfo ReadShaderProgramInfo(ref BinarySerializer dataReader)
|
||||
{
|
||||
DataShaderInfo dataInfo = new DataShaderInfo();
|
||||
|
||||
dataReader.ReadWithMagicAndSize(ref dataInfo, ShdiMagic);
|
||||
|
||||
BufferDescriptor[] cBuffers = new BufferDescriptor[dataInfo.CBuffersCount];
|
||||
BufferDescriptor[] sBuffers = new BufferDescriptor[dataInfo.SBuffersCount];
|
||||
TextureDescriptor[] textures = new TextureDescriptor[dataInfo.TexturesCount];
|
||||
TextureDescriptor[] images = new TextureDescriptor[dataInfo.ImagesCount];
|
||||
|
||||
for (int index = 0; index < dataInfo.CBuffersCount; index++)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref cBuffers[index], BufdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < dataInfo.SBuffersCount; index++)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref sBuffers[index], BufdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < dataInfo.TexturesCount; index++)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref textures[index], TexdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < dataInfo.ImagesCount; index++)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref images[index], TexdMagic);
|
||||
}
|
||||
|
||||
return new ShaderProgramInfo(
|
||||
cBuffers,
|
||||
sBuffers,
|
||||
textures,
|
||||
images,
|
||||
dataInfo.Stage,
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesRtLayer,
|
||||
dataInfo.ClipDistancesWritten,
|
||||
dataInfo.FragmentOutputMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the shader program info into the cache.
|
||||
/// </summary>
|
||||
/// <param name="dataWriter">Cache data writer</param>
|
||||
/// <param name="info">Program info</param>
|
||||
private static void WriteShaderProgramInfo(ref BinarySerializer dataWriter, ShaderProgramInfo info)
|
||||
{
|
||||
if (info == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DataShaderInfo dataInfo = new DataShaderInfo();
|
||||
|
||||
dataInfo.CBuffersCount = (ushort)info.CBuffers.Count;
|
||||
dataInfo.SBuffersCount = (ushort)info.SBuffers.Count;
|
||||
dataInfo.TexturesCount = (ushort)info.Textures.Count;
|
||||
dataInfo.ImagesCount = (ushort)info.Images.Count;
|
||||
dataInfo.Stage = info.Stage;
|
||||
dataInfo.UsesInstanceId = info.UsesInstanceId;
|
||||
dataInfo.UsesRtLayer = info.UsesRtLayer;
|
||||
dataInfo.ClipDistancesWritten = info.ClipDistancesWritten;
|
||||
dataInfo.FragmentOutputMap = info.FragmentOutputMap;
|
||||
|
||||
dataWriter.WriteWithMagicAndSize(ref dataInfo, ShdiMagic);
|
||||
|
||||
for (int index = 0; index < info.CBuffers.Count; index++)
|
||||
{
|
||||
var entry = info.CBuffers[index];
|
||||
dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.SBuffers.Count; index++)
|
||||
{
|
||||
var entry = info.SBuffers[index];
|
||||
dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Textures.Count; index++)
|
||||
{
|
||||
var entry = info.Textures[index];
|
||||
dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.Images.Count; index++)
|
||||
{
|
||||
var entry = info.Images[index];
|
||||
dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Disk cache load exception.
|
||||
/// </summary>
|
||||
class DiskCacheLoadException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of the cache load operation.
|
||||
/// </summary>
|
||||
public DiskCacheLoadResult Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the disk cache load exception.
|
||||
/// </summary>
|
||||
public DiskCacheLoadException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the disk cache load exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
public DiskCacheLoadException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the disk cache load exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Exception message</param>
|
||||
/// <param name="inner">Inner exception</param>
|
||||
public DiskCacheLoadException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the disk cache load exception.
|
||||
/// </summary>
|
||||
/// <param name="result">Result code</param>
|
||||
public DiskCacheLoadException(DiskCacheLoadResult result) : base(result.GetMessage())
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
}
|
72
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
Normal file
72
Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a shader cache load operation.
|
||||
/// </summary>
|
||||
enum DiskCacheLoadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// No error.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// File can't be accessed.
|
||||
/// </summary>
|
||||
NoAccess,
|
||||
|
||||
/// <summary>
|
||||
/// The constant buffer 1 data length is too low for the translation of the guest shader.
|
||||
/// </summary>
|
||||
InvalidCb1DataLength,
|
||||
|
||||
/// <summary>
|
||||
/// The cache is missing the descriptor of a texture used by the shader.
|
||||
/// </summary>
|
||||
MissingTextureDescriptor,
|
||||
|
||||
/// <summary>
|
||||
/// File is corrupted.
|
||||
/// </summary>
|
||||
FileCorruptedGeneric,
|
||||
|
||||
/// <summary>
|
||||
/// File is corrupted, detected by magic value check.
|
||||
/// </summary>
|
||||
FileCorruptedInvalidMagic,
|
||||
|
||||
/// <summary>
|
||||
/// File is corrupted, detected by length check.
|
||||
/// </summary>
|
||||
FileCorruptedInvalidLength,
|
||||
|
||||
/// <summary>
|
||||
/// File might be valid, but is incompatible with the current emulator version.
|
||||
/// </summary>
|
||||
IncompatibleVersion
|
||||
}
|
||||
|
||||
static class DiskCacheLoadResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an error message from a result code.
|
||||
/// </summary>
|
||||
/// <param name="result">Result code</param>
|
||||
/// <returns>Error message</returns>
|
||||
public static string GetMessage(this DiskCacheLoadResult result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
DiskCacheLoadResult.Success => "No error.",
|
||||
DiskCacheLoadResult.NoAccess => "Could not access the cache file.",
|
||||
DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.",
|
||||
DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.",
|
||||
DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.",
|
||||
DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.",
|
||||
DiskCacheLoadResult.FileCorruptedInvalidLength => "Length check failed, the cache file is corrupted.",
|
||||
DiskCacheLoadResult.IncompatibleVersion => "The version of the disk cache is not compatible with this version of the emulator.",
|
||||
_ => "Unknown error."
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Output streams for the disk shader cache.
|
||||
/// </summary>
|
||||
class DiskCacheOutputStreams : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared table of contents (TOC) file stream.
|
||||
/// </summary>
|
||||
public readonly FileStream TocFileStream;
|
||||
|
||||
/// <summary>
|
||||
/// Shared data file stream.
|
||||
/// </summary>
|
||||
public readonly FileStream DataFileStream;
|
||||
|
||||
/// <summary>
|
||||
/// Host table of contents (TOC) file stream.
|
||||
/// </summary>
|
||||
public readonly FileStream HostTocFileStream;
|
||||
|
||||
/// <summary>
|
||||
/// Host data file stream.
|
||||
/// </summary>
|
||||
public readonly FileStream HostDataFileStream;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a disk cache output stream container.
|
||||
/// </summary>
|
||||
/// <param name="tocFileStream">Stream for the shared table of contents file</param>
|
||||
/// <param name="dataFileStream">Stream for the shared data file</param>
|
||||
/// <param name="hostTocFileStream">Stream for the host table of contents file</param>
|
||||
/// <param name="hostDataFileStream">Stream for the host data file</param>
|
||||
public DiskCacheOutputStreams(FileStream tocFileStream, FileStream dataFileStream, FileStream hostTocFileStream, FileStream hostDataFileStream)
|
||||
{
|
||||
TocFileStream = tocFileStream;
|
||||
DataFileStream = dataFileStream;
|
||||
HostTocFileStream = hostTocFileStream;
|
||||
HostDataFileStream = hostDataFileStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the output file streams.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
TocFileStream.Dispose();
|
||||
DataFileStream.Dispose();
|
||||
HostTocFileStream.Dispose();
|
||||
HostDataFileStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
672
Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
Normal file
672
Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs
Normal file
|
@ -0,0 +1,672 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using static Ryujinx.Graphics.Gpu.Shader.ShaderCache;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
class ParallelDiskCacheLoader
|
||||
{
|
||||
private const int ThreadCount = 8;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly ShaderCacheHashTable _graphicsCache;
|
||||
private readonly ComputeShaderCacheHashTable _computeCache;
|
||||
private readonly DiskCacheHostStorage _hostStorage;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
private readonly Action<ShaderCacheState, int, int> _stateChangeCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the cache should be loaded.
|
||||
/// </summary>
|
||||
public bool Active => !_cancellationToken.IsCancellationRequested;
|
||||
|
||||
private bool _needsHostRegen;
|
||||
|
||||
/// <summary>
|
||||
/// Number of shaders that failed to compile from the cache.
|
||||
/// </summary>
|
||||
public int ErrorCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program validation entry.
|
||||
/// </summary>
|
||||
private struct ProgramEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader program.
|
||||
/// </summary>
|
||||
public readonly CachedShaderProgram CachedProgram;
|
||||
|
||||
/// <summary>
|
||||
/// Host program.
|
||||
/// </summary>
|
||||
public readonly IProgram HostProgram;
|
||||
|
||||
/// <summary>
|
||||
/// Program index.
|
||||
/// </summary>
|
||||
public readonly int ProgramIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the program is a compute shader.
|
||||
/// </summary>
|
||||
public readonly bool IsCompute;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the program is a host binary shader.
|
||||
/// </summary>
|
||||
public readonly bool IsBinary;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new program validation entry.
|
||||
/// </summary>
|
||||
/// <param name="cachedProgram">Cached shader program</param>
|
||||
/// <param name="hostProgram">Host program</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
/// <param name="isBinary">Indicates if the program is a host binary shader</param>
|
||||
public ProgramEntry(
|
||||
CachedShaderProgram cachedProgram,
|
||||
IProgram hostProgram,
|
||||
int programIndex,
|
||||
bool isCompute,
|
||||
bool isBinary)
|
||||
{
|
||||
CachedProgram = cachedProgram;
|
||||
HostProgram = hostProgram;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
IsBinary = isBinary;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translated shader compilation entry.
|
||||
/// </summary>
|
||||
private struct ProgramCompilation
|
||||
{
|
||||
/// <summary>
|
||||
/// Translated shader stages.
|
||||
/// </summary>
|
||||
public readonly ShaderProgram[] TranslatedStages;
|
||||
|
||||
/// <summary>
|
||||
/// Cached shaders.
|
||||
/// </summary>
|
||||
public readonly CachedShaderStage[] Shaders;
|
||||
|
||||
/// <summary>
|
||||
/// Specialization state.
|
||||
/// </summary>
|
||||
public readonly ShaderSpecializationState SpecializationState;
|
||||
|
||||
/// <summary>
|
||||
/// Program index.
|
||||
/// </summary>
|
||||
public readonly int ProgramIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the program is a compute shader.
|
||||
/// </summary>
|
||||
public readonly bool IsCompute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new translated shader compilation entry.
|
||||
/// </summary>
|
||||
/// <param name="translatedStages">Translated shader stages</param>
|
||||
/// <param name="shaders">Cached shaders</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public ProgramCompilation(
|
||||
ShaderProgram[] translatedStages,
|
||||
CachedShaderStage[] shaders,
|
||||
ShaderSpecializationState specState,
|
||||
int programIndex,
|
||||
bool isCompute)
|
||||
{
|
||||
TranslatedStages = translatedStages;
|
||||
Shaders = shaders;
|
||||
SpecializationState = specState;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Program translation entry.
|
||||
/// </summary>
|
||||
private struct AsyncProgramTranslation
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader stages.
|
||||
/// </summary>
|
||||
public readonly CachedShaderStage[] Shaders;
|
||||
|
||||
/// <summary>
|
||||
/// Specialization state.
|
||||
/// </summary>
|
||||
public readonly ShaderSpecializationState SpecializationState;
|
||||
|
||||
/// <summary>
|
||||
/// Program index.
|
||||
/// </summary>
|
||||
public readonly int ProgramIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the program is a compute shader.
|
||||
/// </summary>
|
||||
public readonly bool IsCompute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new program translation entry.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Cached shader stages</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public AsyncProgramTranslation(
|
||||
CachedShaderStage[] shaders,
|
||||
ShaderSpecializationState specState,
|
||||
int programIndex,
|
||||
bool isCompute)
|
||||
{
|
||||
Shaders = shaders;
|
||||
SpecializationState = specState;
|
||||
ProgramIndex = programIndex;
|
||||
IsCompute = isCompute;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<ProgramEntry> _validationQueue;
|
||||
private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue;
|
||||
private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue;
|
||||
private readonly SortedList<int, CachedShaderProgram> _programList;
|
||||
|
||||
private int _backendParallelCompileThreads;
|
||||
private int _compiledCount;
|
||||
private int _totalCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new parallel disk cache loader.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="graphicsCache">Graphics shader cache</param>
|
||||
/// <param name="computeCache">Compute shader cache</param>
|
||||
/// <param name="hostStorage">Disk cache host storage</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <param name="stateChangeCallback">Function to be called when there is a state change, reporting state, compiled and total shaders count</param>
|
||||
public ParallelDiskCacheLoader(
|
||||
GpuContext context,
|
||||
ShaderCacheHashTable graphicsCache,
|
||||
ComputeShaderCacheHashTable computeCache,
|
||||
DiskCacheHostStorage hostStorage,
|
||||
CancellationToken cancellationToken,
|
||||
Action<ShaderCacheState, int, int> stateChangeCallback)
|
||||
{
|
||||
_context = context;
|
||||
_graphicsCache = graphicsCache;
|
||||
_computeCache = computeCache;
|
||||
_hostStorage = hostStorage;
|
||||
_cancellationToken = cancellationToken;
|
||||
_stateChangeCallback = stateChangeCallback;
|
||||
_validationQueue = new Queue<ProgramEntry>();
|
||||
_compilationQueue = new ConcurrentQueue<ProgramCompilation>();
|
||||
_asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount);
|
||||
_programList = new SortedList<int, CachedShaderProgram>();
|
||||
_backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all shaders from the cache.
|
||||
/// </summary>
|
||||
public void LoadShaders()
|
||||
{
|
||||
Thread[] workThreads = new Thread[ThreadCount];
|
||||
|
||||
for (int index = 0; index < ThreadCount; index++)
|
||||
{
|
||||
workThreads[index] = new Thread(ProcessAsyncQueue)
|
||||
{
|
||||
Name = $"Gpu.AsyncTranslationThread.{index}"
|
||||
};
|
||||
}
|
||||
|
||||
int programCount = _hostStorage.GetProgramCount();
|
||||
|
||||
_compiledCount = 0;
|
||||
_totalCount = programCount;
|
||||
|
||||
_stateChangeCallback(ShaderCacheState.Start, 0, programCount);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Loading {programCount} shaders from the cache...");
|
||||
|
||||
for (int index = 0; index < ThreadCount; index++)
|
||||
{
|
||||
workThreads[index].Start(_cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_hostStorage.LoadShaders(_context, this);
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Error loading the shader cache. {diskCacheLoadException.Message}");
|
||||
|
||||
// If we can't even access the file, then we also can't rebuild.
|
||||
if (diskCacheLoadException.Result != DiskCacheLoadResult.NoAccess)
|
||||
{
|
||||
_needsHostRegen = true;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException invalidDataException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Error decompressing the shader cache file. {invalidDataException.Message}");
|
||||
_needsHostRegen = true;
|
||||
}
|
||||
catch (IOException ioException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Error reading the shader cache file. {ioException.Message}");
|
||||
_needsHostRegen = true;
|
||||
}
|
||||
|
||||
_asyncTranslationQueue.CompleteAdding();
|
||||
|
||||
for (int index = 0; index < ThreadCount; index++)
|
||||
{
|
||||
workThreads[index].Join();
|
||||
}
|
||||
|
||||
CheckCompilationBlocking();
|
||||
|
||||
if (_needsHostRegen)
|
||||
{
|
||||
// Rebuild both shared and host cache files.
|
||||
// Rebuilding shared is required because the shader information returned by the translator
|
||||
// might have changed, and so we have to reconstruct the file with the new information.
|
||||
try
|
||||
{
|
||||
_hostStorage.ClearSharedCache();
|
||||
_hostStorage.ClearHostCache(_context);
|
||||
|
||||
if (_programList.Count != 0)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders...");
|
||||
|
||||
using var streams = _hostStorage.GetOutputStreams(_context);
|
||||
|
||||
foreach (var kv in _programList)
|
||||
{
|
||||
if (!Active)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
CachedShaderProgram program = kv.Value;
|
||||
_hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams);
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_hostStorage.ClearGuestCache();
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, "Shader cache deleted due to corruption.");
|
||||
}
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache. {diskCacheLoadException.Message}");
|
||||
}
|
||||
catch (IOException ioException)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache file. {ioException.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
|
||||
|
||||
_stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a host program for compilation.
|
||||
/// </summary>
|
||||
/// <param name="cachedProgram">Cached program</param>
|
||||
/// <param name="hostProgram">Host program to be compiled</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public void QueueHostProgram(CachedShaderProgram cachedProgram, IProgram hostProgram, int programIndex, bool isCompute)
|
||||
{
|
||||
EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a guest program for compilation.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Cached shader stages</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
public void QueueGuestProgram(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
{
|
||||
_asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the state of programs that have already been compiled,
|
||||
/// and add to the cache if the compilation was successful.
|
||||
/// </summary>
|
||||
public void CheckCompilation()
|
||||
{
|
||||
ProcessCompilationQueue();
|
||||
|
||||
// Process programs that already finished compiling.
|
||||
// If not yet compiled, do nothing. This avoids blocking to wait for shader compilation.
|
||||
while (_validationQueue.TryPeek(out ProgramEntry entry))
|
||||
{
|
||||
ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false);
|
||||
|
||||
if (result != ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
ProcessCompiledProgram(ref entry, result);
|
||||
_validationQueue.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until all programs finishes compiling, then adds the ones
|
||||
/// with successful compilation to the cache.
|
||||
/// </summary>
|
||||
private void CheckCompilationBlocking()
|
||||
{
|
||||
ProcessCompilationQueue();
|
||||
|
||||
while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active)
|
||||
{
|
||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a compiled program result.
|
||||
/// </summary>
|
||||
/// <param name="entry">Compiled program entry</param>
|
||||
/// <param name="result">Compilation result</param>
|
||||
/// <param name="asyncCompile">For failed host compilations, indicates if a guest compilation should be done asynchronously</param>
|
||||
private void ProcessCompiledProgram(ref ProgramEntry entry, ProgramLinkStatus result, bool asyncCompile = true)
|
||||
{
|
||||
if (result == ProgramLinkStatus.Success)
|
||||
{
|
||||
// Compilation successful, add to memory cache.
|
||||
if (entry.IsCompute)
|
||||
{
|
||||
_computeCache.Add(entry.CachedProgram);
|
||||
}
|
||||
else
|
||||
{
|
||||
_graphicsCache.Add(entry.CachedProgram);
|
||||
}
|
||||
|
||||
if (!entry.IsBinary)
|
||||
{
|
||||
_needsHostRegen = true;
|
||||
}
|
||||
|
||||
_programList.Add(entry.ProgramIndex, entry.CachedProgram);
|
||||
SignalCompiled();
|
||||
}
|
||||
else if (entry.IsBinary)
|
||||
{
|
||||
// If this is a host binary and compilation failed,
|
||||
// we still have a chance to recompile from the guest binary.
|
||||
CachedShaderProgram program = entry.CachedProgram;
|
||||
|
||||
if (asyncCompile)
|
||||
{
|
||||
QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute);
|
||||
ProcessCompilationQueue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Failed to compile from both host and guest binary.
|
||||
ErrorCount++;
|
||||
SignalCompiled();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the queue of translated guest programs that should be compiled on the host.
|
||||
/// </summary>
|
||||
private void ProcessCompilationQueue()
|
||||
{
|
||||
while (_compilationQueue.TryDequeue(out ProgramCompilation compilation) && Active)
|
||||
{
|
||||
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
|
||||
|
||||
int fragmentOutputMap = -1;
|
||||
|
||||
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
|
||||
{
|
||||
ShaderProgram shader = compilation.TranslatedStages[index];
|
||||
shaderSources[index] = CreateShaderSource(shader);
|
||||
|
||||
if (shader.Info.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
fragmentOutputMap = shader.Info.FragmentOutputMap;
|
||||
}
|
||||
}
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap));
|
||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
|
||||
|
||||
EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a program for validation, which will check if the program was compiled successfully.
|
||||
/// </summary>
|
||||
/// <param name="newEntry">Program entry to be validated</param>
|
||||
private void EnqueueForValidation(ProgramEntry newEntry)
|
||||
{
|
||||
_validationQueue.Enqueue(newEntry);
|
||||
|
||||
// Do not allow more than N shader compilation in-flight, where N is the maximum number of threads
|
||||
// the driver will be using for parallel compilation.
|
||||
// Submitting more seems to cause NVIDIA OpenGL driver to crash.
|
||||
if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry))
|
||||
{
|
||||
ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processses the queue of programs that should be translated from guest code.
|
||||
/// </summary>
|
||||
/// <param name="state">Cancellation token</param>
|
||||
private void ProcessAsyncQueue(object state)
|
||||
{
|
||||
CancellationToken ct = (CancellationToken)state;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct))
|
||||
{
|
||||
RecompileFromGuestCode(
|
||||
asyncCompilation.Shaders,
|
||||
asyncCompilation.SpecializationState,
|
||||
asyncCompilation.ProgramIndex,
|
||||
asyncCompilation.IsCompute);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recompiles a program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
/// <param name="isCompute">Indicates if the program is a compute shader</param>
|
||||
private void RecompileFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isCompute)
|
||||
{
|
||||
RecompileComputeFromGuestCode(shaders, specState, programIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecompileGraphicsFromGuestCode(shaders, specState, programIndex);
|
||||
}
|
||||
}
|
||||
catch (DiskCacheLoadException diskCacheLoadException)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Error translating guest shader. {diskCacheLoadException.Message}");
|
||||
|
||||
ErrorCount++;
|
||||
SignalCompiled();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recompiles a graphics program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
||||
{
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
|
||||
ResourceCounts counts = new ResourceCounts();
|
||||
|
||||
TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
|
||||
TranslatorContext nextStage = null;
|
||||
|
||||
for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
|
||||
{
|
||||
CachedShaderStage shader = shaders[stageIndex + 1];
|
||||
|
||||
if (shader != null)
|
||||
{
|
||||
byte[] guestCode = shader.Code;
|
||||
byte[] cb1Data = shader.Cb1Data;
|
||||
|
||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
|
||||
TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);
|
||||
|
||||
if (nextStage != null)
|
||||
{
|
||||
currentStage.SetNextStage(nextStage);
|
||||
}
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
{
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
|
||||
DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
|
||||
translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
|
||||
}
|
||||
|
||||
translatorContexts[stageIndex + 1] = currentStage;
|
||||
nextStage = currentStage;
|
||||
}
|
||||
}
|
||||
|
||||
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
TranslatorContext currentStage = translatorContexts[stageIndex + 1];
|
||||
|
||||
if (currentStage != null)
|
||||
{
|
||||
ShaderProgram program;
|
||||
|
||||
byte[] guestCode = shaders[stageIndex + 1].Code;
|
||||
byte[] cb1Data = shaders[stageIndex + 1].Cb1Data;
|
||||
|
||||
if (stageIndex == 0 && shaders[0] != null)
|
||||
{
|
||||
program = currentStage.Translate(translatorContexts[0]);
|
||||
|
||||
byte[] guestCodeA = shaders[0].Code;
|
||||
byte[] cb1DataA = shaders[0].Cb1Data;
|
||||
|
||||
shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
|
||||
shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
program = currentStage.Translate();
|
||||
|
||||
shaders[stageIndex + 1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
|
||||
}
|
||||
|
||||
if (program != null)
|
||||
{
|
||||
translatedStages.Add(program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_compilationQueue.Enqueue(new ProgramCompilation(translatedStages.ToArray(), shaders, newSpecState, programIndex, isCompute: false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recompiles a compute program from guest code.
|
||||
/// </summary>
|
||||
/// <param name="shaders">Shader stages</param>
|
||||
/// <param name="specState">Specialization state</param>
|
||||
/// <param name="programIndex">Program index</param>
|
||||
private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
|
||||
{
|
||||
CachedShaderStage shader = shaders[0];
|
||||
ResourceCounts counts = new ResourceCounts();
|
||||
ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
|
||||
DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);
|
||||
|
||||
ShaderProgram program = translatorContext.Translate();
|
||||
|
||||
shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);
|
||||
|
||||
_compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals that compilation of a program has been finished successfully,
|
||||
/// or that it failed and guest recompilation has also been attempted.
|
||||
/// </summary>
|
||||
private void SignalCompiled()
|
||||
{
|
||||
_stateChangeCallback(ShaderCacheState.Loading, ++_compiledCount, _totalCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -9,19 +9,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <summary>
|
||||
/// Represents a GPU state and memory accessor.
|
||||
/// </summary>
|
||||
class GpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
|
||||
class GpuAccessor : GpuAccessorBase, IGpuAccessor
|
||||
{
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly GpuAccessorState _state;
|
||||
private readonly int _stageIndex;
|
||||
private readonly bool _compute;
|
||||
private readonly int _localSizeX;
|
||||
private readonly int _localSizeY;
|
||||
private readonly int _localSizeZ;
|
||||
private readonly int _localMemorySize;
|
||||
private readonly int _sharedMemorySize;
|
||||
|
||||
public int Cb1DataSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU state accessor for graphics shader translation.
|
||||
|
@ -43,43 +36,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="localSizeX">Local group size X of the compute shader</param>
|
||||
/// <param name="localSizeY">Local group size Y of the compute shader</param>
|
||||
/// <param name="localSizeZ">Local group size Z of the compute shader</param>
|
||||
/// <param name="localMemorySize">Local memory size of the compute shader</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
public GpuAccessor(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
GpuAccessorState state,
|
||||
int localSizeX,
|
||||
int localSizeY,
|
||||
int localSizeZ,
|
||||
int localMemorySize,
|
||||
int sharedMemorySize) : base(context)
|
||||
public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context)
|
||||
{
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_compute = true;
|
||||
_localSizeX = localSizeX;
|
||||
_localSizeY = localSizeY;
|
||||
_localSizeZ = localSizeZ;
|
||||
_localMemorySize = localMemorySize;
|
||||
_sharedMemorySize = sharedMemorySize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the constant buffer 1.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset in bytes to read from</param>
|
||||
/// <returns>Value at the given offset</returns>
|
||||
/// <inheritdoc/>
|
||||
public uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
if (Cb1DataSize < offset + 4)
|
||||
{
|
||||
Cb1DataSize = offset + 4;
|
||||
}
|
||||
|
||||
ulong baseAddress = _compute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(1)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1);
|
||||
|
@ -87,111 +53,115 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
return _channel.MemoryManager.Physical.Read<uint>(baseAddress + (ulong)offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a log message.
|
||||
/// </summary>
|
||||
/// <param name="message">Message to print</param>
|
||||
/// <inheritdoc/>
|
||||
public void Log(string message)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span of the specified memory location, containing shader code.
|
||||
/// </summary>
|
||||
/// <param name="address">GPU virtual address of the data</param>
|
||||
/// <param name="minimumSize">Minimum size that the returned span may have</param>
|
||||
/// <returns>Span of the memory location</returns>
|
||||
public override ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize)
|
||||
{
|
||||
int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff));
|
||||
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size X for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size X</returns>
|
||||
public int QueryComputeLocalSizeX() => _localSizeX;
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
{
|
||||
return _state.ResourceCounts.UniformBuffersCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Y for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Y</returns>
|
||||
public int QueryComputeLocalSizeY() => _localSizeY;
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
return _state.ResourceCounts.StorageBuffersCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Z for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Z</returns>
|
||||
public int QueryComputeLocalSizeZ() => _localSizeZ;
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingTexture(int index)
|
||||
{
|
||||
return _state.ResourceCounts.TexturesCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Memory size in bytes</returns>
|
||||
public int QueryComputeLocalMemorySize() => _localMemorySize;
|
||||
/// <inheritdoc/>
|
||||
public int QueryBindingImage(int index)
|
||||
{
|
||||
return _state.ResourceCounts.ImagesCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Shared Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Shared Memory size in bytes</returns>
|
||||
public int QueryComputeSharedMemorySize() => _sharedMemorySize;
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX;
|
||||
|
||||
/// <summary>
|
||||
/// Queries Constant Buffer usage information.
|
||||
/// </summary>
|
||||
/// <returns>A mask where each bit set indicates a bound constant buffer</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeY() => _state.ComputeState.LocalSizeY;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalSizeZ() => _state.ComputeState.LocalSizeZ;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeLocalMemorySize() => _state.ComputeState.LocalMemorySize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int QueryComputeSharedMemorySize() => _state.ComputeState.SharedMemorySize;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint QueryConstantBufferUse()
|
||||
{
|
||||
return _compute
|
||||
uint useMask = _compute
|
||||
? _channel.BufferManager.GetComputeUniformBufferUseMask()
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex);
|
||||
|
||||
_state.SpecializationState?.RecordConstantBufferUse(_stageIndex, useMask);
|
||||
return useMask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries current primitive topology for geometry shaders.
|
||||
/// </summary>
|
||||
/// <returns>Current primitive topology</returns>
|
||||
/// <inheritdoc/>
|
||||
public InputTopology QueryPrimitiveTopology()
|
||||
{
|
||||
return _state.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 => _state.TessellationMode.UnpackPatchType() == TessPatchType.Isolines
|
||||
? InputTopology.Lines
|
||||
: InputTopology.Triangles,
|
||||
_ => InputTopology.Points
|
||||
};
|
||||
_state.SpecializationState?.RecordPrimitiveTopology();
|
||||
return ConvertToInputTopology(_state.GraphicsState.Topology, _state.GraphicsState.TessellationMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader primitive winding order.
|
||||
/// </summary>
|
||||
/// <returns>True if the primitive winding order is clockwise, false if counter-clockwise</returns>
|
||||
public bool QueryTessCw() => _state.TessellationMode.UnpackCw();
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTessCw()
|
||||
{
|
||||
return _state.GraphicsState.TessellationMode.UnpackCw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader abstract patch type.
|
||||
/// </summary>
|
||||
/// <returns>Abstract patch type</returns>
|
||||
public TessPatchType QueryTessPatchType() => _state.TessellationMode.UnpackPatchType();
|
||||
/// <inheritdoc/>
|
||||
public TessPatchType QueryTessPatchType()
|
||||
{
|
||||
return _state.GraphicsState.TessellationMode.UnpackPatchType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the tessellation evaluation shader spacing between tessellated vertices of the patch.
|
||||
/// </summary>
|
||||
/// <returns>Spacing between tessellated vertices of the patch</returns>
|
||||
public TessSpacing QueryTessSpacing() => _state.TessellationMode.UnpackSpacing();
|
||||
/// <inheritdoc/>
|
||||
public TessSpacing QueryTessSpacing()
|
||||
{
|
||||
return _state.GraphicsState.TessellationMode.UnpackSpacing();
|
||||
}
|
||||
|
||||
//// <inheritdoc/>
|
||||
public TextureFormat QueryTextureFormat(int handle, int cbufSlot)
|
||||
{
|
||||
_state.SpecializationState?.RecordTextureFormat(_stageIndex, handle, cbufSlot);
|
||||
var descriptor = GetTextureDescriptor(handle, cbufSlot);
|
||||
return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SamplerType QuerySamplerType(int handle, int cbufSlot)
|
||||
{
|
||||
_state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot);
|
||||
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTextureCoordNormalized(int handle, int cbufSlot)
|
||||
{
|
||||
_state.SpecializationState?.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot);
|
||||
return GetTextureDescriptor(handle, cbufSlot).UnpackTextureCoordNormalized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture on the pool.
|
||||
|
@ -199,65 +169,58 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <param name="handle">Index of the texture (this is the word offset of the handle in the constant buffer)</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>Texture descriptor</returns>
|
||||
public override Image.ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
|
||||
private Image.TextureDescriptor GetTextureDescriptor(int handle, int cbufSlot)
|
||||
{
|
||||
if (_compute)
|
||||
{
|
||||
return _channel.TextureManager.GetComputeTextureDescriptor(
|
||||
_state.TexturePoolGpuVa,
|
||||
_state.TextureBufferIndex,
|
||||
_state.TexturePoolMaximumId,
|
||||
_state.PoolState.TexturePoolGpuVa,
|
||||
_state.PoolState.TextureBufferIndex,
|
||||
_state.PoolState.TexturePoolMaximumId,
|
||||
handle,
|
||||
cbufSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _channel.TextureManager.GetGraphicsTextureDescriptor(
|
||||
_state.TexturePoolGpuVa,
|
||||
_state.TextureBufferIndex,
|
||||
_state.TexturePoolMaximumId,
|
||||
_state.PoolState.TexturePoolGpuVa,
|
||||
_state.PoolState.TextureBufferIndex,
|
||||
_state.PoolState.TexturePoolMaximumId,
|
||||
_stageIndex,
|
||||
handle,
|
||||
cbufSlot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries transform feedback enable state.
|
||||
/// </summary>
|
||||
/// <returns>True if the shader uses transform feedback, false otherwise</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
return _state.TransformFeedbackDescriptors != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the varying locations that should be written to the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Varying locations for the specified buffer</returns>
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
|
||||
{
|
||||
return _state.TransformFeedbackDescriptors[bufferIndex].VaryingLocations;
|
||||
return _state.TransformFeedbackDescriptors[bufferIndex].AsSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Stride for the specified buffer</returns>
|
||||
/// <inheritdoc/>
|
||||
public int QueryTransformFeedbackStride(int bufferIndex)
|
||||
{
|
||||
return _state.TransformFeedbackDescriptors[bufferIndex].Stride;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries if host state forces early depth testing.
|
||||
/// </summary>
|
||||
/// <returns>True if early depth testing is forced</returns>
|
||||
/// <inheritdoc/>
|
||||
public bool QueryEarlyZForce()
|
||||
{
|
||||
return _state.EarlyZForce;
|
||||
_state.SpecializationState?.RecordEarlyZForce();
|
||||
return _state.GraphicsState.EarlyZForce;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RegisterTexture(int handle, int cbufSlot)
|
||||
{
|
||||
_state.SpecializationState?.RegisterTexture(_stageIndex, handle, cbufSlot, GetTextureDescriptor(handle, cbufSlot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
abstract class TextureDescriptorCapableGpuAccessor : IGpuAccessor
|
||||
/// <summary>
|
||||
/// GPU accessor.
|
||||
/// </summary>
|
||||
class GpuAccessorBase
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
|
||||
public TextureDescriptorCapableGpuAccessor(GpuContext context)
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public GpuAccessorBase(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public abstract ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
|
||||
|
||||
public abstract ITextureDescriptor GetTextureDescriptor(int handle, int cbufSlot);
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
|
@ -79,20 +82,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||
|
||||
/// <summary>
|
||||
/// Queries texture format information, for shaders using image load or store.
|
||||
/// Converts a packed Maxwell texture format to the shader translator texture format.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only returns non-compressed color formats.
|
||||
/// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
|
||||
/// </remarks>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>Color format of the non-compressed texture</returns>
|
||||
public TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
|
||||
/// <param name="format">Packed maxwell format</param>
|
||||
/// <param name="formatSrgb">Indicates if the format is sRGB</param>
|
||||
/// <returns>Shader translator texture format</returns>
|
||||
protected static TextureFormat ConvertToTextureFormat(uint format, bool formatSrgb)
|
||||
{
|
||||
var descriptor = GetTextureDescriptor(handle, cbufSlot);
|
||||
|
||||
if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
|
||||
if (!FormatTable.TryGetTextureFormat(format, formatSrgb, out FormatInfo formatInfo))
|
||||
{
|
||||
return TextureFormat.Unknown;
|
||||
}
|
||||
|
@ -144,32 +141,31 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries sampler type information.
|
||||
/// Converts the Maxwell primitive topology to the shader translator topology.
|
||||
/// </summary>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>The sampler type value for the given handle</returns>
|
||||
public SamplerType QuerySamplerType(int handle, int cbufSlot = -1)
|
||||
/// <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 GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries texture target information.
|
||||
/// </summary>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>True if the texture is a rectangle texture, false otherwise</returns>
|
||||
public bool QueryIsTextureRectangle(int handle, int cbufSlot = -1)
|
||||
{
|
||||
var descriptor = GetTextureDescriptor(handle, cbufSlot);
|
||||
|
||||
TextureTarget target = descriptor.UnpackTextureTarget();
|
||||
|
||||
bool is2DTexture = target == TextureTarget.Texture2D ||
|
||||
target == TextureTarget.Texture2DRect;
|
||||
|
||||
return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +1,61 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// State used by the <see cref="GpuAccessor"/>.
|
||||
/// </summary>
|
||||
struct GpuAccessorState
|
||||
class GpuAccessorState
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address of the texture pool.
|
||||
/// GPU texture pool state.
|
||||
/// </summary>
|
||||
public ulong TexturePoolGpuVa { get; }
|
||||
public readonly GpuChannelPoolState PoolState;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum ID of the texture pool.
|
||||
/// GPU compute state, for compute shaders.
|
||||
/// </summary>
|
||||
public int TexturePoolMaximumId { get; }
|
||||
public readonly GpuChannelComputeState ComputeState;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer slot where the texture handles are located.
|
||||
/// GPU graphics state, for vertex, tessellation, geometry and fragment shaders.
|
||||
/// </summary>
|
||||
public int TextureBufferIndex { get; }
|
||||
public readonly GpuChannelGraphicsState GraphicsState;
|
||||
|
||||
/// <summary>
|
||||
/// Early Z force enable.
|
||||
/// Shader specialization state (shared by all stages).
|
||||
/// </summary>
|
||||
public bool EarlyZForce { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Primitive topology of current draw.
|
||||
/// </summary>
|
||||
public PrimitiveTopology Topology { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tessellation mode.
|
||||
/// </summary>
|
||||
public TessMode TessellationMode { get; }
|
||||
public readonly ShaderSpecializationState SpecializationState;
|
||||
|
||||
/// <summary>
|
||||
/// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null.
|
||||
/// </summary>
|
||||
public TransformFeedbackDescriptor[] TransformFeedbackDescriptors { get; set; }
|
||||
public readonly TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU accessor state.
|
||||
/// Shader resource counts (shared by all stages).
|
||||
/// </summary>
|
||||
/// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
|
||||
/// <param name="earlyZForce">Early Z force enable</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="tessellationMode">Tessellation mode</param>
|
||||
public readonly ResourceCounts ResourceCounts;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor state.
|
||||
/// </summary>
|
||||
/// <param name="poolState">GPU texture pool state</param>
|
||||
/// <param name="computeState">GPU compute state, for compute shaders</param>
|
||||
/// <param name="graphicsState">GPU graphics state, for vertex, tessellation, geometry and fragment shaders</param>
|
||||
/// <param name="specializationState">Shader specialization state (shared by all stages)</param>
|
||||
/// <param name="transformFeedbackDescriptors">Transform feedback information, if the shader uses transform feedback. Otherwise, should be null</param>
|
||||
public GpuAccessorState(
|
||||
ulong texturePoolGpuVa,
|
||||
int texturePoolMaximumId,
|
||||
int textureBufferIndex,
|
||||
bool earlyZForce,
|
||||
PrimitiveTopology topology,
|
||||
TessMode tessellationMode)
|
||||
GpuChannelPoolState poolState,
|
||||
GpuChannelComputeState computeState,
|
||||
GpuChannelGraphicsState graphicsState,
|
||||
ShaderSpecializationState specializationState,
|
||||
TransformFeedbackDescriptor[] transformFeedbackDescriptors = null)
|
||||
{
|
||||
TexturePoolGpuVa = texturePoolGpuVa;
|
||||
TexturePoolMaximumId = texturePoolMaximumId;
|
||||
TextureBufferIndex = textureBufferIndex;
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
TessellationMode = tessellationMode;
|
||||
TransformFeedbackDescriptors = null;
|
||||
PoolState = poolState;
|
||||
GraphicsState = graphicsState;
|
||||
ComputeState = computeState;
|
||||
SpecializationState = specializationState;
|
||||
TransformFeedbackDescriptors = transformFeedbackDescriptors;
|
||||
ResourceCounts = new ResourceCounts();
|
||||
}
|
||||
}
|
||||
}
|
57
Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs
Normal file
57
Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// State used by the <see cref="GpuAccessor"/>.
|
||||
/// </summary>
|
||||
struct GpuChannelComputeState
|
||||
{
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
/// <summary>
|
||||
/// Local group size X of the compute shader.
|
||||
/// </summary>
|
||||
public readonly int LocalSizeX;
|
||||
|
||||
/// <summary>
|
||||
/// Local group size Y of the compute shader.
|
||||
/// </summary>
|
||||
public readonly int LocalSizeY;
|
||||
|
||||
/// <summary>
|
||||
/// Local group size Z of the compute shader.
|
||||
/// </summary>
|
||||
public readonly int LocalSizeZ;
|
||||
|
||||
/// <summary>
|
||||
/// Local memory size of the compute shader.
|
||||
/// </summary>
|
||||
public readonly int LocalMemorySize;
|
||||
|
||||
/// <summary>
|
||||
/// Shared memory size of the compute shader.
|
||||
/// </summary>
|
||||
public readonly int SharedMemorySize;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU compute state.
|
||||
/// </summary>
|
||||
/// <param name="localSizeX">Local group size X of the compute shader</param>
|
||||
/// <param name="localSizeY">Local group size Y of the compute shader</param>
|
||||
/// <param name="localSizeZ">Local group size Z of the compute shader</param>
|
||||
/// <param name="localMemorySize">Local memory size of the compute shader</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
public GpuChannelComputeState(
|
||||
int localSizeX,
|
||||
int localSizeY,
|
||||
int localSizeZ,
|
||||
int localMemorySize,
|
||||
int sharedMemorySize)
|
||||
{
|
||||
LocalSizeX = localSizeX;
|
||||
LocalSizeY = localSizeY;
|
||||
LocalSizeZ = localSizeZ;
|
||||
LocalMemorySize = localMemorySize;
|
||||
SharedMemorySize = sharedMemorySize;
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
Normal file
41
Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// State used by the <see cref="GpuAccessor"/>.
|
||||
/// </summary>
|
||||
struct GpuChannelGraphicsState
|
||||
{
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
/// <summary>
|
||||
/// Early Z force enable.
|
||||
/// </summary>
|
||||
public readonly bool EarlyZForce;
|
||||
|
||||
/// <summary>
|
||||
/// Primitive topology of current draw.
|
||||
/// </summary>
|
||||
public readonly PrimitiveTopology Topology;
|
||||
|
||||
/// <summary>
|
||||
/// Tessellation mode.
|
||||
/// </summary>
|
||||
public readonly TessMode TessellationMode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU graphics state.
|
||||
/// </summary>
|
||||
/// <param name="earlyZForce">Early Z force enable</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="tessellationMode">Tessellation mode</param>
|
||||
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode)
|
||||
{
|
||||
EarlyZForce = earlyZForce;
|
||||
Topology = topology;
|
||||
TessellationMode = tessellationMode;
|
||||
}
|
||||
}
|
||||
}
|
36
Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs
Normal file
36
Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// State used by the <see cref="GpuAccessor"/>.
|
||||
/// </summary>
|
||||
struct GpuChannelPoolState
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address of the texture pool.
|
||||
/// </summary>
|
||||
public readonly ulong TexturePoolGpuVa;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum ID of the texture pool.
|
||||
/// </summary>
|
||||
public readonly int TexturePoolMaximumId;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer slot where the texture handles are located.
|
||||
/// </summary>
|
||||
public readonly int TextureBufferIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU texture pool state.
|
||||
/// </summary>
|
||||
/// <param name="texturePoolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="texturePoolMaximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="textureBufferIndex">Constant buffer slot where the texture handles are located</param>
|
||||
public GpuChannelPoolState(ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex)
|
||||
{
|
||||
TexturePoolGpuVa = texturePoolGpuVa;
|
||||
TexturePoolMaximumId = texturePoolMaximumId;
|
||||
TextureBufferIndex = textureBufferIndex;
|
||||
}
|
||||
}
|
||||
}
|
113
Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs
Normal file
113
Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.HashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// State of a hash calculation.
|
||||
/// </summary>
|
||||
struct HashState
|
||||
{
|
||||
// This is using a slightly modified implementation of FastHash64.
|
||||
// Reference: https://github.com/ztanml/fast-hash/blob/master/fasthash.c
|
||||
private const ulong M = 0x880355f21e6d1965UL;
|
||||
private ulong _hash;
|
||||
private int _start;
|
||||
|
||||
/// <summary>
|
||||
/// One shot hash calculation for a given data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be hashed</param>
|
||||
/// <returns>Hash of the given data</returns>
|
||||
public static uint CalcHash(ReadOnlySpan<byte> data)
|
||||
{
|
||||
HashState state = new HashState();
|
||||
|
||||
state.Initialize();
|
||||
state.Continue(data);
|
||||
return state.Finalize(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the hash state.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
_hash = 23;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash of the given data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The full data must be passed on <paramref name="data"/>.
|
||||
/// If this is not the first time the method is called, then <paramref name="data"/> must start with the data passed on the last call.
|
||||
/// If a smaller slice of the data was already hashed before, only the additional data will be hashed.
|
||||
/// This can be used for additive hashing of data in chuncks.
|
||||
/// </remarks>
|
||||
/// <param name="data">Data to be hashed</param>
|
||||
public void Continue(ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong h = _hash;
|
||||
|
||||
ReadOnlySpan<ulong> dataAsUlong = MemoryMarshal.Cast<byte, ulong>(data.Slice(_start));
|
||||
|
||||
for (int i = 0; i < dataAsUlong.Length; i++)
|
||||
{
|
||||
ulong value = dataAsUlong[i];
|
||||
|
||||
h ^= Mix(value);
|
||||
h *= M;
|
||||
}
|
||||
|
||||
_hash = h;
|
||||
_start = data.Length & ~7;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the hash finalization step, and returns the calculated hash.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The full data must be passed on <paramref name="data"/>.
|
||||
/// <paramref name="data"/> must start with the data passed on the last call to <see cref="Continue"/>.
|
||||
/// No internal state is changed, so one can still continue hashing data with <see cref="Continue"/>
|
||||
/// after calling this method.
|
||||
/// </remarks>
|
||||
/// <param name="data">Data to be hashed</param>
|
||||
/// <returns>Hash of all the data hashed with this <see cref="HashState"/></returns>
|
||||
public uint Finalize(ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong h = _hash;
|
||||
|
||||
int remainder = data.Length & 7;
|
||||
if (remainder != 0)
|
||||
{
|
||||
ulong v = 0;
|
||||
|
||||
for (int i = data.Length - remainder; i < data.Length; i++)
|
||||
{
|
||||
v |= (ulong)data[i] << ((i - remainder) * 8);
|
||||
}
|
||||
|
||||
h ^= Mix(v);
|
||||
h *= M;
|
||||
}
|
||||
|
||||
h = Mix(h);
|
||||
return (uint)(h - (h >> 32));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash mix function.
|
||||
/// </summary>
|
||||
/// <param name="h">Hash to mix</param>
|
||||
/// <returns>Mixed hash</returns>
|
||||
private static ulong Mix(ulong h)
|
||||
{
|
||||
h ^= h >> 23;
|
||||
h *= 0x2127599bf4325c37UL;
|
||||
h ^= h >> 47;
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
27
Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs
Normal file
27
Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.HashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Data accessor, used by <see cref="PartitionedHashTable{T}"/> to access data of unknown length.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be used to access chuncks of data and try finding a match on the table.
|
||||
/// This is necessary because the data size is assumed to be unknown, and so the
|
||||
/// hash table must try to "guess" the size of the data based on the entries on the table.
|
||||
/// </remarks>
|
||||
public interface IDataAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a span of shader code at the specified offset, with at most the specified size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This might return a span smaller than the requested <paramref name="length"/> if there's
|
||||
/// no more code available.
|
||||
/// </remarks>
|
||||
/// <param name="offset">Offset in shader code</param>
|
||||
/// <param name="length">Size in bytes</param>
|
||||
/// <returns>Shader code span</returns>
|
||||
ReadOnlySpan<byte> GetSpan(int offset, int length);
|
||||
}
|
||||
}
|
452
Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs
Normal file
452
Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs
Normal file
|
@ -0,0 +1,452 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.HashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Partitioned hash table.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Hash table entry type</typeparam>
|
||||
class PartitionHashTable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash table entry.
|
||||
/// </summary>
|
||||
private struct Entry
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash <see cref="OwnSize"/> bytes of <see cref="Data"/>.
|
||||
/// </summary>
|
||||
public readonly uint Hash;
|
||||
|
||||
/// <summary>
|
||||
/// If this entry is only a sub-region of <see cref="Data"/>, this indicates the size in bytes
|
||||
/// of that region. Otherwise, it should be zero.
|
||||
/// </summary>
|
||||
public readonly int OwnSize;
|
||||
|
||||
/// <summary>
|
||||
/// Data used to compute the hash for this entry.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid additional allocations, this might be a instance of the full entry data,
|
||||
/// and only a sub-region of it might be actually used by this entry. Such sub-region
|
||||
/// has its size indicated by <see cref="OwnSize"/> in this case.
|
||||
/// </remarks>
|
||||
public readonly byte[] Data;
|
||||
|
||||
/// <summary>
|
||||
/// Item associated with this entry.
|
||||
/// </summary>
|
||||
public T Item;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the entry is partial, which means that this entry is only for a sub-region of the data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Partial entries have no items associated with them. They just indicates that the data might be present on
|
||||
/// the table, and one must keep looking for the full entry on other tables of larger data size.
|
||||
/// </remarks>
|
||||
public bool IsPartial => OwnSize != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partial hash table entry.
|
||||
/// </summary>
|
||||
/// <param name="hash">Hash of the data</param>
|
||||
/// <param name="ownerData">Full data</param>
|
||||
/// <param name="ownSize">Size of the sub-region of data that belongs to this entry</param>
|
||||
public Entry(uint hash, byte[] ownerData, int ownSize)
|
||||
{
|
||||
Hash = hash;
|
||||
OwnSize = ownSize;
|
||||
Data = ownerData;
|
||||
Item = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new full hash table entry.
|
||||
/// </summary>
|
||||
/// <param name="hash">Hash of the data</param>
|
||||
/// <param name="data">Data</param>
|
||||
/// <param name="item">Item associated with this entry</param>
|
||||
public Entry(uint hash, byte[] data, T item)
|
||||
{
|
||||
Hash = hash;
|
||||
OwnSize = 0;
|
||||
Data = data;
|
||||
Item = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data for this entry, either full or partial.
|
||||
/// </summary>
|
||||
/// <returns>Data sub-region</returns>
|
||||
public ReadOnlySpan<byte> GetData()
|
||||
{
|
||||
if (OwnSize != 0)
|
||||
{
|
||||
return new ReadOnlySpan<byte>(Data).Slice(0, OwnSize);
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash table bucket.
|
||||
/// </summary>
|
||||
private struct Bucket
|
||||
{
|
||||
/// <summary>
|
||||
/// Inline entry, to avoid allocations for the common single entry case.
|
||||
/// </summary>
|
||||
public Entry InlineEntry;
|
||||
|
||||
/// <summary>
|
||||
/// List of additional entries for the not-so-common multiple entries case.
|
||||
/// </summary>
|
||||
public List<Entry> MoreEntries;
|
||||
}
|
||||
|
||||
private Bucket[] _buckets;
|
||||
private int _count;
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of entries on the hash table.
|
||||
/// </summary>
|
||||
public int Count => _count;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the partitioned hash table.
|
||||
/// </summary>
|
||||
public PartitionHashTable()
|
||||
{
|
||||
_buckets = Array.Empty<Bucket>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item on the table, or adds a new one if not present.
|
||||
/// </summary>
|
||||
/// <param name="data">Data</param>
|
||||
/// <param name="dataHash">Hash of the data</param>
|
||||
/// <param name="item">Item to be added if not found</param>
|
||||
/// <returns>Existing item if found, or <paramref name="item"/> if not found</returns>
|
||||
public T GetOrAdd(byte[] data, uint dataHash, T item)
|
||||
{
|
||||
if (TryFindItem(dataHash, data, out T existingItem))
|
||||
{
|
||||
return existingItem;
|
||||
}
|
||||
|
||||
Entry entry = new Entry(dataHash, data, item);
|
||||
|
||||
AddToBucket(dataHash, ref entry);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the hash table.
|
||||
/// </summary>
|
||||
/// <param name="data">Data</param>
|
||||
/// <param name="dataHash">Hash of the data</param>
|
||||
/// <param name="item">Item to be added</param>
|
||||
/// <returns>True if the item was added, false due to an item associated with the data already being on the table</returns>
|
||||
public bool Add(byte[] data, uint dataHash, T item)
|
||||
{
|
||||
if (TryFindItem(dataHash, data, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry entry = new Entry(dataHash, data, item);
|
||||
|
||||
AddToBucket(dataHash, ref entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a partial entry to the hash table.
|
||||
/// </summary>
|
||||
/// <param name="ownerData">Full data</param>
|
||||
/// <param name="ownSize">Size of the sub-region of <paramref name="ownerData"/> used by the partial entry</param>
|
||||
/// <returns>True if added, false otherwise</returns>
|
||||
public bool AddPartial(byte[] ownerData, int ownSize)
|
||||
{
|
||||
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(ownerData).Slice(0, ownSize);
|
||||
|
||||
return AddPartial(ownerData, HashState.CalcHash(data), ownSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a partial entry to the hash table.
|
||||
/// </summary>
|
||||
/// <param name="ownerData">Full data</param>
|
||||
/// <param name="dataHash">Hash of the data sub-region</param>
|
||||
/// <param name="ownSize">Size of the sub-region of <paramref name="ownerData"/> used by the partial entry</param>
|
||||
/// <returns>True if added, false otherwise</returns>
|
||||
public bool AddPartial(byte[] ownerData, uint dataHash, int ownSize)
|
||||
{
|
||||
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(ownerData).Slice(0, ownSize);
|
||||
|
||||
if (TryFindItem(dataHash, data, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry entry = new Entry(dataHash, ownerData, ownSize);
|
||||
|
||||
AddToBucket(dataHash, ref entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds entry with a given hash to the table.
|
||||
/// </summary>
|
||||
/// <param name="dataHash">Hash of the entry</param>
|
||||
/// <param name="entry">Entry</param>
|
||||
private void AddToBucket(uint dataHash, ref Entry entry)
|
||||
{
|
||||
int pow2Count = GetPow2Count(++_count);
|
||||
if (pow2Count != _buckets.Length)
|
||||
{
|
||||
Rebuild(pow2Count);
|
||||
}
|
||||
|
||||
ref Bucket bucket = ref GetBucketForHash(dataHash);
|
||||
|
||||
AddToBucket(ref bucket, ref entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entry to a bucket.
|
||||
/// </summary>
|
||||
/// <param name="bucket">Bucket to add the entry into</param>
|
||||
/// <param name="entry">Entry to be added</param>
|
||||
private void AddToBucket(ref Bucket bucket, ref Entry entry)
|
||||
{
|
||||
if (bucket.InlineEntry.Data == null)
|
||||
{
|
||||
bucket.InlineEntry = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
(bucket.MoreEntries ??= new List<Entry>()).Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates partial entries on a new hash table for all existing full entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be called every time a new hash table is created, and there are hash
|
||||
/// tables with data sizes that are higher than that of the new table.
|
||||
/// This will then fill the new hash table with "partial" entries of full entries
|
||||
/// on the hash tables with higher size.
|
||||
/// </remarks>
|
||||
/// <param name="newTable">New hash table</param>
|
||||
/// <param name="newEntrySize">Size of the data on the new hash table</param>
|
||||
public void FillPartials(PartitionHashTable<T> newTable, int newEntrySize)
|
||||
{
|
||||
for (int i = 0; i < _buckets.Length; i++)
|
||||
{
|
||||
ref Bucket bucket = ref _buckets[i];
|
||||
ref Entry inlineEntry = ref bucket.InlineEntry;
|
||||
|
||||
if (inlineEntry.Data != null)
|
||||
{
|
||||
if (!inlineEntry.IsPartial)
|
||||
{
|
||||
newTable.AddPartial(inlineEntry.Data, newEntrySize);
|
||||
}
|
||||
|
||||
if (bucket.MoreEntries != null)
|
||||
{
|
||||
foreach (Entry entry in bucket.MoreEntries)
|
||||
{
|
||||
if (entry.IsPartial)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newTable.AddPartial(entry.Data, newEntrySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an item on the table.
|
||||
/// </summary>
|
||||
/// <param name="dataHash">Hash of <paramref name="data"/></param>
|
||||
/// <param name="data">Data to find</param>
|
||||
/// <param name="item">Item associated with the data</param>
|
||||
/// <returns>True if an item was found, false otherwise</returns>
|
||||
private bool TryFindItem(uint dataHash, ReadOnlySpan<byte> data, out T item)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
ref Bucket bucket = ref GetBucketForHash(dataHash);
|
||||
|
||||
if (bucket.InlineEntry.Data != null)
|
||||
{
|
||||
if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(data))
|
||||
{
|
||||
item = bucket.InlineEntry.Item;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bucket.MoreEntries != null)
|
||||
{
|
||||
foreach (Entry entry in bucket.MoreEntries)
|
||||
{
|
||||
if (entry.Hash == dataHash && entry.GetData().SequenceEqual(data))
|
||||
{
|
||||
item = entry.Item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the result of a hash table lookup.
|
||||
/// </summary>
|
||||
public enum SearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// No entry was found, the search must continue on hash tables of lower size.
|
||||
/// </summary>
|
||||
NotFound,
|
||||
|
||||
/// <summary>
|
||||
/// A partial entry was found, the search must continue on hash tables of higher size.
|
||||
/// </summary>
|
||||
FoundPartial,
|
||||
|
||||
/// <summary>
|
||||
/// A full entry was found, the search was concluded and the item can be retrieved.
|
||||
/// </summary>
|
||||
FoundFull
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an item on the table.
|
||||
/// </summary>
|
||||
/// <param name="dataAccessor">Data accessor</param>
|
||||
/// <param name="size">Size of the hash table data</param>
|
||||
/// <param name="item">The item on the table, if found, otherwise unmodified</param>
|
||||
/// <param name="data">The data on the table, if found, otherwise unmodified</param>
|
||||
/// <returns>Table lookup result</returns>
|
||||
public SearchResult TryFindItem(ref SmartDataAccessor dataAccessor, int size, ref T item, ref byte[] data)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
return SearchResult.NotFound;
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> dataSpan = dataAccessor.GetSpanAndHash(size, out uint dataHash);
|
||||
|
||||
if (dataSpan.Length != size)
|
||||
{
|
||||
return SearchResult.NotFound;
|
||||
}
|
||||
|
||||
ref Bucket bucket = ref GetBucketForHash(dataHash);
|
||||
|
||||
if (bucket.InlineEntry.Data != null)
|
||||
{
|
||||
if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(dataSpan))
|
||||
{
|
||||
item = bucket.InlineEntry.Item;
|
||||
data = bucket.InlineEntry.Data;
|
||||
return bucket.InlineEntry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull;
|
||||
}
|
||||
|
||||
if (bucket.MoreEntries != null)
|
||||
{
|
||||
foreach (Entry entry in bucket.MoreEntries)
|
||||
{
|
||||
if (entry.Hash == dataHash && entry.GetData().SequenceEqual(dataSpan))
|
||||
{
|
||||
item = entry.Item;
|
||||
data = entry.Data;
|
||||
return entry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SearchResult.NotFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the table for a new count.
|
||||
/// </summary>
|
||||
/// <param name="newPow2Count">New power of two count of the table</param>
|
||||
private void Rebuild(int newPow2Count)
|
||||
{
|
||||
Bucket[] newBuckets = new Bucket[newPow2Count];
|
||||
|
||||
uint mask = (uint)newPow2Count - 1;
|
||||
|
||||
for (int i = 0; i < _buckets.Length; i++)
|
||||
{
|
||||
ref Bucket bucket = ref _buckets[i];
|
||||
|
||||
if (bucket.InlineEntry.Data != null)
|
||||
{
|
||||
AddToBucket(ref newBuckets[(int)(bucket.InlineEntry.Hash & mask)], ref bucket.InlineEntry);
|
||||
|
||||
if (bucket.MoreEntries != null)
|
||||
{
|
||||
foreach (Entry entry in bucket.MoreEntries)
|
||||
{
|
||||
Entry entryCopy = entry;
|
||||
AddToBucket(ref newBuckets[(int)(entry.Hash & mask)], ref entryCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_buckets = newBuckets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bucket for a given hash.
|
||||
/// </summary>
|
||||
/// <param name="hash">Data hash</param>
|
||||
/// <returns>Bucket for the hash</returns>
|
||||
private ref Bucket GetBucketForHash(uint hash)
|
||||
{
|
||||
int index = (int)(hash & (_buckets.Length - 1));
|
||||
|
||||
return ref _buckets[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a power of two count from a regular count.
|
||||
/// </summary>
|
||||
/// <param name="count">Count</param>
|
||||
/// <returns>Power of two count</returns>
|
||||
private static int GetPow2Count(int count)
|
||||
{
|
||||
// This returns the nearest power of two that is lower than count.
|
||||
// This was done to optimize memory usage rather than performance.
|
||||
return 1 << BitOperations.Log2((uint)count);
|
||||
}
|
||||
}
|
||||
}
|
244
Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs
Normal file
244
Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs
Normal file
|
@ -0,0 +1,244 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.HashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Partitioned hash table.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class PartitionedHashTable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Entry for a given data size.
|
||||
/// </summary>
|
||||
private struct SizeEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Size for the data that will be stored on the hash table on this entry.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of entries on the hash table.
|
||||
/// </summary>
|
||||
public int TableCount => _table.Count;
|
||||
|
||||
private readonly PartitionHashTable<T> _table;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an entry for a given size.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of the data to be stored on this entry</param>
|
||||
public SizeEntry(int size)
|
||||
{
|
||||
Size = size;
|
||||
_table = new PartitionHashTable<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item for existing data, or adds a new one.
|
||||
/// </summary>
|
||||
/// <param name="data">Data associated with the item</param>
|
||||
/// <param name="dataHash">Hash of <paramref name="data"/></param>
|
||||
/// <param name="item">Item to be added</param>
|
||||
/// <returns>Existing item, or <paramref name="item"/> if not present</returns>
|
||||
public T GetOrAdd(byte[] data, uint dataHash, T item)
|
||||
{
|
||||
Debug.Assert(data.Length == Size);
|
||||
return _table.GetOrAdd(data, dataHash, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item.
|
||||
/// </summary>
|
||||
/// <param name="data">Data associated with the item</param>
|
||||
/// <param name="dataHash">Hash of <paramref name="data"/></param>
|
||||
/// <param name="item">Item to be added</param>
|
||||
/// <returns>True if added, false otherwise</returns>
|
||||
public bool Add(byte[] data, uint dataHash, T item)
|
||||
{
|
||||
Debug.Assert(data.Length == Size);
|
||||
return _table.Add(data, dataHash, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a partial entry.
|
||||
/// </summary>
|
||||
/// <param name="ownerData">Full entry data</param>
|
||||
/// <param name="dataHash">Hash of the sub-region of the data that belongs to this entry</param>
|
||||
/// <returns>True if added, false otherwise</returns>
|
||||
public bool AddPartial(byte[] ownerData, uint dataHash)
|
||||
{
|
||||
return _table.AddPartial(ownerData, dataHash, Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a new hash table with "partials" of existing full entries of higher size.
|
||||
/// </summary>
|
||||
/// <param name="newEntry">Entry with the new hash table</param>
|
||||
public void FillPartials(SizeEntry newEntry)
|
||||
{
|
||||
Debug.Assert(newEntry.Size < Size);
|
||||
_table.FillPartials(newEntry._table, newEntry.Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an item on the hash table.
|
||||
/// </summary>
|
||||
/// <param name="dataAccessor">Data accessor</param>
|
||||
/// <param name="item">The item on the table, if found, otherwise unmodified</param>
|
||||
/// <param name="data">The data on the table, if found, otherwise unmodified</param>
|
||||
/// <returns>Table lookup result</returns>
|
||||
public PartitionHashTable<T>.SearchResult TryFindItem(ref SmartDataAccessor dataAccessor, ref T item, ref byte[] data)
|
||||
{
|
||||
return _table.TryFindItem(ref dataAccessor, Size, ref item, ref data);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<SizeEntry> _sizeTable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new partitioned hash table.
|
||||
/// </summary>
|
||||
public PartitionedHashTable()
|
||||
{
|
||||
_sizeTable = new List<SizeEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the table.
|
||||
/// </summary>
|
||||
/// <param name="data">Data</param>
|
||||
/// <param name="item">Item associated with the data</param>
|
||||
public void Add(byte[] data, T item)
|
||||
{
|
||||
GetOrAdd(data, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an existing item from the table, or adds a new one if not present.
|
||||
/// </summary>
|
||||
/// <param name="data">Data</param>
|
||||
/// <param name="item">Item associated with the data</param>
|
||||
/// <returns>Existing item, or <paramref name="item"/> if not present</returns>
|
||||
public T GetOrAdd(byte[] data, T item)
|
||||
{
|
||||
SizeEntry sizeEntry;
|
||||
|
||||
int index = BinarySearch(_sizeTable, data.Length);
|
||||
if (index < _sizeTable.Count && _sizeTable[index].Size == data.Length)
|
||||
{
|
||||
sizeEntry = _sizeTable[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index < _sizeTable.Count && _sizeTable[index].Size < data.Length)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
sizeEntry = new SizeEntry(data.Length);
|
||||
|
||||
_sizeTable.Insert(index, sizeEntry);
|
||||
|
||||
for (int i = index + 1; i < _sizeTable.Count; i++)
|
||||
{
|
||||
_sizeTable[i].FillPartials(sizeEntry);
|
||||
}
|
||||
}
|
||||
|
||||
HashState hashState = new HashState();
|
||||
hashState.Initialize();
|
||||
|
||||
for (int i = 0; i < index; i++)
|
||||
{
|
||||
ReadOnlySpan<byte> dataSlice = new ReadOnlySpan<byte>(data).Slice(0, _sizeTable[i].Size);
|
||||
hashState.Continue(dataSlice);
|
||||
_sizeTable[i].AddPartial(data, hashState.Finalize(dataSlice));
|
||||
}
|
||||
|
||||
hashState.Continue(data);
|
||||
return sizeEntry.GetOrAdd(data, hashState.Finalize(data), item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs binary search on a list of hash tables, each one with a fixed data size.
|
||||
/// </summary>
|
||||
/// <param name="entries">List of hash tables</param>
|
||||
/// <param name="size">Size to search for</param>
|
||||
/// <returns>Index of the hash table with the given size, or nearest one otherwise</returns>
|
||||
private static int BinarySearch(List<SizeEntry> entries, int size)
|
||||
{
|
||||
int left = 0;
|
||||
int middle = 0;
|
||||
int right = entries.Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
middle = left + ((right - left) >> 1);
|
||||
|
||||
SizeEntry entry = entries[middle];
|
||||
|
||||
if (size == entry.Size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (size < entry.Size)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return middle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an item on the table.
|
||||
/// </summary>
|
||||
/// <param name="dataAccessor">Data accessor</param>
|
||||
/// <param name="item">Item, if found</param>
|
||||
/// <param name="data">Data, if found</param>
|
||||
/// <returns>True if the item was found on the table, false otherwise</returns>
|
||||
public bool TryFindItem(IDataAccessor dataAccessor, out T item, out byte[] data)
|
||||
{
|
||||
SmartDataAccessor sda = new SmartDataAccessor(dataAccessor);
|
||||
|
||||
item = default;
|
||||
data = null;
|
||||
|
||||
int left = 0;
|
||||
int right = _sizeTable.Count;
|
||||
|
||||
while (left != right)
|
||||
{
|
||||
int index = left + ((right - left) >> 1);
|
||||
|
||||
PartitionHashTable<T>.SearchResult result = _sizeTable[index].TryFindItem(ref sda, ref item, ref data);
|
||||
|
||||
if (result == PartitionHashTable<T>.SearchResult.FoundFull)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result == PartitionHashTable<T>.SearchResult.NotFound)
|
||||
{
|
||||
right = index;
|
||||
}
|
||||
else /* if (result == PartitionHashTable<T>.SearchResult.FoundPartial) */
|
||||
{
|
||||
left = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
96
Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs
Normal file
96
Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.HashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Smart data accessor that can cache data and hashes to avoid reading and re-hashing the same memory regions.
|
||||
/// </summary>
|
||||
ref struct SmartDataAccessor
|
||||
{
|
||||
private readonly IDataAccessor _dataAccessor;
|
||||
private ReadOnlySpan<byte> _data;
|
||||
private readonly SortedList<int, HashState> _cachedHashes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new smart data accessor.
|
||||
/// </summary>
|
||||
/// <param name="dataAccessor">Data accessor</param>
|
||||
public SmartDataAccessor(IDataAccessor dataAccessor)
|
||||
{
|
||||
_dataAccessor = dataAccessor;
|
||||
_data = ReadOnlySpan<byte>.Empty;
|
||||
_cachedHashes = new SortedList<int, HashState>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a spans of a given size.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual length of the span returned depends on the <see cref="IDataAccessor"/>
|
||||
/// and might be less than requested.
|
||||
/// </remarks>
|
||||
/// <param name="length">Size in bytes</param>
|
||||
/// <returns>Span with the requested size</returns>
|
||||
public ReadOnlySpan<byte> GetSpan(int length)
|
||||
{
|
||||
if (_data.Length < length)
|
||||
{
|
||||
_data = _dataAccessor.GetSpan(0, length);
|
||||
}
|
||||
else if (_data.Length > length)
|
||||
{
|
||||
return _data.Slice(0, length);
|
||||
}
|
||||
|
||||
return _data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span of the requested size, and a hash of its data.
|
||||
/// </summary>
|
||||
/// <param name="length">Length of the span</param>
|
||||
/// <param name="hash">Hash of the span data</param>
|
||||
/// <returns>Span of data</returns>
|
||||
public ReadOnlySpan<byte> GetSpanAndHash(int length, out uint hash)
|
||||
{
|
||||
ReadOnlySpan<byte> data = GetSpan(length);
|
||||
hash = data.Length == length ? CalcHashCached(data) : 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash for a requested span.
|
||||
/// This will try to use a cached hash if the data was already accessed before, to avoid re-hashing.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be hashed</param>
|
||||
/// <returns>Hash of the data</returns>
|
||||
private uint CalcHashCached(ReadOnlySpan<byte> data)
|
||||
{
|
||||
HashState state = default;
|
||||
bool found = false;
|
||||
|
||||
for (int i = _cachedHashes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int cachedHashSize = _cachedHashes.Keys[i];
|
||||
|
||||
if (cachedHashSize < data.Length)
|
||||
{
|
||||
state = _cachedHashes.Values[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
state = new HashState();
|
||||
state.Initialize();
|
||||
}
|
||||
|
||||
state.Continue(data);
|
||||
_cachedHashes[data.Length & ~7] = state;
|
||||
return state.Finalize(data);
|
||||
}
|
||||
}
|
||||
}
|
36
Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
Normal file
36
Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds counts for the resources used by a shader.
|
||||
/// </summary>
|
||||
class ResourceCounts
|
||||
{
|
||||
/// <summary>
|
||||
/// Total of uniform buffers used by the shaders.
|
||||
/// </summary>
|
||||
public int UniformBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total of storage buffers used by the shaders.
|
||||
/// </summary>
|
||||
public int StorageBuffersCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total of textures used by the shaders.
|
||||
/// </summary>
|
||||
public int TexturesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Total of images used by the shaders.
|
||||
/// </summary>
|
||||
public int ImagesCount;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader resource counts class.
|
||||
/// </summary>
|
||||
public ResourceCounts()
|
||||
{
|
||||
UniformBuffersCount = 1; // The first binding is reserved for the support buffer.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
|
@ -9,7 +11,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
#pragma warning disable CS0649
|
||||
public ulong VertexA;
|
||||
public ulong Vertex;
|
||||
public ulong VertexB;
|
||||
public ulong TessControl;
|
||||
public ulong TessEvaluation;
|
||||
public ulong Geometry;
|
||||
|
@ -34,7 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
public bool Equals(ShaderAddresses other)
|
||||
{
|
||||
return VertexA == other.VertexA &&
|
||||
Vertex == other.Vertex &&
|
||||
VertexB == other.VertexB &&
|
||||
TessControl == other.TessControl &&
|
||||
TessEvaluation == other.TessEvaluation &&
|
||||
Geometry == other.Geometry &&
|
||||
|
@ -47,7 +49,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// <returns>Hash code</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
|
||||
return HashCode.Combine(VertexA, VertexB, TessControl, TessEvaluation, Geometry, Fragment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a view of the structure as a span of addresses.
|
||||
/// </summary>
|
||||
/// <returns>Span of addresses</returns>
|
||||
public Span<ulong> AsSpan()
|
||||
{
|
||||
return MemoryMarshal.CreateSpan(ref VertexA, Unsafe.SizeOf<ShaderAddresses>() / sizeof(ulong));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
280
Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
Normal file
280
Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
Normal file
|
@ -0,0 +1,280 @@
|
|||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.HashTable;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds already cached code for a guest shader.
|
||||
/// </summary>
|
||||
struct CachedGraphicsGuestCode
|
||||
{
|
||||
public byte[] VertexACode;
|
||||
public byte[] VertexBCode;
|
||||
public byte[] TessControlCode;
|
||||
public byte[] TessEvaluationCode;
|
||||
public byte[] GeometryCode;
|
||||
public byte[] FragmentCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the guest code of a shader stage by its index.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Index of the shader stage</param>
|
||||
/// <returns>Guest code, or null if not present</returns>
|
||||
public byte[] GetByIndex(int stageIndex)
|
||||
{
|
||||
return stageIndex switch
|
||||
{
|
||||
1 => TessControlCode,
|
||||
2 => TessEvaluationCode,
|
||||
3 => GeometryCode,
|
||||
4 => FragmentCode,
|
||||
_ => VertexBCode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Graphics shader cache hash table.
|
||||
/// </summary>
|
||||
class ShaderCacheHashTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader ID cache.
|
||||
/// </summary>
|
||||
private struct IdCache
|
||||
{
|
||||
private PartitionedHashTable<int> _cache;
|
||||
private int _id;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the state.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
_cache = new PartitionedHashTable<int>();
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds guest code to the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the code was already cached, it will just return the existing ID.
|
||||
/// </remarks>
|
||||
/// <param name="code">Code to add</param>
|
||||
/// <returns>Unique ID for the guest code</returns>
|
||||
public int Add(byte[] code)
|
||||
{
|
||||
int id = ++_id;
|
||||
int cachedId = _cache.GetOrAdd(code, id);
|
||||
if (cachedId != id)
|
||||
{
|
||||
--_id;
|
||||
}
|
||||
|
||||
return cachedId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find cached guest code.
|
||||
/// </summary>
|
||||
/// <param name="dataAccessor">Code accessor used to read guest code to find a match on the hash table</param>
|
||||
/// <param name="id">ID of the guest code, if found</param>
|
||||
/// <param name="data">Cached guest code, if found</param>
|
||||
/// <returns>True if found, false otherwise</returns>
|
||||
public bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
|
||||
{
|
||||
return _cache.TryFindItem(dataAccessor, out id, out data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Guest code IDs of the guest shaders that when combined forms a single host program.
|
||||
/// </summary>
|
||||
private struct IdTable : IEquatable<IdTable>
|
||||
{
|
||||
public int VertexAId;
|
||||
public int VertexBId;
|
||||
public int TessControlId;
|
||||
public int TessEvaluationId;
|
||||
public int GeometryId;
|
||||
public int FragmentId;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IdTable other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IdTable other)
|
||||
{
|
||||
return other.VertexAId == VertexAId &&
|
||||
other.VertexBId == VertexBId &&
|
||||
other.TessControlId == TessControlId &&
|
||||
other.TessEvaluationId == TessEvaluationId &&
|
||||
other.GeometryId == GeometryId &&
|
||||
other.FragmentId == FragmentId;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId);
|
||||
}
|
||||
}
|
||||
|
||||
private IdCache _vertexACache;
|
||||
private IdCache _vertexBCache;
|
||||
private IdCache _tessControlCache;
|
||||
private IdCache _tessEvaluationCache;
|
||||
private IdCache _geometryCache;
|
||||
private IdCache _fragmentCache;
|
||||
|
||||
private readonly Dictionary<IdTable, ShaderSpecializationList> _shaderPrograms;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new graphics shader cache hash table.
|
||||
/// </summary>
|
||||
public ShaderCacheHashTable()
|
||||
{
|
||||
_vertexACache.Initialize();
|
||||
_vertexBCache.Initialize();
|
||||
_tessControlCache.Initialize();
|
||||
_tessEvaluationCache.Initialize();
|
||||
_geometryCache.Initialize();
|
||||
_fragmentCache.Initialize();
|
||||
|
||||
_shaderPrograms = new Dictionary<IdTable, ShaderSpecializationList>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a program to the cache.
|
||||
/// </summary>
|
||||
/// <param name="program">Program to be added</param>
|
||||
public void Add(CachedShaderProgram program)
|
||||
{
|
||||
IdTable idTable = new IdTable();
|
||||
|
||||
foreach (var shader in program.Shaders)
|
||||
{
|
||||
if (shader == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shader.Info != null)
|
||||
{
|
||||
switch (shader.Info.Stage)
|
||||
{
|
||||
case ShaderStage.Vertex:
|
||||
idTable.VertexBId = _vertexBCache.Add(shader.Code);
|
||||
break;
|
||||
case ShaderStage.TessellationControl:
|
||||
idTable.TessControlId = _tessControlCache.Add(shader.Code);
|
||||
break;
|
||||
case ShaderStage.TessellationEvaluation:
|
||||
idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code);
|
||||
break;
|
||||
case ShaderStage.Geometry:
|
||||
idTable.GeometryId = _geometryCache.Add(shader.Code);
|
||||
break;
|
||||
case ShaderStage.Fragment:
|
||||
idTable.FragmentId = _fragmentCache.Add(shader.Code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
idTable.VertexAId = _vertexACache.Add(shader.Code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
|
||||
{
|
||||
specList = new ShaderSpecializationList();
|
||||
_shaderPrograms.Add(idTable, specList);
|
||||
}
|
||||
|
||||
specList.Add(program);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a cached program.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Even if false is returned, <paramref name="guestCode"/> might still contain cached guest code.
|
||||
/// This can be used to avoid additional allocations for guest code that was already cached.
|
||||
/// </remarks>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="addresses">Guest addresses of the shaders to find</param>
|
||||
/// <param name="program">Cached host program for the given state, if found</param>
|
||||
/// <param name="guestCode">Cached guest code, if any found</param>
|
||||
/// <returns>True if a cached host program was found, false otherwise</returns>
|
||||
public bool TryFind(
|
||||
GpuChannel channel,
|
||||
GpuChannelPoolState poolState,
|
||||
ShaderAddresses addresses,
|
||||
out CachedShaderProgram program,
|
||||
out CachedGraphicsGuestCode guestCode)
|
||||
{
|
||||
var memoryManager = channel.MemoryManager;
|
||||
IdTable idTable = new IdTable();
|
||||
guestCode = new CachedGraphicsGuestCode();
|
||||
|
||||
program = null;
|
||||
|
||||
bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode);
|
||||
found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode);
|
||||
found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode);
|
||||
found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode);
|
||||
found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode);
|
||||
found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode);
|
||||
|
||||
if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
|
||||
{
|
||||
return specList.TryFindForGraphics(channel, poolState, out program);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the ID of a single cached shader stage.
|
||||
/// </summary>
|
||||
/// <param name="idCache">ID cache of the stage</param>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="baseAddress">Base address of the shader</param>
|
||||
/// <param name="id">ID, if found</param>
|
||||
/// <param name="data">Cached guest code, if found</param>
|
||||
/// <returns>True if a cached shader is found, false otherwise</returns>
|
||||
private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data)
|
||||
{
|
||||
if (baseAddress == 0)
|
||||
{
|
||||
id = 0;
|
||||
data = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(memoryManager, baseAddress);
|
||||
return idCache.TryFind(codeAccessor, out id, out data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all programs that have been added to the table.
|
||||
/// </summary>
|
||||
/// <returns>Programs added to the table</returns>
|
||||
public IEnumerable<CachedShaderProgram> GetPrograms()
|
||||
{
|
||||
foreach (var specList in _shaderPrograms.Values)
|
||||
{
|
||||
foreach (var program in specList)
|
||||
{
|
||||
yield return program;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs
Normal file
32
Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.HashTable;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader code accessor.
|
||||
/// </summary>
|
||||
struct ShaderCodeAccessor : IDataAccessor
|
||||
{
|
||||
private readonly MemoryManager _memoryManager;
|
||||
private readonly ulong _baseAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader code accessor.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">Memory manager used to access the shader code</param>
|
||||
/// <param name="baseAddress">Base address of the shader in memory</param>
|
||||
public ShaderCodeAccessor(MemoryManager memoryManager, ulong baseAddress)
|
||||
{
|
||||
_memoryManager = memoryManager;
|
||||
_baseAddress = baseAddress;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadOnlySpan<byte> GetSpan(int offset, int length)
|
||||
{
|
||||
return _memoryManager.GetSpanMapped(_baseAddress + (ulong)offset, length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached shader code for a single shader stage.
|
||||
/// </summary>
|
||||
class ShaderCodeHolder
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader program containing translated code.
|
||||
/// </summary>
|
||||
public ShaderProgram Program { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Shader program information.
|
||||
/// </summary>
|
||||
public ShaderProgramInfo Info { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Host shader object.
|
||||
/// </summary>
|
||||
/// <remarks>Null if the host shader program cache is in use.</remarks>
|
||||
public IShader HostShader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maxwell binary shader code.
|
||||
/// </summary>
|
||||
public byte[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional maxwell binary shader code for "Vertex A" shader.
|
||||
/// </summary>
|
||||
public byte[] Code2 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instace of the shader code holder.
|
||||
/// </summary>
|
||||
/// <param name="program">Shader program</param>
|
||||
/// <param name="info">Shader program information</param>
|
||||
/// <param name="code">Maxwell binary shader code</param>
|
||||
/// <param name="code2">Optional binary shader code of the "Vertex A" shader, when combined with "Vertex B"</param>
|
||||
public ShaderCodeHolder(ShaderProgram program, ShaderProgramInfo info, byte[] code, byte[] code2 = null)
|
||||
{
|
||||
Program = program;
|
||||
Info = info;
|
||||
Code = code;
|
||||
Code2 = code2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
delegate bool ShaderCompileTaskCallback(bool success, ShaderCompileTask task);
|
||||
|
||||
/// <summary>
|
||||
/// A class that represents a shader compilation.
|
||||
/// </summary>
|
||||
class ShaderCompileTask
|
||||
{
|
||||
private bool _compiling;
|
||||
|
||||
private Task _programsTask;
|
||||
private IProgram _program;
|
||||
|
||||
private ShaderCompileTaskCallback _action;
|
||||
private AutoResetEvent _taskDoneEvent;
|
||||
|
||||
public bool IsFaulted => _programsTask.IsFaulted;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new shader compile task, with an event to signal whenever a subtask completes.
|
||||
/// </summary>
|
||||
/// <param name="taskDoneEvent">Event to signal when a subtask completes</param>
|
||||
public ShaderCompileTask(AutoResetEvent taskDoneEvent)
|
||||
{
|
||||
_taskDoneEvent = taskDoneEvent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the completion status of the shader compile task, and run callbacks on step completion.
|
||||
/// Calling this periodically is required to progress through steps of the compilation.
|
||||
/// </summary>
|
||||
/// <returns>True if the task is complete, false if it is in progress</returns>
|
||||
public bool IsDone()
|
||||
{
|
||||
if (_compiling)
|
||||
{
|
||||
ProgramLinkStatus status = _program.CheckProgramLink(false);
|
||||
|
||||
if (status != ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
return _action(status == ProgramLinkStatus.Success, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Waiting on the task.
|
||||
|
||||
if (_programsTask.IsCompleted)
|
||||
{
|
||||
return _action(true, this);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a callback when the specified task has completed.
|
||||
/// </summary>
|
||||
/// <param name="task">The task object that needs to complete</param>
|
||||
/// <param name="action">The action to perform when it is complete</param>
|
||||
public void OnTask(Task task, ShaderCompileTaskCallback action)
|
||||
{
|
||||
_compiling = false;
|
||||
|
||||
_programsTask = task;
|
||||
_action = action;
|
||||
|
||||
task.ContinueWith(task => _taskDoneEvent.Set());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a callback when the specified program has been linked.
|
||||
/// </summary>
|
||||
/// <param name="task">The program that needs to be linked</param>
|
||||
/// <param name="action">The action to perform when linking is complete</param>
|
||||
public void OnCompiled(IProgram program, ShaderCompileTaskCallback action)
|
||||
{
|
||||
_compiling = true;
|
||||
|
||||
_program = program;
|
||||
_action = action;
|
||||
|
||||
if (program == null)
|
||||
{
|
||||
action(false, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
76
Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
Normal file
76
Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// List of cached shader programs that differs only by specialization state.
|
||||
/// </summary>
|
||||
class ShaderSpecializationList : IEnumerable<CachedShaderProgram>
|
||||
{
|
||||
private readonly List<CachedShaderProgram> _entries = new List<CachedShaderProgram>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a program to the list.
|
||||
/// </summary>
|
||||
/// <param name="program">Program to be added</param>
|
||||
public void Add(CachedShaderProgram program)
|
||||
{
|
||||
_entries.Add(program);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing 3D program on the cache.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="program">Cached program, if found</param>
|
||||
/// <returns>True if a compatible program is found, false otherwise</returns>
|
||||
public bool TryFindForGraphics(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesGraphics(channel, poolState))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
program = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find an existing compute program on the cache.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="program">Cached program, if found</param>
|
||||
/// <returns>True if a compatible program is found, false otherwise</returns>
|
||||
public bool TryFindForCompute(GpuChannel channel, GpuChannelPoolState poolState, out CachedShaderProgram program)
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry.SpecializationState.MatchesCompute(channel, poolState))
|
||||
{
|
||||
program = entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
program = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<CachedShaderProgram> GetEnumerator()
|
||||
{
|
||||
return _entries.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
615
Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
Normal file
615
Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs
Normal file
|
@ -0,0 +1,615 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
class ShaderSpecializationState
|
||||
{
|
||||
private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24);
|
||||
private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
|
||||
private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24);
|
||||
private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24);
|
||||
private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24);
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating GPU state that is used by the shader.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
private enum QueriedStateFlags
|
||||
{
|
||||
EarlyZForce = 1 << 0,
|
||||
PrimitiveTopology = 1 << 1,
|
||||
TessellationMode = 1 << 2,
|
||||
TransformFeedback = 1 << 3
|
||||
}
|
||||
|
||||
private QueriedStateFlags _queriedState;
|
||||
private bool _compute;
|
||||
private byte _constantBufferUsePerStage;
|
||||
|
||||
/// <summary>
|
||||
/// Compute engine state.
|
||||
/// </summary>
|
||||
public GpuChannelComputeState ComputeState;
|
||||
|
||||
/// <summary>
|
||||
/// 3D engine state.
|
||||
/// </summary>
|
||||
public GpuChannelGraphicsState GraphicsState;
|
||||
|
||||
/// <summary>
|
||||
/// Contant buffers bound at the time the shader was compiled, per stage.
|
||||
/// </summary>
|
||||
public Array5<uint> ConstantBufferUse;
|
||||
|
||||
/// <summary>
|
||||
/// Transform feedback buffers active at the time the shader was compiled.
|
||||
/// </summary>
|
||||
public TransformFeedbackDescriptor[] TransformFeedbackDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating texture state that is used by the shader.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
private enum QueriedTextureStateFlags
|
||||
{
|
||||
TextureFormat = 1 << 0,
|
||||
SamplerType = 1 << 1,
|
||||
CoordNormalized = 1 << 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference type wrapping a value.
|
||||
/// </summary>
|
||||
private class Box<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapped value.
|
||||
/// </summary>
|
||||
public T Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State of a texture or image that is accessed by the shader.
|
||||
/// </summary>
|
||||
private struct TextureSpecializationState
|
||||
{
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating which state of the texture the shader depends on.
|
||||
/// </summary>
|
||||
public QueriedTextureStateFlags QueriedFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Encoded texture format value.
|
||||
/// </summary>
|
||||
public uint Format;
|
||||
|
||||
/// <summary>
|
||||
/// True if the texture format is sRGB, false otherwise.
|
||||
/// </summary>
|
||||
public bool FormatSrgb;
|
||||
|
||||
/// <summary>
|
||||
/// Texture target.
|
||||
/// </summary>
|
||||
public Image.TextureTarget TextureTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height).
|
||||
/// </summary>
|
||||
public bool CoordNormalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture binding information, used to identify each texture accessed by the shader.
|
||||
/// </summary>
|
||||
private struct TextureKey : IEquatable<TextureKey>
|
||||
{
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
/// <summary>
|
||||
/// Shader stage where the texture is used.
|
||||
/// </summary>
|
||||
public readonly int StageIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Texture handle offset in words on the texture buffer.
|
||||
/// </summary>
|
||||
public readonly int Handle;
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register).
|
||||
/// </summary>
|
||||
public readonly int CbufSlot;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture key.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Texture handle offset in words on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register)</param>
|
||||
public TextureKey(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
StageIndex = stageIndex;
|
||||
Handle = handle;
|
||||
CbufSlot = cbufSlot;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is TextureKey textureKey && Equals(textureKey);
|
||||
}
|
||||
|
||||
public bool Equals(TextureKey other)
|
||||
{
|
||||
return StageIndex == other.StageIndex && Handle == other.Handle && CbufSlot == other.CbufSlot;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(StageIndex, Handle, CbufSlot);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<TextureKey, Box<TextureSpecializationState>> _textureSpecialization;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
private ShaderSpecializationState()
|
||||
{
|
||||
_textureSpecialization = new Dictionary<TextureKey, Box<TextureSpecializationState>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current compute engine state</param>
|
||||
public ShaderSpecializationState(GpuChannelComputeState state) : this()
|
||||
{
|
||||
ComputeState = state;
|
||||
_compute = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current 3D engine state</param>
|
||||
/// <param name="descriptors">Optional transform feedback buffers in use, if any</param>
|
||||
public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this()
|
||||
{
|
||||
GraphicsState = state;
|
||||
_compute = false;
|
||||
|
||||
if (descriptors != null)
|
||||
{
|
||||
TransformFeedbackDescriptors = descriptors;
|
||||
_queriedState |= QueriedStateFlags.TransformFeedback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public void RecordPrimitiveTopology()
|
||||
{
|
||||
_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>
|
||||
/// <param name="stageIndex">Shader stage index</param>
|
||||
/// <param name="useMask">Mask indicating the constant buffers bound at the time of the shader compilation</param>
|
||||
public void RecordConstantBufferUse(int stageIndex, uint useMask)
|
||||
{
|
||||
ConstantBufferUse[stageIndex] = useMask;
|
||||
_constantBufferUsePerStage |= (byte)(1 << stageIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a given texture is accessed by the shader.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <param name="descriptor">Descriptor of the texture</param>
|
||||
public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor)
|
||||
{
|
||||
Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
|
||||
state.Value.Format = descriptor.UnpackFormat();
|
||||
state.Value.FormatSrgb = descriptor.UnpackSrgb();
|
||||
state.Value.TextureTarget = descriptor.UnpackTextureTarget();
|
||||
state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a given texture is accessed by the shader.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <param name="format">Maxwell texture format value</param>
|
||||
/// <param name="formatSrgb">Whenever the texture format is a sRGB format</param>
|
||||
/// <param name="target">Texture target type</param>
|
||||
/// <param name="coordNormalized">Whenever the texture coordinates used on the shader are considered normalized</param>
|
||||
public void RegisterTexture(
|
||||
int stageIndex,
|
||||
int handle,
|
||||
int cbufSlot,
|
||||
uint format,
|
||||
bool formatSrgb,
|
||||
Image.TextureTarget target,
|
||||
bool coordNormalized)
|
||||
{
|
||||
Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
|
||||
state.Value.Format = format;
|
||||
state.Value.FormatSrgb = formatSrgb;
|
||||
state.Value.TextureTarget = target;
|
||||
state.Value.CoordNormalized = coordNormalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the format of a given texture was used during the shader translation process.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
|
||||
state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the target of a given texture was used during the shader translation process.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
|
||||
state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the coordinate normalization state of a given texture was used during the shader translation process.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
Box<TextureSpecializationState> state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot);
|
||||
state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given texture was registerd on this specialization state.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public bool TextureRegistered(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
return GetTextureSpecState(stageIndex, handle, cbufSlot) != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recorded format of a given texture.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value;
|
||||
return (state.Format, state.FormatSrgb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recorded target of a given texture.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public Image.TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recorded coordinate normalization state of a given texture.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets texture specialization state for a given texture, or create a new one if not present.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <returns>Texture specialization state</returns>
|
||||
private Box<TextureSpecializationState> GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
|
||||
|
||||
if (!_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
|
||||
{
|
||||
_textureSpecialization.Add(key, state = new Box<TextureSpecializationState>());
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets texture specialization state for a given texture.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Shader stage where the texture is used</param>
|
||||
/// <param name="handle">Offset in words of the texture handle on the texture buffer</param>
|
||||
/// <param name="cbufSlot">Slot of the texture buffer constant buffer</param>
|
||||
/// <returns>Texture specialization state</returns>
|
||||
private Box<TextureSpecializationState> GetTextureSpecState(int stageIndex, int handle, int cbufSlot)
|
||||
{
|
||||
TextureKey key = new TextureKey(stageIndex, handle, cbufSlot);
|
||||
|
||||
if (_textureSpecialization.TryGetValue(key, out Box<TextureSpecializationState> state))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded state matches the current GPU 3D engine state.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesGraphics(GpuChannel channel, GpuChannelPoolState poolState)
|
||||
{
|
||||
return Matches(channel, poolState, isCompute: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded state matches the current GPU compute engine state.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
public bool MatchesCompute(GpuChannel channel, GpuChannelPoolState poolState)
|
||||
{
|
||||
return Matches(channel, poolState, isCompute: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the recorded state matches the current GPU state.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="poolState">Texture pool state</param>
|
||||
/// <param name="isCompute">Indicates whenever the check is requested by the 3D or compute engine</param>
|
||||
/// <returns>True if the state matches, false otherwise</returns>
|
||||
private bool Matches(GpuChannel channel, GpuChannelPoolState poolState, bool isCompute)
|
||||
{
|
||||
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
|
||||
|
||||
while (constantBufferUsePerStageMask != 0)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
|
||||
|
||||
uint useMask = isCompute
|
||||
? channel.BufferManager.GetComputeUniformBufferUseMask()
|
||||
: channel.BufferManager.GetGraphicsUniformBufferUseMask(index);
|
||||
|
||||
if (ConstantBufferUse[index] != useMask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
foreach (var kv in _textureSpecialization)
|
||||
{
|
||||
TextureKey textureKey = kv.Key;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex);
|
||||
|
||||
ulong textureCbAddress;
|
||||
ulong samplerCbAddress;
|
||||
|
||||
if (isCompute)
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
textureCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, textureBufferIndex);
|
||||
samplerCbAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(textureKey.StageIndex, samplerBufferIndex);
|
||||
}
|
||||
|
||||
if (!channel.MemoryManager.Physical.IsMapped(textureCbAddress) || !channel.MemoryManager.Physical.IsMapped(samplerCbAddress))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Image.TextureDescriptor descriptor;
|
||||
|
||||
if (isCompute)
|
||||
{
|
||||
descriptor = channel.TextureManager.GetComputeTextureDescriptor(
|
||||
poolState.TexturePoolGpuVa,
|
||||
poolState.TextureBufferIndex,
|
||||
poolState.TexturePoolMaximumId,
|
||||
textureKey.Handle,
|
||||
textureKey.CbufSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor = channel.TextureManager.GetGraphicsTextureDescriptor(
|
||||
poolState.TexturePoolGpuVa,
|
||||
poolState.TextureBufferIndex,
|
||||
poolState.TexturePoolMaximumId,
|
||||
textureKey.StageIndex,
|
||||
textureKey.Handle,
|
||||
textureKey.CbufSlot);
|
||||
}
|
||||
|
||||
Box<TextureSpecializationState> specializationState = kv.Value;
|
||||
|
||||
if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) &&
|
||||
specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads shader specialization state that has been serialized.
|
||||
/// </summary>
|
||||
/// <param name="dataReader">Data reader</param>
|
||||
/// <returns>Shader specialization state</returns>
|
||||
public static ShaderSpecializationState Read(ref BinarySerializer dataReader)
|
||||
{
|
||||
ShaderSpecializationState specState = new ShaderSpecializationState();
|
||||
|
||||
dataReader.Read(ref specState._queriedState);
|
||||
dataReader.Read(ref specState._compute);
|
||||
|
||||
if (specState._compute)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic);
|
||||
}
|
||||
|
||||
dataReader.Read(ref specState._constantBufferUsePerStage);
|
||||
|
||||
int constantBufferUsePerStageMask = specState._constantBufferUsePerStage;
|
||||
|
||||
while (constantBufferUsePerStageMask != 0)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
|
||||
dataReader.Read(ref specState.ConstantBufferUse[index]);
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||
{
|
||||
ushort tfCount = 0;
|
||||
dataReader.Read(ref tfCount);
|
||||
specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount];
|
||||
|
||||
for (int index = 0; index < tfCount; index++)
|
||||
{
|
||||
dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic);
|
||||
}
|
||||
}
|
||||
|
||||
ushort count = 0;
|
||||
dataReader.Read(ref count);
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
TextureKey textureKey = default;
|
||||
Box<TextureSpecializationState> textureState = new Box<TextureSpecializationState>();
|
||||
|
||||
dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic);
|
||||
dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic);
|
||||
|
||||
specState._textureSpecialization[textureKey] = textureState;
|
||||
}
|
||||
|
||||
return specState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the shader specialization state.
|
||||
/// </summary>
|
||||
/// <param name="dataWriter">Data writer</param>
|
||||
public void Write(ref BinarySerializer dataWriter)
|
||||
{
|
||||
dataWriter.Write(ref _queriedState);
|
||||
dataWriter.Write(ref _compute);
|
||||
|
||||
if (_compute)
|
||||
{
|
||||
dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic);
|
||||
}
|
||||
|
||||
dataWriter.Write(ref _constantBufferUsePerStage);
|
||||
|
||||
int constantBufferUsePerStageMask = _constantBufferUsePerStage;
|
||||
|
||||
while (constantBufferUsePerStageMask != 0)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask);
|
||||
dataWriter.Write(ref ConstantBufferUse[index]);
|
||||
constantBufferUsePerStageMask &= ~(1 << index);
|
||||
}
|
||||
|
||||
if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback))
|
||||
{
|
||||
ushort tfCount = (ushort)TransformFeedbackDescriptors.Length;
|
||||
dataWriter.Write(ref tfCount);
|
||||
|
||||
for (int index = 0; index < TransformFeedbackDescriptors.Length; index++)
|
||||
{
|
||||
dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic);
|
||||
}
|
||||
}
|
||||
|
||||
ushort count = (ushort)_textureSpecialization.Count;
|
||||
dataWriter.Write(ref count);
|
||||
|
||||
foreach (var kv in _textureSpecialization)
|
||||
{
|
||||
var textureKey = kv.Key;
|
||||
var textureState = kv.Value;
|
||||
|
||||
dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic);
|
||||
dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,58 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform feedback descriptor.
|
||||
/// </summary>
|
||||
struct TransformFeedbackDescriptor
|
||||
{
|
||||
public int BufferIndex { get; }
|
||||
public int Stride { get; }
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
public byte[] VaryingLocations { get; }
|
||||
/// <summary>
|
||||
/// Index of the transform feedback.
|
||||
/// </summary>
|
||||
public readonly int BufferIndex;
|
||||
|
||||
public TransformFeedbackDescriptor(int bufferIndex, int stride, byte[] varyingLocations)
|
||||
/// <summary>
|
||||
/// Amount of bytes consumed per vertex.
|
||||
/// </summary>
|
||||
public readonly int Stride;
|
||||
|
||||
/// <summary>
|
||||
/// Number of varyings written into the buffer.
|
||||
/// </summary>
|
||||
public readonly int VaryingCount;
|
||||
|
||||
/// <summary>
|
||||
/// Location of varyings to be written into the buffer. Each byte is one location.
|
||||
/// </summary>
|
||||
public Array32<uint> VaryingLocations; // Making this readonly breaks AsSpan
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new transform feedback descriptor.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback</param>
|
||||
/// <param name="stride">Amount of bytes consumed per vertex</param>
|
||||
/// <param name="varyingCount">Number of varyings written into the buffer. Indicates size in bytes of <paramref name="varyingLocations"/></param>
|
||||
/// <param name="varyingLocations">Location of varyings to be written into the buffer. Each byte is one location</param>
|
||||
public TransformFeedbackDescriptor(int bufferIndex, int stride, int varyingCount, ref Array32<uint> varyingLocations)
|
||||
{
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations));
|
||||
BufferIndex = bufferIndex;
|
||||
Stride = stride;
|
||||
VaryingCount = varyingCount;
|
||||
VaryingLocations = varyingLocations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span of the <see cref="VaryingLocations"/>.
|
||||
/// </summary>
|
||||
/// <returns>Span of varying locations</returns>
|
||||
public ReadOnlySpan<byte> AsSpan()
|
||||
{
|
||||
return MemoryMarshal.Cast<uint, byte>(VaryingLocations.ToSpan()).Slice(0, Math.Min(128, VaryingCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
|
@ -444,8 +445,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return TextureTarget.Texture2DArray;
|
||||
case Target.Texture2DMultisample:
|
||||
return TextureTarget.Texture2DMultisample;
|
||||
case Target.Rectangle:
|
||||
return TextureTarget.TextureRectangle;
|
||||
case Target.Texture2DMultisampleArray:
|
||||
return TextureTarget.Texture2DMultisampleArray;
|
||||
case Target.Cubemap:
|
||||
return TextureTarget.TextureCubeMap;
|
||||
case Target.CubemapArray:
|
||||
|
@ -528,5 +529,19 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
return All.Never;
|
||||
}
|
||||
|
||||
public static ShaderType Convert(this ShaderStage stage)
|
||||
{
|
||||
return stage switch
|
||||
{
|
||||
ShaderStage.Compute => ShaderType.ComputeShader,
|
||||
ShaderStage.Vertex => ShaderType.VertexShader,
|
||||
ShaderStage.TessellationControl => ShaderType.TessControlShader,
|
||||
ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader,
|
||||
ShaderStage.Geometry => ShaderType.GeometryShader,
|
||||
ShaderStage.Fragment => ShaderType.FragmentShader,
|
||||
_ => ShaderType.VertexShader
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
|
@ -24,46 +26,66 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
}
|
||||
|
||||
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
|
||||
private IShader[] _shaders;
|
||||
private int[] _shaderHandles;
|
||||
|
||||
public bool HasFragmentShader;
|
||||
public int FragmentOutputMap { get; }
|
||||
|
||||
public Program(IShader[] shaders, int fragmentOutputMap)
|
||||
public Program(ShaderSource[] shaders, int fragmentOutputMap)
|
||||
{
|
||||
Handle = GL.CreateProgram();
|
||||
|
||||
GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
|
||||
|
||||
_shaderHandles = new int[shaders.Length];
|
||||
|
||||
for (int index = 0; index < shaders.Length; index++)
|
||||
{
|
||||
Shader shader = (Shader)shaders[index];
|
||||
ShaderSource shader = shaders[index];
|
||||
|
||||
if (shader.IsFragment)
|
||||
if (shader.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
HasFragmentShader = true;
|
||||
}
|
||||
|
||||
GL.AttachShader(Handle, shader.Handle);
|
||||
int shaderHandle = GL.CreateShader(shader.Stage.Convert());
|
||||
|
||||
switch (shader.Language)
|
||||
{
|
||||
case TargetLanguage.Glsl:
|
||||
GL.ShaderSource(shaderHandle, shader.Code);
|
||||
GL.CompileShader(shaderHandle);
|
||||
break;
|
||||
case TargetLanguage.Spirv:
|
||||
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
|
||||
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
|
||||
break;
|
||||
}
|
||||
|
||||
GL.AttachShader(Handle, shaderHandle);
|
||||
|
||||
_shaderHandles[index] = shaderHandle;
|
||||
}
|
||||
|
||||
GL.LinkProgram(Handle);
|
||||
|
||||
_shaders = shaders;
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
}
|
||||
|
||||
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
|
||||
{
|
||||
BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
|
||||
|
||||
Handle = GL.CreateProgram();
|
||||
|
||||
unsafe
|
||||
if (code.Length >= 4)
|
||||
{
|
||||
fixed (byte* ptr = code)
|
||||
BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
|
||||
|
||||
unsafe
|
||||
{
|
||||
GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
|
||||
fixed (byte* ptr = code)
|
||||
{
|
||||
GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,18 +111,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
}
|
||||
|
||||
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
|
||||
|
||||
if (_shaders != null)
|
||||
{
|
||||
for (int index = 0; index < _shaders.Length; index++)
|
||||
{
|
||||
int shaderHandle = ((Shader)_shaders[index]).Handle;
|
||||
|
||||
GL.DetachShader(Handle, shaderHandle);
|
||||
}
|
||||
|
||||
_shaders = null;
|
||||
}
|
||||
DeleteShaders();
|
||||
|
||||
if (status == 0)
|
||||
{
|
||||
|
@ -129,10 +140,25 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return data;
|
||||
}
|
||||
|
||||
private void DeleteShaders()
|
||||
{
|
||||
if (_shaderHandles != null)
|
||||
{
|
||||
foreach (int shaderHandle in _shaderHandles)
|
||||
{
|
||||
GL.DetachShader(Handle, shaderHandle);
|
||||
GL.DeleteShader(shaderHandle);
|
||||
}
|
||||
|
||||
_shaderHandles = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
DeleteShaders();
|
||||
GL.DeleteProgram(Handle);
|
||||
|
||||
Handle = 0;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using Ryujinx.Graphics.OpenGL.Queries;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL
|
||||
|
@ -54,11 +53,6 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
ResourcePool = new ResourcePool();
|
||||
}
|
||||
|
||||
public IShader CompileShader(ShaderStage stage, string code)
|
||||
{
|
||||
return new Shader(stage, code);
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(int size)
|
||||
{
|
||||
BufferCount++;
|
||||
|
@ -66,7 +60,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
return Buffer.Create(size);
|
||||
}
|
||||
|
||||
public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
|
||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||
{
|
||||
return new Program(shaders, info.FragmentOutputMap);
|
||||
}
|
||||
|
@ -101,6 +95,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
public Capabilities GetCapabilities()
|
||||
{
|
||||
return new Capabilities(
|
||||
api: TargetApi.OpenGL,
|
||||
vendorName: GpuVendor,
|
||||
hasFrontFacingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows,
|
||||
hasVectorIndexingBug: HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows,
|
||||
supportsAstcCompression: HwCapabilities.SupportsAstcCompression,
|
||||
|
@ -155,6 +151,12 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
_pipeline.Initialize(this);
|
||||
_counters.Initialize();
|
||||
|
||||
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
||||
// This call is expected to fail if we're running with a core profile,
|
||||
// as this clamp target was deprecated, but that's fine as a core profile
|
||||
// should already have the desired behaviour were outputs are not clamped.
|
||||
GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False);
|
||||
}
|
||||
|
||||
private void PrintGpuInformation()
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
class Shader : IShader
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
public bool IsFragment { get; }
|
||||
|
||||
public Shader(ShaderStage stage, string code)
|
||||
{
|
||||
ShaderType type = stage switch
|
||||
{
|
||||
ShaderStage.Compute => ShaderType.ComputeShader,
|
||||
ShaderStage.Vertex => ShaderType.VertexShader,
|
||||
ShaderStage.TessellationControl => ShaderType.TessControlShader,
|
||||
ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader,
|
||||
ShaderStage.Geometry => ShaderType.GeometryShader,
|
||||
ShaderStage.Fragment => ShaderType.FragmentShader,
|
||||
_ => ShaderType.VertexShader
|
||||
};
|
||||
|
||||
Handle = GL.CreateShader(type);
|
||||
IsFragment = stage == ShaderStage.Fragment;
|
||||
|
||||
GL.ShaderSource(Handle, code);
|
||||
GL.CompileShader(Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
GL.DeleteShader(Handle);
|
||||
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ namespace Ryujinx.Graphics.Shader
|
|||
{
|
||||
public struct BufferDescriptor
|
||||
{
|
||||
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
|
||||
|
||||
public readonly int Binding;
|
||||
public readonly int Slot;
|
||||
public BufferUsageFlags Flags;
|
||||
|
|
|
@ -70,6 +70,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
AppendLine("}" + suffix);
|
||||
}
|
||||
|
||||
public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp)
|
||||
{
|
||||
TextureDescriptor[] descriptors = Config.GetTextureDescriptors();
|
||||
|
||||
for (int i = 0; i < descriptors.Length; i++)
|
||||
{
|
||||
var descriptor = descriptors[i];
|
||||
|
||||
if (descriptor.CbufSlot == texOp.CbufSlot &&
|
||||
descriptor.HandleIndex == texOp.Handle &&
|
||||
descriptor.Format == texOp.Format)
|
||||
{
|
||||
return (descriptor, i);
|
||||
}
|
||||
}
|
||||
|
||||
return (default, -1);
|
||||
}
|
||||
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
|
||||
{
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
|
|
|
@ -756,27 +756,34 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||
|
||||
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
|
||||
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
IAstNode lod = operation.GetSource(lodSrcIndex);
|
||||
|
||||
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
|
||||
|
||||
if (texOp.Index == 3)
|
||||
{
|
||||
return $"textureQueryLevels({samplerName})";
|
||||
}
|
||||
else
|
||||
{
|
||||
string texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
|
||||
(TextureDescriptor descriptor, int descriptorIndex) = context.FindTextureDescriptor(texOp);
|
||||
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
|
||||
string texCall;
|
||||
|
||||
if (hasLod)
|
||||
{
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
IAstNode lod = operation.GetSource(lodSrcIndex);
|
||||
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
|
||||
|
||||
texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
|
||||
}
|
||||
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
int index = context.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
texCall = "Helper_TextureSizeUnscale(" + texCall + ", " + index + ")";
|
||||
texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})";
|
||||
}
|
||||
|
||||
return texCall;
|
||||
|
|
|
@ -250,9 +250,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
: "gl_SubgroupInvocationID";
|
||||
}
|
||||
|
||||
// TODO: There must be a better way to handle this...
|
||||
if (config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
// TODO: There must be a better way to handle this...
|
||||
switch (value)
|
||||
{
|
||||
case AttributeConsts.PositionX: return $"(gl_FragCoord.x / {DefaultNames.SupportBlockRenderScaleName}[0])";
|
||||
|
|
|
@ -373,7 +373,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
|||
|
||||
for (int i = 0; i < cbOffsetsCount; i++)
|
||||
{
|
||||
uint targetOffset = config.GpuAccessor.ConstantBuffer1Read(cbBaseOffset + i * 4);
|
||||
uint targetOffset = config.ConstantBuffer1Read(cbBaseOffset + i * 4);
|
||||
Block target = getBlock(baseOffset + targetOffset);
|
||||
target.Predecessors.Add(block);
|
||||
block.Successors.Add(target);
|
||||
|
|
|
@ -5144,6 +5144,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
|||
public int SrcC => (int)((_opcode >> 39) & 0xFF);
|
||||
public int Pred => (int)((_opcode >> 16) & 0x7);
|
||||
public bool PredInv => (_opcode & 0x80000) != 0;
|
||||
public int Imm16 => (int)((_opcode >> 20) & 0xFFFF);
|
||||
public bool WriteCC => (_opcode & 0x800000000000) != 0;
|
||||
public AvgMode AvgMode => (AvgMode)((_opcode >> 56) & 0x3);
|
||||
public bool DFormat => (_opcode & 0x40000000000000) != 0;
|
||||
|
@ -5164,6 +5165,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
|
|||
public int SrcC => (int)((_opcode >> 39) & 0xFF);
|
||||
public int Pred => (int)((_opcode >> 16) & 0x7);
|
||||
public bool PredInv => (_opcode & 0x80000) != 0;
|
||||
public int Imm16 => (int)((_opcode >> 20) & 0xFFFF);
|
||||
public bool WriteCC => (_opcode & 0x800000000000) != 0;
|
||||
public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7));
|
||||
public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7));
|
||||
|
|
|
@ -2,153 +2,341 @@
|
|||
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU state access interface.
|
||||
/// </summary>
|
||||
public interface IGpuAccessor
|
||||
{
|
||||
/// <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>
|
||||
/// <param name="offset">Offset in bytes to read from</param>
|
||||
/// <returns>Value at the given offset</returns>
|
||||
uint ConstantBuffer1Read(int offset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span of the specified memory location, containing shader code.
|
||||
/// </summary>
|
||||
/// <param name="address">GPU virtual address of the data</param>
|
||||
/// <param name="minimumSize">Minimum size that the returned span may have</param>
|
||||
/// <returns>Span of the memory location</returns>
|
||||
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
|
||||
|
||||
/// <summary>
|
||||
/// Queries the binding number of a constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="index">Constant buffer index</param>
|
||||
/// <returns>Binding number</returns>
|
||||
int QueryBindingConstantBuffer(int index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the binding number of a storage buffer.
|
||||
/// </summary>
|
||||
/// <param name="index">Storage buffer index</param>
|
||||
/// <returns>Binding number</returns>
|
||||
int QueryBindingStorageBuffer(int index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the binding number of a texture.
|
||||
/// </summary>
|
||||
/// <param name="index">Texture index</param>
|
||||
/// <returns>Binding number</returns>
|
||||
int QueryBindingTexture(int index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the binding number of an image.
|
||||
/// </summary>
|
||||
/// <param name="index">Image index</param>
|
||||
/// <returns>Binding number</returns>
|
||||
int QueryBindingImage(int index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size X for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size X</returns>
|
||||
int QueryComputeLocalSizeX()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Y for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Y</returns>
|
||||
int QueryComputeLocalSizeY()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Size Z for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Size Z</returns>
|
||||
int QueryComputeLocalSizeZ()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Local Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Local Memory size in bytes</returns>
|
||||
int QueryComputeLocalMemorySize()
|
||||
{
|
||||
return 0x1000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Shared Memory size in bytes for compute shaders.
|
||||
/// </summary>
|
||||
/// <returns>Shared Memory size in bytes</returns>
|
||||
int QueryComputeSharedMemorySize()
|
||||
{
|
||||
return 0xc000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries Constant Buffer usage information.
|
||||
/// </summary>
|
||||
/// <returns>A mask where each bit set indicates a bound constant buffer</returns>
|
||||
uint QueryConstantBufferUse()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||
/// </summary>
|
||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
||||
bool QueryHostHasFrontFacingBug()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host about the presence of the vector indexing bug.
|
||||
/// </summary>
|
||||
/// <returns>True if the bug is present on the host device used, false otherwise</returns>
|
||||
bool QueryHostHasVectorIndexingBug()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host storage buffer alignment required.
|
||||
/// </summary>
|
||||
/// <returns>Host storage buffer alignment in bytes</returns>
|
||||
int QueryHostStorageBufferOffsetAlignment()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for texture formats with BGRA component order (such as BGRA8).
|
||||
/// </summary>
|
||||
/// <returns>True if BGRA formats are supported, false otherwise</returns>
|
||||
bool QueryHostSupportsBgraFormat()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering critical sections on the shader code.
|
||||
/// </summary>
|
||||
/// <returns>True if fragment shader interlock is supported, false otherwise</returns>
|
||||
bool QueryHostSupportsFragmentShaderInterlock()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for fragment shader ordering scoped critical sections on the shader code.
|
||||
/// </summary>
|
||||
/// <returns>True if fragment shader ordering is supported, false otherwise</returns>
|
||||
bool QueryHostSupportsFragmentShaderOrderingIntel()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host support for readable images without a explicit format declaration on the shader.
|
||||
/// </summary>
|
||||
/// <returns>True if formatted image load is supported, false otherwise</returns>
|
||||
bool QueryHostSupportsImageLoadFormatted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU non-constant texture offset support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns>
|
||||
bool QueryHostSupportsNonConstantTextureOffset()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU shader ballot support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns>
|
||||
bool QueryHostSupportsShaderBallot()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU texture shadow LOD support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns>
|
||||
bool QueryHostSupportsTextureShadowLod()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries sampler type information.
|
||||
/// </summary>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>The sampler type value for the given handle</returns>
|
||||
SamplerType QuerySamplerType(int handle, int cbufSlot = -1)
|
||||
{
|
||||
return SamplerType.Texture2D;
|
||||
}
|
||||
|
||||
bool QueryIsTextureRectangle(int handle, int cbufSlot = -1)
|
||||
/// <summary>
|
||||
/// Queries texture coordinate normalization information.
|
||||
/// </summary>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>True if the coordinates are normalized, false otherwise</returns>
|
||||
bool QueryTextureCoordNormalized(int handle, int cbufSlot = -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <remarks>
|
||||
/// This only returns non-compressed color formats.
|
||||
/// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
|
||||
/// </remarks>
|
||||
/// <param name="handle">Texture handle</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot for the texture handle</param>
|
||||
/// <returns>Color format of the non-compressed texture</returns>
|
||||
TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1)
|
||||
{
|
||||
return TextureFormat.R8G8B8A8Unorm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries transform feedback enable state.
|
||||
/// </summary>
|
||||
/// <returns>True if the shader uses transform feedback, false otherwise</returns>
|
||||
bool QueryTransformFeedbackEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the varying locations that should be written to the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Varying locations for the specified buffer</returns>
|
||||
ReadOnlySpan<byte> QueryTransformFeedbackVaryingLocations(int bufferIndex)
|
||||
{
|
||||
return ReadOnlySpan<byte>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer.
|
||||
/// </summary>
|
||||
/// <param name="bufferIndex">Index of the transform feedback buffer</param>
|
||||
/// <returns>Stride for the specified buffer</returns>
|
||||
int QueryTransformFeedbackStride(int bufferIndex)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries if host state forces early depth testing.
|
||||
/// </summary>
|
||||
/// <returns>True if early depth testing is forced</returns>
|
||||
bool QueryEarlyZForce()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a texture used by the shader.
|
||||
/// </summary>
|
||||
/// <param name="handle">Texture handle word offset</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
||||
void RegisterTexture(int handle, int cbufSlot)
|
||||
{
|
||||
// Only useful when recording information for a disk shader cache.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,16 +13,28 @@ namespace Ryujinx.Graphics.Shader
|
|||
{
|
||||
public static string ToGlslString(this InputTopology topology)
|
||||
{
|
||||
switch (topology)
|
||||
return topology switch
|
||||
{
|
||||
case InputTopology.Points: return "points";
|
||||
case InputTopology.Lines: return "lines";
|
||||
case InputTopology.LinesAdjacency: return "lines_adjacency";
|
||||
case InputTopology.Triangles: return "triangles";
|
||||
case InputTopology.TrianglesAdjacency: return "triangles_adjacency";
|
||||
}
|
||||
InputTopology.Points => "points",
|
||||
InputTopology.Lines => "lines",
|
||||
InputTopology.LinesAdjacency => "lines_adjacency",
|
||||
InputTopology.Triangles => "triangles",
|
||||
InputTopology.TrianglesAdjacency => "triangles_adjacency",
|
||||
_ => "points"
|
||||
};
|
||||
}
|
||||
|
||||
return "points";
|
||||
public static int ToInputVertices(this InputTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
InputTopology.Points => 1,
|
||||
InputTopology.Lines or
|
||||
InputTopology.LinesAdjacency => 2,
|
||||
InputTopology.Triangles or
|
||||
InputTopology.TrianglesAdjacency => 3,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,6 +73,26 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
};
|
||||
}
|
||||
|
||||
public static Operand Extend(EmitterContext context, Operand src, VectorSelect type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
VectorSelect.U8B0 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(0)), 8),
|
||||
VectorSelect.U8B1 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(8)), 8),
|
||||
VectorSelect.U8B2 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(16)), 8),
|
||||
VectorSelect.U8B3 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(24)), 8),
|
||||
VectorSelect.U16H0 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(0)), 16),
|
||||
VectorSelect.U16H1 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(16)), 16),
|
||||
VectorSelect.S8B0 => SignExtendTo32(context, context.ShiftRightU32(src, Const(0)), 8),
|
||||
VectorSelect.S8B1 => SignExtendTo32(context, context.ShiftRightU32(src, Const(8)), 8),
|
||||
VectorSelect.S8B2 => SignExtendTo32(context, context.ShiftRightU32(src, Const(16)), 8),
|
||||
VectorSelect.S8B3 => SignExtendTo32(context, context.ShiftRightU32(src, Const(24)), 8),
|
||||
VectorSelect.S16H0 => SignExtendTo32(context, context.ShiftRightU32(src, Const(0)), 16),
|
||||
VectorSelect.S16H1 => SignExtendTo32(context, context.ShiftRightU32(src, Const(16)), 16),
|
||||
_ => src
|
||||
};
|
||||
}
|
||||
|
||||
public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false)
|
||||
{
|
||||
if (!setCC)
|
||||
|
@ -118,6 +138,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
}
|
||||
}
|
||||
|
||||
public static (Operand, Operand) NegateLong(EmitterContext context, Operand low, Operand high)
|
||||
{
|
||||
low = context.BitwiseNot(low);
|
||||
high = context.BitwiseNot(high);
|
||||
low = AddWithCarry(context, low, Const(1), out Operand carryOut);
|
||||
high = context.IAdd(high, carryOut);
|
||||
return (low, high);
|
||||
}
|
||||
|
||||
public static Operand AddWithCarry(EmitterContext context, Operand lhs, Operand rhs, out Operand carryOut)
|
||||
{
|
||||
Operand result = context.IAdd(lhs, rhs);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue