Compare commits

..

7 Commits

Author SHA1 Message Date
868919e101 misc: Update GtkSharp.Dependencies and speed up initial Windows build (#3128)
Update GtkSharp.Dependencies to fix between menu flickering and enable
the SkipInstallGtk in csproj to avoid downloading unused GTK3 install
locally.
2022-02-17 22:10:48 +01:00
9ca040c0ff Use ReadOnlySpan<byte> compiler optimization for static data (#3130) 2022-02-17 21:38:50 +01:00
7e9011673b Use a basic cubic interpolation for the audren upsampler (#3129)
Before, it was selecting nearest neighbour, which sounded terrible. This is likely temporary til the upsampling algorithm used by the switch is reversed.

Fixes bad audio in Skyward Sword HD.
2022-02-17 20:19:29 +01:00
741db8e43d amadeus: Fix PCMFloat datasource command v1 (#3127)
Really simple copy pasta error here.

Shouldn't affect anything as float support was added at the same REV as
datasource command v2.
2022-02-16 23:55:40 +01:00
3bd357045f Do not allow render targets not explicitly written by the fragment shader to be modified (#3063)
* Do not allow render targets not explicitly written by the fragment shader to be modified

* Shader cache version bump

* Remove blank lines

* Avoid redundant color mask updates

* HostShaderCacheEntry can be null

* Avoid more redundant glColorMask calls

* nit: Mask -> Masks

* Fix currentComponentMask

* More efficient way to update _currentComponentMasks
2022-02-16 23:15:39 +01:00
ab5d77c0c4 amadeus: Fix limiter correctness (#3126)
This fixes missing audio on Nintendo Switch Sports Online Play Test.
2022-02-16 21:38:45 +01:00
7bfb5f79b8 When copying linear textures, DMA should ignore region X/Y (#3121) 2022-02-16 11:13:45 +01:00
33 changed files with 277 additions and 173 deletions

View File

@ -1,13 +1,14 @@
// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf
using ARMeilleure.State;
using System;
namespace ARMeilleure.Instructions
{
static class CryptoHelper
{
#region "LookUp Tables"
private static readonly byte[] _sBox = new byte[]
private static ReadOnlySpan<byte> _sBox => new byte[]
{
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
@ -27,7 +28,7 @@ namespace ARMeilleure.Instructions
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
private static readonly byte[] _invSBox = new byte[]
private static ReadOnlySpan<byte> _invSBox => new byte[]
{
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
@ -47,7 +48,7 @@ namespace ARMeilleure.Instructions
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
};
private static readonly byte[] _gfMul02 = new byte[]
private static ReadOnlySpan<byte> _gfMul02 => new byte[]
{
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e,
@ -67,7 +68,7 @@ namespace ARMeilleure.Instructions
0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5
};
private static readonly byte[] _gfMul03 = new byte[]
private static ReadOnlySpan<byte> _gfMul03 => new byte[]
{
0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21,
@ -87,7 +88,7 @@ namespace ARMeilleure.Instructions
0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a
};
private static readonly byte[] _gfMul09 = new byte[]
private static ReadOnlySpan<byte> _gfMul09 => new byte[]
{
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7,
@ -107,7 +108,7 @@ namespace ARMeilleure.Instructions
0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46
};
private static readonly byte[] _gfMul0B = new byte[]
private static ReadOnlySpan<byte> _gfMul0B => new byte[]
{
0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9,
@ -127,7 +128,7 @@ namespace ARMeilleure.Instructions
0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3
};
private static readonly byte[] _gfMul0D = new byte[]
private static ReadOnlySpan<byte> _gfMul0D => new byte[]
{
0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b,
@ -147,7 +148,7 @@ namespace ARMeilleure.Instructions
0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97
};
private static readonly byte[] _gfMul0E = new byte[]
private static ReadOnlySpan<byte> _gfMul0E => new byte[]
{
0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba,
@ -167,12 +168,12 @@ namespace ARMeilleure.Instructions
0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d
};
private static readonly byte[] _srPerm = new byte[]
private static ReadOnlySpan<byte> _srPerm => new byte[]
{
0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3
};
private static readonly byte[] _isrPerm = new byte[]
private static ReadOnlySpan<byte> _isrPerm => new byte[]
{
0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11
};

View File

@ -824,7 +824,7 @@ namespace ARMeilleure.Instructions
return (ulong)(size - 1);
}
private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
private static ReadOnlySpan<byte> ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
{

View File

@ -100,9 +100,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient;
@ -131,7 +133,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
*((float*)outputBuffers[channelIndex] + sampleIndex) = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
delayedSample = inputSample;

View File

@ -111,9 +111,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
{
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
float inputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
float sampleInputMax = Math.Abs(inputSample);
float inputCoefficient = Parameter.ReleaseCoefficient;
@ -142,7 +144,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
*((float*)outputBuffers[channelIndex] + sampleIndex) = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
delayedSample = inputSample;

View File

@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
{
SourceSampleRate = SampleRate,
SampleFormat = SampleFormat.PcmInt16,
SampleFormat = SampleFormat.PcmFloat,
Pitch = Pitch,
DecodingBehaviour = DecodingBehaviour,
ExtraParameter = 0,

View File

@ -600,19 +600,42 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResampleForUpsampler(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float ratio, ref float fraction, int sampleCount)
{
// TODO: use a bandwidth filter to have better resampling.
// Currently a simple cubic interpolation, assuming duplicated values at edges.
// TODO: Discover and use algorithm that the switch uses.
int inputBufferIndex = 0;
int maxIndex = inputBuffer.Length - 1;
int cubicEnd = inputBuffer.Length - 3;
for (int i = 0; i < sampleCount; i++)
{
float outputData = inputBuffer[inputBufferIndex];
float s0, s1, s2, s3;
if (fraction > 1.0f)
s1 = inputBuffer[inputBufferIndex];
if (inputBufferIndex == 0 || inputBufferIndex > cubicEnd)
{
outputData = inputBuffer[inputBufferIndex + 1];
// Clamp interplation values at the ends of the input buffer.
s0 = inputBuffer[Math.Max(0, inputBufferIndex - 1)];
s2 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 1)];
s3 = inputBuffer[Math.Min(maxIndex, inputBufferIndex + 2)];
}
else
{
s0 = inputBuffer[inputBufferIndex - 1];
s2 = inputBuffer[inputBufferIndex + 1];
s3 = inputBuffer[inputBufferIndex + 2];
}
outputBuffer[i] = outputData;
float a = s3 - s2 - s0 + s1;
float b = s0 - s1 - a;
float c = s2 - s0;
float d = s1;
float f2 = fraction * fraction;
float f3 = f2 * fraction;
outputBuffer[i] = a * f3 + b * f2 + c * fraction + d;
fraction += ratio;
inputBufferIndex += (int)MathF.Truncate(fraction);

View File

@ -37,6 +37,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);
UpdateParameter(ref parameter);
}

View File

@ -558,7 +558,16 @@ namespace Ryujinx.Audio.Renderer.Server
if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported())
{
Memory<EffectResultState> dspResultState = _effectContext.GetDspStateMemory(effectId);
Memory<EffectResultState> dspResultState;
if (effect.Parameter.StatisticsEnabled)
{
dspResultState = _effectContext.GetDspStateMemory(effectId);
}
else
{
dspResultState = Memory<EffectResultState>.Empty;
}
_commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId);
}

View File

@ -1,8 +1,10 @@
using System;
namespace Ryujinx.Common
{
public static class BitUtils
{
private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
private static ReadOnlySpan<byte> ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
public static uint AlignUp(uint value, int size)
{

View File

@ -43,7 +43,7 @@ namespace Ryujinx.Common
Prime32_1
};
private static readonly byte[] Xxh3KSecret = new byte[]
private static ReadOnlySpan<byte> Xxh3KSecret => new byte[]
{
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,

View File

@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.GAL
BufferHandle CreateBuffer(int size);
IProgram CreateProgram(IShader[] shaders);
IProgram CreateProgram(IShader[] shaders, ShaderInfo info);
ISampler CreateSampler(SamplerCreateInfo info);
ITexture CreateTexture(TextureCreateInfo info, float scale);
@ -33,7 +33,7 @@ namespace Ryujinx.Graphics.GAL
Capabilities GetCapabilities();
IProgram LoadProgramBinary(byte[] programBinary);
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data);

View File

@ -5,17 +5,21 @@
public ThreadedProgram Threaded { get; set; }
private byte[] _data;
private bool _hasFragmentShader;
private ShaderInfo _info;
public BinaryProgramRequest(ThreadedProgram program, byte[] data)
public BinaryProgramRequest(ThreadedProgram program, byte[] data, bool hasFragmentShader, ShaderInfo info)
{
Threaded = program;
_data = data;
_hasFragmentShader = hasFragmentShader;
_info = info;
}
public IProgram Create(IRenderer renderer)
{
return renderer.LoadProgramBinary(_data);
return renderer.LoadProgramBinary(_data, _hasFragmentShader, _info);
}
}
}

View File

@ -7,12 +7,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
public ThreadedProgram Threaded { get; set; }
private IShader[] _shaders;
private ShaderInfo _info;
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders)
public SourceProgramRequest(ThreadedProgram program, IShader[] shaders, ShaderInfo info)
{
Threaded = program;
_shaders = shaders;
_info = info;
}
public IProgram Create(IRenderer renderer)
@ -24,7 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs
return threaded?.Base;
}).ToArray();
return renderer.CreateProgram(shaders);
return renderer.CreateProgram(shaders, _info);
}
}
}

View File

@ -268,10 +268,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
public IProgram CreateProgram(IShader[] shaders)
public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
{
var program = new ThreadedProgram(this);
SourceProgramRequest request = new SourceProgramRequest(program, shaders);
SourceProgramRequest request = new SourceProgramRequest(program, shaders, info);
Programs.Add(request);
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));
@ -355,11 +355,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_baseRenderer.Initialize(logLevel);
}
public IProgram LoadProgramBinary(byte[] programBinary)
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
var program = new ThreadedProgram(this);
BinaryProgramRequest request = new BinaryProgramRequest(program, programBinary);
BinaryProgramRequest request = new BinaryProgramRequest(program, programBinary, hasFragmentShader, info);
Programs.Add(request);
New<CreateProgramCommand>().Set(Ref((IProgramRequest)request));

View File

@ -0,0 +1,12 @@
namespace Ryujinx.Graphics.GAL
{
public struct ShaderInfo
{
public int FragmentOutputMap { get; }
public ShaderInfo(int fragmentOutputMap)
{
FragmentOutputMap = fragmentOutputMap;
}
}
}

View File

@ -85,9 +85,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
int alignWidth = Constants.StrideAlignment / bpp;
return tex.RegionX == 0 &&
tex.RegionY == 0 &&
stride / bpp == BitUtils.AlignUp(xCount, alignWidth);
return stride / bpp == BitUtils.AlignUp(xCount, alignWidth);
}
else
{
@ -161,6 +159,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
var dst = Unsafe.As<uint, DmaTexture>(ref _state.State.SetDstBlockSize);
var src = Unsafe.As<uint, DmaTexture>(ref _state.State.SetSrcBlockSize);
int srcRegionX = 0, srcRegionY = 0, dstRegionX = 0, dstRegionY = 0;
if (!srcLinear)
{
srcRegionX = src.RegionX;
srcRegionY = src.RegionY;
}
if (!dstLinear)
{
dstRegionX = dst.RegionX;
dstRegionY = dst.RegionY;
}
int srcStride = (int)_state.State.PitchIn;
int dstStride = (int)_state.State.PitchOut;
@ -182,8 +194,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
dst.MemoryLayout.UnpackGobBlocksInZ(),
dstBpp);
(int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(src.RegionX, src.RegionY, xCount, yCount);
(int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dst.RegionX, dst.RegionY, xCount, yCount);
(int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(srcRegionX, srcRegionY, xCount, yCount);
(int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dstRegionX, dstRegionY, xCount, yCount);
if (srcLinear && srcStride < 0)
{
@ -272,13 +284,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(src.RegionY + y);
dstCalculator.SetY(dst.RegionY + y);
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
for (int x = 0; x < xCount; x++)
{
int srcOffset = srcCalculator.GetOffset(src.RegionX + x);
int dstOffset = dstCalculator.GetOffset(dst.RegionX + x);
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}

View File

@ -77,7 +77,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
programInfo.Images.Count,
programInfo.UsesInstanceId,
programInfo.UsesRtLayer,
programInfo.ClipDistancesWritten);
programInfo.ClipDistancesWritten,
programInfo.FragmentOutputMap);
CBuffers = programInfo.CBuffers.ToArray();
SBuffers = programInfo.SBuffers.ToArray();
Textures = programInfo.Textures.ToArray();
@ -97,7 +98,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
Images,
Header.UseFlags.HasFlag(UseFlags.InstanceId),
Header.UseFlags.HasFlag(UseFlags.RtLayer),
Header.ClipDistancesWritten);
Header.ClipDistancesWritten,
Header.FragmentOutputMap);
}
/// <summary>

View File

@ -26,7 +26,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
/// <summary>
/// Host shader entry header used for binding information.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)]
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)]
struct HostShaderCacheEntryHeader
{
/// <summary>
@ -70,6 +70,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
/// </summary>
public byte Reserved;
/// <summary>
/// Mask of components written by the fragment shader stage.
/// </summary>
public int FragmentOutputMap;
/// <summary>
/// Create a new host shader cache entry header.
/// </summary>
@ -78,6 +83,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
/// <param name="texturesCount">Count of texture descriptors</param>
/// <param name="imagesCount">Count of image descriptors</param>
/// <param name="usesInstanceId">Set to true if the shader uses instance id</param>
/// <param name="clipDistancesWritten">Mask of clip distances that are written to on the shader</param>
/// <param name="fragmentOutputMap">Mask of components written by the fragment shader stage</param>
public HostShaderCacheEntryHeader(
int cBuffersCount,
int sBuffersCount,
@ -85,13 +92,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
int imagesCount,
bool usesInstanceId,
bool usesRtLayer,
byte clipDistancesWritten) : this()
byte clipDistancesWritten,
int fragmentOutputMap) : this()
{
CBuffersCount = cBuffersCount;
SBuffersCount = sBuffersCount;
TexturesCount = texturesCount;
ImagesCount = imagesCount;
ClipDistancesWritten = clipDistancesWritten;
FragmentOutputMap = fragmentOutputMap;
InUse = true;
UseFlags = usesInstanceId ? UseFlags.InstanceId : UseFlags.None;

View File

@ -40,7 +40,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <summary>
/// Version of the codegen (to be changed when codegen or guest format change).
/// </summary>
private const ulong ShaderCodeGenVersion = 3106;
private const ulong ShaderCodeGenVersion = 3063;
// Progress reporting helpers
private volatile int _shaderCount;
@ -188,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
hostProgramBinary = hostProgramBinarySpan.ToArray();
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, false, new ShaderInfo(-1));
}
ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
@ -252,7 +252,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
// Compile shader and create program as the shader program binary got invalidated.
shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, program.Code);
hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1));
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
@ -303,7 +303,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan);
hostProgramBinary = hostProgramBinarySpan.ToArray();
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
bool hasFragmentShader = false;
int fragmentOutputMap = -1;
int fragmentIndex = (int)ShaderStage.Fragment - 1;
if (hostShaderEntries[fragmentIndex] != null && hostShaderEntries[fragmentIndex].Header.InUse)
{
hasFragmentShader = true;
fragmentOutputMap = hostShaderEntries[fragmentIndex].Header.FragmentOutputMap;
}
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary, hasFragmentShader, new ShaderInfo(fragmentOutputMap));
}
ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent);
@ -426,7 +437,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostShaders.Add(hostShader);
}
hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
int fragmentIndex = (int)ShaderStage.Fragment - 1;
int fragmentOutputMap = -1;
if (shaders[fragmentIndex] != null)
{
fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap;
}
hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap));
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
@ -617,7 +636,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader });
IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, new ShaderInfo(-1));
cpShader = new ShaderBundle(hostProgram, shader);
@ -755,7 +774,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostShaders.Add(hostShader);
}
IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray());
int fragmentIndex = (int)ShaderStage.Fragment - 1;
int fragmentOutputMap = -1;
if (shaders[fragmentIndex] != null)
{
fragmentOutputMap = shaders[fragmentIndex].Info.FragmentOutputMap;
}
IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), new ShaderInfo(fragmentOutputMap));
gpShaders = new ShaderBundle(hostProgram, shaders);

View File

@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
}
// ZigZag LUTs from libavcodec.
private static readonly byte[] ZigZagDirect = new byte[]
private static ReadOnlySpan<byte> ZigZagDirect => new byte[]
{
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
@ -130,7 +130,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
53, 60, 61, 54, 47, 55, 62, 63
};
private static readonly byte[] ZigZagScan = new byte[]
private static ReadOnlySpan<byte> ZigZagScan => new byte[]
{
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4,
1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264
private static void WriteScalingList(ref H264BitStreamWriter writer, IArray<byte> list)
{
byte[] scan = list.Length == 16 ? ZigZagScan : ZigZagDirect;
ReadOnlySpan<byte> scan = list.Length == 16 ? ZigZagScan : ZigZagDirect;
int lastScale = 8;

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
public ISurface CreateSurface(int width, int height) => new Surface(width, height);
private static readonly byte[] LiteralToFilter = new byte[]
private static ReadOnlySpan<byte> LiteralToFilter => new byte[]
{
Constants.EightTapSmooth,
Constants.EightTap,

View File

@ -1,5 +1,6 @@
using Ryujinx.Graphics.Nvdec.Vp9.Common;
using Ryujinx.Graphics.Nvdec.Vp9.Types;
using System;
using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.IntraPred;
namespace Ryujinx.Graphics.Nvdec.Vp9
@ -24,7 +25,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9
private const int NeedAbove = 1 << 2;
private const int NeedAboveRight = 1 << 3;
private static readonly byte[] ExtendModes = new byte[]
private static ReadOnlySpan<byte> ExtendModes => new byte[]
{
NeedAbove | NeedLeft, // DC
NeedAbove, // V

View File

@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
public short Row;
public short Col;
private static readonly byte[] LogInBase2 = new byte[]
private static ReadOnlySpan<byte> LogInBase2 => new byte[]
{
0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

View File

@ -53,7 +53,9 @@ namespace Ryujinx.Graphics.OpenGL
private ClipOrigin _clipOrigin;
private ClipDepthMode _clipDepthMode;
private readonly uint[] _componentMasks;
private uint _fragmentOutputMap;
private uint _componentMasks;
private uint _currentComponentMasks;
private uint _scissorEnables;
@ -73,12 +75,8 @@ namespace Ryujinx.Graphics.OpenGL
_clipOrigin = ClipOrigin.LowerLeft;
_clipDepthMode = ClipDepthMode.NegativeOneToOne;
_componentMasks = new uint[Constants.MaxRenderTargets];
for (int index = 0; index < Constants.MaxRenderTargets; index++)
{
_componentMasks[index] = 0xf;
}
_fragmentOutputMap = uint.MaxValue;
_componentMasks = uint.MaxValue;
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
new Span<Vector4<float>>(_renderScale).Fill(defaultScale);
@ -1001,18 +999,30 @@ namespace Ryujinx.Graphics.OpenGL
public void SetProgram(IProgram program)
{
_program = (Program)program;
Program prg = (Program)program;
if (_tfEnabled)
{
GL.EndTransformFeedback();
_program.Bind();
prg.Bind();
GL.BeginTransformFeedback(_tfTopology);
}
else
{
_program.Bind();
prg.Bind();
}
if (prg.HasFragmentShader && _fragmentOutputMap != (uint)prg.FragmentOutputMap)
{
_fragmentOutputMap = (uint)prg.FragmentOutputMap;
for (int index = 0; index < Constants.MaxRenderTargets; index++)
{
RestoreComponentMask(index, force: false);
}
}
_program = prg;
}
public void SetRasterizerDiscard(bool discard)
@ -1037,11 +1047,13 @@ namespace Ryujinx.Graphics.OpenGL
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMasks)
{
_componentMasks = 0;
for (int index = 0; index < componentMasks.Length; index++)
{
_componentMasks[index] = componentMasks[index];
_componentMasks |= componentMasks[index] << (index * 4);
RestoreComponentMask(index);
RestoreComponentMask(index, force: false);
}
}
@ -1436,18 +1448,34 @@ namespace Ryujinx.Graphics.OpenGL
}
}
public void RestoreComponentMask(int index)
public void RestoreComponentMask(int index, bool force = true)
{
// If the bound render target is bgra, swap the red and blue masks.
uint redMask = _fpIsBgra[index].X == 0 ? 1u : 4u;
uint blueMask = _fpIsBgra[index].X == 0 ? 4u : 1u;
int shift = index * 4;
uint componentMask = _componentMasks & _fragmentOutputMap;
uint checkMask = 0xfu << shift;
uint componentMaskAtIndex = componentMask & checkMask;
if (!force && componentMaskAtIndex == (_currentComponentMasks & checkMask))
{
return;
}
componentMask >>= shift;
componentMask &= 0xfu;
GL.ColorMask(
index,
(_componentMasks[index] & redMask) != 0,
(_componentMasks[index] & 2u) != 0,
(_componentMasks[index] & blueMask) != 0,
(_componentMasks[index] & 8u) != 0);
(componentMask & redMask) != 0,
(componentMask & 2u) != 0,
(componentMask & blueMask) != 0,
(componentMask & 8u) != 0);
_currentComponentMasks &= ~checkMask;
_currentComponentMasks |= componentMaskAtIndex;
}
public void RestoreScissor0Enable()

View File

@ -1,11 +1,8 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.OpenGL
{
@ -29,7 +26,10 @@ namespace Ryujinx.Graphics.OpenGL
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
private IShader[] _shaders;
public Program(IShader[] shaders)
public bool HasFragmentShader;
public int FragmentOutputMap { get; }
public Program(IShader[] shaders, int fragmentOutputMap)
{
Handle = GL.CreateProgram();
@ -37,17 +37,23 @@ namespace Ryujinx.Graphics.OpenGL
for (int index = 0; index < shaders.Length; index++)
{
int shaderHandle = ((Shader)shaders[index]).Handle;
Shader shader = (Shader)shaders[index];
GL.AttachShader(Handle, shaderHandle);
if (shader.IsFragment)
{
HasFragmentShader = true;
}
GL.AttachShader(Handle, shader.Handle);
}
GL.LinkProgram(Handle);
_shaders = shaders;
FragmentOutputMap = fragmentOutputMap;
}
public Program(ReadOnlySpan<byte> code)
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
{
BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
@ -60,6 +66,9 @@ namespace Ryujinx.Graphics.OpenGL
GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
}
}
HasFragmentShader = hasFragmentShader;
FragmentOutputMap = fragmentOutputMap;
}
public void Bind()

View File

@ -66,9 +66,9 @@ namespace Ryujinx.Graphics.OpenGL
return Buffer.Create(size);
}
public IProgram CreateProgram(IShader[] shaders)
public IProgram CreateProgram(IShader[] shaders, ShaderInfo info)
{
return new Program(shaders);
return new Program(shaders, info.FragmentOutputMap);
}
public ISampler CreateSampler(SamplerCreateInfo info)
@ -202,9 +202,9 @@ namespace Ryujinx.Graphics.OpenGL
_sync.Dispose();
}
public IProgram LoadProgramBinary(byte[] programBinary)
public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info)
{
return new Program(programBinary);
return new Program(programBinary, hasFragmentShader, info.FragmentOutputMap);
}
public void CreateSync(ulong id)

View File

@ -7,6 +7,7 @@ namespace Ryujinx.Graphics.OpenGL
class Shader : IShader
{
public int Handle { get; private set; }
public bool IsFragment { get; }
public Shader(ShaderStage stage, string code)
{
@ -22,6 +23,7 @@ namespace Ryujinx.Graphics.OpenGL
};
Handle = GL.CreateShader(type);
IsFragment = stage == ShaderStage.Fragment;
GL.ShaderSource(Handle, code);
GL.CompileShader(Handle);

View File

@ -5,32 +5,35 @@ namespace Ryujinx.Graphics.Shader
{
public class ShaderProgramInfo
{
public ReadOnlyCollection<BufferDescriptor> CBuffers { get; }
public ReadOnlyCollection<BufferDescriptor> SBuffers { get; }
public ReadOnlyCollection<BufferDescriptor> CBuffers { get; }
public ReadOnlyCollection<BufferDescriptor> SBuffers { get; }
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
public ReadOnlyCollection<TextureDescriptor> Images { get; }
public ReadOnlyCollection<TextureDescriptor> Images { get; }
public bool UsesInstanceId { get; }
public bool UsesRtLayer { get; }
public byte ClipDistancesWritten { get; }
public int FragmentOutputMap { get; }
public ShaderProgramInfo(
BufferDescriptor[] cBuffers,
BufferDescriptor[] sBuffers,
BufferDescriptor[] cBuffers,
BufferDescriptor[] sBuffers,
TextureDescriptor[] textures,
TextureDescriptor[] images,
bool usesInstanceId,
bool usesRtLayer,
byte clipDistancesWritten)
bool usesInstanceId,
bool usesRtLayer,
byte clipDistancesWritten,
int fragmentOutputMap)
{
CBuffers = Array.AsReadOnly(cBuffers);
SBuffers = Array.AsReadOnly(sBuffers);
Textures = Array.AsReadOnly(textures);
Images = Array.AsReadOnly(images);
Images = Array.AsReadOnly(images);
UsesInstanceId = usesInstanceId;
UsesRtLayer = usesRtLayer;
ClipDistancesWritten = clipDistancesWritten;
FragmentOutputMap = fragmentOutputMap;
}
}
}

View File

@ -172,11 +172,10 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int rtIndex = 0; rtIndex < 8; rtIndex++)
{
OmapTarget target = Config.OmapTargets[rtIndex];
for (int component = 0; component < 4; component++)
{
if (!target.ComponentEnabled(component))
bool componentEnabled = (Config.OmapTargets & (1 << (rtIndex * 4 + component))) != 0;
if (!componentEnabled)
{
continue;
}
@ -210,7 +209,8 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
if (target.Enabled)
bool targetEnabled = (Config.OmapTargets & (0xf << (rtIndex * 4))) != 0;
if (targetEnabled)
{
Config.SetOutputUserAttribute(rtIndex, perPatch: false);
regIndexBase += 4;

View File

@ -25,9 +25,9 @@ namespace Ryujinx.Graphics.Shader.Translation
public ImapPixelType[] ImapTypes { get; }
public OmapTarget[] OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public IGpuAccessor GpuAccessor { get; }
@ -135,21 +135,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public int GetDepthRegister()
{
int count = 0;
for (int index = 0; index < OmapTargets.Length; index++)
{
for (int component = 0; component < 4; component++)
{
if (OmapTargets[index].ComponentEnabled(component))
{
count++;
}
}
}
// The depth register is always two registers after the last color output.
return count + 1;
return BitOperations.PopCount((uint)OmapTargets) + 1;
}
public TextureFormat GetTextureFormat(int handle, int cbufSlot = -1)

View File

@ -36,37 +36,6 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
struct OmapTarget
{
public bool Red { get; }
public bool Green { get; }
public bool Blue { get; }
public bool Alpha { get; }
public bool Enabled => Red || Green || Blue || Alpha;
public OmapTarget(bool red, bool green, bool blue, bool alpha)
{
Red = red;
Green = green;
Blue = blue;
Alpha = alpha;
}
public bool ComponentEnabled(int component)
{
switch (component)
{
case 0: return Red;
case 1: return Green;
case 2: return Blue;
case 3: return Alpha;
}
throw new ArgumentOutOfRangeException(nameof(component));
}
}
class ShaderHeader
{
public int SphType { get; }
@ -85,7 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public bool GpPassthrough { get; }
public bool DoesLoadOrStore { get; }
public bool DoesFp64 { get; }
public bool DoesFp64 { get; }
public int StreamOutMask { get; }
@ -104,13 +73,13 @@ namespace Ryujinx.Graphics.Shader.Translation
public int MaxOutputVertexCount { get; }
public int StoreReqStart { get; }
public int StoreReqEnd { get; }
public int StoreReqEnd { get; }
public ImapPixelType[] ImapTypes { get; }
public OmapTarget[] OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public int OmapTargets { get; }
public bool OmapSampleMask { get; }
public bool OmapDepth { get; }
public ShaderHeader(IGpuAccessor gpuAccessor, ulong address)
{
@ -144,7 +113,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpPassthrough = commonWord0.Extract(24);
DoesLoadOrStore = commonWord0.Extract(26);
DoesFp64 = commonWord0.Extract(27);
DoesFp64 = commonWord0.Extract(27);
StreamOutMask = commonWord0.Extract(28, 4);
@ -163,7 +132,7 @@ namespace Ryujinx.Graphics.Shader.Translation
MaxOutputVertexCount = commonWord4.Extract(0, 12);
StoreReqStart = commonWord4.Extract(12, 8);
StoreReqEnd = commonWord4.Extract(24, 8);
StoreReqEnd = commonWord4.Extract(24, 8);
ImapTypes = new ImapPixelType[32];
@ -179,21 +148,11 @@ namespace Ryujinx.Graphics.Shader.Translation
}
int type2OmapTarget = header[18];
int type2Omap = header[19];
OmapTargets = new OmapTarget[8];
for (int offset = 0; offset < OmapTargets.Length * 4; offset += 4)
{
OmapTargets[offset >> 2] = new OmapTarget(
type2OmapTarget.Extract(offset + 0),
type2OmapTarget.Extract(offset + 1),
type2OmapTarget.Extract(offset + 2),
type2OmapTarget.Extract(offset + 3));
}
int type2Omap = header[19];
OmapTargets = type2OmapTarget;
OmapSampleMask = type2Omap.Extract(0);
OmapDepth = type2Omap.Extract(1);
OmapDepth = type2Omap.Extract(1);
}
}
}

View File

@ -105,7 +105,8 @@ namespace Ryujinx.Graphics.Shader.Translation
config.GetImageDescriptors(),
config.UsedFeatures.HasFlag(FeatureFlags.InstanceId),
config.UsedFeatures.HasFlag(FeatureFlags.RtLayer),
config.ClipDistancesWritten);
config.ClipDistancesWritten,
config.OmapTargets);
return program;
}

View File

@ -9,6 +9,8 @@
<TieredCompilation>false</TieredCompilation>
<TieredCompilationQuickJit>false</TieredCompilationQuickJit>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. -->
<SkipGtkInstall>true</SkipGtkInstall>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
@ -19,7 +21,7 @@
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.5.0" />