diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 400f63f56..b8d74a463 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 5031; + private const uint CodeGenVersion = 4714; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 8d805e32e..1bd0182b5 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -239,33 +239,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl context.AppendLine(); } - bool isFragment = context.Config.Stage == ShaderStage.Fragment; - - if (isFragment || context.Config.Stage == ShaderStage.Compute || context.Config.Stage == ShaderStage.Vertex) + if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce()) { - if (isFragment && context.Config.GpuAccessor.QueryEarlyZForce()) - { - context.AppendLine("layout(early_fragment_tests) in;"); - context.AppendLine(); - } - - if ((context.Config.UsedFeatures & (FeatureFlags.FragCoordXY | FeatureFlags.IntegerSampling)) != 0) - { - string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage); - - int scaleElements = context.Config.GetTextureDescriptors().Length + context.Config.GetImageDescriptors().Length; - - if (isFragment) - { - scaleElements++; // Also includes render target scale, for gl_FragCoord. - } - - if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0) - { - AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl"); - context.AppendLine(); - } - } + context.AppendLine("layout(early_fragment_tests) in;"); + context.AppendLine(); } if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0) diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl deleted file mode 100644 index 08c625488..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl +++ /dev/null @@ -1,19 +0,0 @@ -ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex) -{ - float scale = support_buffer.s_render_scale[1 + samplerIndex]; - if (scale == 1.0) - { - return inputVec; - } - return ivec2(vec2(inputVec) * scale); -} - -int Helper_TextureSizeUnscale(int size, int samplerIndex) -{ - float scale = support_buffer.s_render_scale[1 + samplerIndex]; - if (scale == 1.0) - { - return size; - } - return int(float(size) / scale); -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl deleted file mode 100644 index 07a38a7a6..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl +++ /dev/null @@ -1,26 +0,0 @@ -ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex) -{ - float scale = support_buffer.s_render_scale[1 + samplerIndex]; - if (scale == 1.0) - { - return inputVec; - } - if (scale < 0.0) // If less than 0, try interpolate between texels by using the screen position. - { - return ivec2(vec2(inputVec) * (-scale) + mod(gl_FragCoord.xy, 0.0 - scale)); - } - else - { - return ivec2(vec2(inputVec) * scale); - } -} - -int Helper_TextureSizeUnscale(int size, int samplerIndex) -{ - float scale = abs(support_buffer.s_render_scale[1 + samplerIndex]); - if (scale == 1.0) - { - return size; - } - return int(float(size) / scale); -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl deleted file mode 100644 index 72baa441d..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl +++ /dev/null @@ -1,20 +0,0 @@ -ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex) -{ - float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]); - if (scale == 1.0) - { - return inputVec; - } - - return ivec2(vec2(inputVec) * scale); -} - -int Helper_TextureSizeUnscale(int size, int samplerIndex) -{ - float scale = abs(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]); - if (scale == 1.0) - { - return size; - } - return int(float(size) / scale); -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index 71e40fe7c..6cf36a2a6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -101,6 +101,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier"); Add(Instruction.Minimum, InstType.CallBinary, "min"); Add(Instruction.MinimumU32, InstType.CallBinary, "min"); + Add(Instruction.Modulo, InstType.CallBinary, "mod"); Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32); Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index ef5260d10..dfc8197b6 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -97,30 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCallBuilder.Append(str); } - string ApplyScaling(string vector) - { - if (context.Config.Stage.SupportsRenderScale() && - texOp.Inst == Instruction.ImageLoad && - !isBindless && - !isIndexed) - { - // Image scales start after texture ones. - int scaleIndex = context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp); - - if (pCount == 3 && isArray) - { - // The array index is not scaled, just x and y. - vector = $"ivec3(Helper_TexelFetchScale(({vector}).xy, {scaleIndex}), ({vector}).z)"; - } - else if (pCount == 2 && !isArray) - { - vector = $"Helper_TexelFetchScale({vector}, {scaleIndex})"; - } - } - - return vector; - } - if (pCount > 1) { string[] elems = new string[pCount]; @@ -130,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions elems[index] = Src(AggregateType.S32); } - Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})")); + Append($"ivec{pCount}({string.Join(", ", elems)})"); } else { @@ -584,53 +560,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } } - string ApplyScaling(string vector) - { - if (intCoords) - { - if (context.Config.Stage.SupportsRenderScale() && - !isBindless && - !isIndexed) - { - int index = context.Config.FindTextureDescriptorIndex(texOp); - - if (pCount == 3 && isArray) - { - // The array index is not scaled, just x and y. - vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + index + "), (" + vector + ").z)"; - } - else if (pCount == 2 && !isArray) - { - vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")"; - } - } - } - - return vector; - } - - string ApplyBias(string vector) - { - int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision(); - if (isGather && gatherBiasPrecision != 0) - { - // GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels. - // Offset by the gather precision divided by 2 to correct for rounding. - - if (pCount == 1) - { - vector = $"{vector} + (1.0 / (float(textureSize({samplerName}, 0)) * float({1 << (gatherBiasPrecision + 1)})))"; - } - else - { - vector = $"{vector} + (1.0 / (vec{pCount}(textureSize({samplerName}, 0).{"xyz".Substring(0, pCount)}) * float({1 << (gatherBiasPrecision + 1)})))"; - } - } - - return vector; - } - - Append(ApplyBias(ApplyScaling(AssemblePVector(pCount)))); + Append(AssemblePVector(pCount)); string AssembleDerivativesVector(int count) { @@ -750,7 +680,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } else { - (TextureDescriptor descriptor, int descriptorIndex) = context.Config.FindTextureDescriptor(texOp); + TextureDescriptor descriptor = context.Config.FindTextureDescriptor(texOp); bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer; string texCall; @@ -767,14 +697,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}"; } - if (context.Config.Stage.SupportsRenderScale() && - (texOp.Index < 2 || (texOp.Type & SamplerType.Mask) == SamplerType.Texture3D) && - !isBindless && - !isIndexed) - { - texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})"; - } - return texCall; } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs index 0ef89b398..7af6d316e 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public Dictionary OutputsPerPatch { get; } = new Dictionary(); public Instruction CoordTemp { get; set; } + public StructuredFunction CurrentFunction { get; set; } private readonly Dictionary _locals = new Dictionary(); private readonly Dictionary _localForArgs = new Dictionary(); private readonly Dictionary _funcArgs = new Dictionary(); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index fda0dc47c..eb64f8241 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Numerics; using static Spv.Specification; @@ -114,6 +113,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); Add(Instruction.Minimum, GenerateMinimum); Add(Instruction.MinimumU32, GenerateMinimumU32); + Add(Instruction.Modulo, GenerateModulo); Add(Instruction.Multiply, GenerateMultiply); Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32); Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32); @@ -744,8 +744,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv pCoords = Src(AggregateType.S32); } - pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords: true, isBindless, isIndexed, isArray, pCount); - (var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)]; var image = context.Load(imageType, imageVariable); @@ -1040,6 +1038,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin); } + private static OperationResult GenerateModulo(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FMod, null); + } + private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation) { return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul); @@ -1101,7 +1104,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation) { - context.Return(); + if (operation.SourcesCount != 0) + { + context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0))); + } + else + { + context.Return(); + } + return OperationResult.Invalid; } @@ -1439,35 +1450,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv } } - SpvInstruction ApplyBias(SpvInstruction vector, SpvInstruction image) - { - int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision(); - if (isGather && gatherBiasPrecision != 0) - { - // GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels. - // Offset by the gather precision divided by 2 to correct for rounding. - var sizeType = pCount == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), pCount); - var pVectorType = pCount == 1 ? context.TypeFP32() : context.TypeVector(context.TypeFP32(), pCount); - - var bias = context.Constant(context.TypeFP32(), (float)(1 << (gatherBiasPrecision + 1))); - var biasVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(bias, pCount).ToArray()); - - var one = context.Constant(context.TypeFP32(), 1f); - var oneVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(one, pCount).ToArray()); - - var divisor = context.FMul( - pVectorType, - context.ConvertSToF(pVectorType, context.ImageQuerySize(sizeType, image)), - biasVector); - - vector = context.FAdd(pVectorType, vector, context.FDiv(pVectorType, oneVector, divisor)); - } - - return vector; - } - SpvInstruction pCoords = AssemblePVector(pCount); - pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount); SpvInstruction AssembleDerivativesVector(int count) { @@ -1638,8 +1621,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv image = context.Image(imageType, image); } - pCoords = ApplyBias(pCoords, image); - var operands = operandsList.ToArray(); SpvInstruction result; @@ -1755,11 +1736,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index); } - if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D) - { - result = ScalingHelpers.ApplyUnscaling(context, texOp.WithType(type), result, isBindless, isIndexed); - } - return new OperationResult(AggregateType.S32, result); } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs deleted file mode 100644 index c8b21e881..000000000 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs +++ /dev/null @@ -1,227 +0,0 @@ -using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using Ryujinx.Graphics.Shader.StructuredIr; -using Ryujinx.Graphics.Shader.Translation; -using static Spv.Specification; - -namespace Ryujinx.Graphics.Shader.CodeGen.Spirv -{ - using SpvInstruction = Spv.Generator.Instruction; - - static class ScalingHelpers - { - public static SpvInstruction ApplyScaling( - CodeGenContext context, - AstTextureOperation texOp, - SpvInstruction vector, - bool intCoords, - bool isBindless, - bool isIndexed, - bool isArray, - int pCount) - { - if (intCoords) - { - if (context.Config.Stage.SupportsRenderScale() && - !isBindless && - !isIndexed) - { - int index = texOp.Inst == Instruction.ImageLoad - ? context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp) - : context.Config.FindTextureDescriptorIndex(texOp); - - if (pCount == 3 && isArray) - { - return ApplyScaling2DArray(context, vector, index); - } - else if (pCount == 2 && !isArray) - { - return ApplyScaling2D(context, vector, index); - } - } - } - - return vector; - } - - private static SpvInstruction ApplyScaling2DArray(CodeGenContext context, SpvInstruction vector, int index) - { - // The array index is not scaled, just x and y. - var vectorXY = context.VectorShuffle(context.TypeVector(context.TypeS32(), 2), vector, vector, 0, 1); - var vectorZ = context.CompositeExtract(context.TypeS32(), vector, 2); - var vectorXYScaled = ApplyScaling2D(context, vectorXY, index); - var vectorScaled = context.CompositeConstruct(context.TypeVector(context.TypeS32(), 3), vectorXYScaled, vectorZ); - - return vectorScaled; - } - - private static SpvInstruction ApplyScaling2D(CodeGenContext context, SpvInstruction vector, int index) - { - var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32()); - var fieldIndex = context.Constant(context.TypeU32(), 4); - var scaleIndex = context.Constant(context.TypeU32(), index); - - if (context.Config.Stage == ShaderStage.Vertex) - { - var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32()); - var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3)); - var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer); - - scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount); - } - - scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1)); - - var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex); - var scale = context.Load(context.TypeFP32(), scaleElemPointer); - - var ivector2Type = context.TypeVector(context.TypeS32(), 2); - var localVector = context.CoordTemp; - - var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f)); - - var mergeLabel = context.Label(); - - if (context.Config.Stage == ShaderStage.Fragment) - { - var scaledInterpolatedLabel = context.Label(); - var scaledNoInterpolationLabel = context.Label(); - - var needsInterpolation = context.FOrdLessThan(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 0f)); - - context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone); - context.BranchConditional(needsInterpolation, scaledInterpolatedLabel, scaledNoInterpolationLabel); - - // scale < 0.0 - context.AddLabel(scaledInterpolatedLabel); - - ApplyScalingInterpolated(context, localVector, vector, scale); - context.Branch(mergeLabel); - - // scale >= 0.0 - context.AddLabel(scaledNoInterpolationLabel); - - ApplyScalingNoInterpolation(context, localVector, vector, scale); - context.Branch(mergeLabel); - - context.AddLabel(mergeLabel); - - var passthroughLabel = context.Label(); - var finalMergeLabel = context.Label(); - - context.SelectionMerge(finalMergeLabel, SelectionControlMask.MaskNone); - context.BranchConditional(passthrough, passthroughLabel, finalMergeLabel); - - context.AddLabel(passthroughLabel); - - context.Store(localVector, vector); - context.Branch(finalMergeLabel); - - context.AddLabel(finalMergeLabel); - - return context.Load(ivector2Type, localVector); - } - else - { - var passthroughLabel = context.Label(); - var scaledLabel = context.Label(); - - context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone); - context.BranchConditional(passthrough, passthroughLabel, scaledLabel); - - // scale == 1.0 - context.AddLabel(passthroughLabel); - - context.Store(localVector, vector); - context.Branch(mergeLabel); - - // scale != 1.0 - context.AddLabel(scaledLabel); - - ApplyScalingNoInterpolation(context, localVector, vector, scale); - context.Branch(mergeLabel); - - context.AddLabel(mergeLabel); - - return context.Load(ivector2Type, localVector); - } - } - - private static void ApplyScalingInterpolated(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale) - { - var vector2Type = context.TypeVector(context.TypeFP32(), 2); - - var scaleNegated = context.FNegate(context.TypeFP32(), scale); - var scaleVector = context.CompositeConstruct(vector2Type, scaleNegated, scaleNegated); - - var vectorFloat = context.ConvertSToF(vector2Type, vector); - var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scaleNegated); - - var fragCoordPointer = context.Inputs[new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord)]; - var fragCoord = context.Load(context.TypeVector(context.TypeFP32(), 4), fragCoordPointer); - var fragCoordXY = context.VectorShuffle(vector2Type, fragCoord, fragCoord, 0, 1); - - var scaleMod = context.FMod(vector2Type, fragCoordXY, scaleVector); - var vectorInterpolated = context.FAdd(vector2Type, vectorScaled, scaleMod); - - context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorInterpolated)); - } - - private static void ApplyScalingNoInterpolation(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale) - { - if (context.Config.Stage == ShaderStage.Vertex) - { - scale = context.GlslFAbs(context.TypeFP32(), scale); - } - - var vector2Type = context.TypeVector(context.TypeFP32(), 2); - - var vectorFloat = context.ConvertSToF(vector2Type, vector); - var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scale); - - context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorScaled)); - } - - public static SpvInstruction ApplyUnscaling( - CodeGenContext context, - AstTextureOperation texOp, - SpvInstruction size, - bool isBindless, - bool isIndexed) - { - if (context.Config.Stage.SupportsRenderScale() && - !isBindless && - !isIndexed) - { - int index = context.Config.FindTextureDescriptorIndex(texOp); - - var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32()); - var fieldIndex = context.Constant(context.TypeU32(), 4); - var scaleIndex = context.Constant(context.TypeU32(), index); - - if (context.Config.Stage == ShaderStage.Vertex) - { - var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32()); - var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3)); - var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer); - - scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount); - } - - scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1)); - - var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex); - var scale = context.GlslFAbs(context.TypeFP32(), context.Load(context.TypeFP32(), scaleElemPointer)); - - var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f)); - - var sizeFloat = context.ConvertSToF(context.TypeFP32(), size); - var sizeUnscaled = context.FDiv(context.TypeFP32(), sizeFloat, scale); - var sizeUnscaledInt = context.ConvertFToS(context.TypeS32(), sizeUnscaled); - - return context.Select(context.TypeS32(), passthrough, size, sizeUnscaledInt); - } - - return size; - } - } -} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs index 3ccfd7f55..0fa954e15 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs @@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv public readonly FuncBinaryInstruction GlslSMax; public readonly FuncBinaryInstruction GlslFMin; public readonly FuncBinaryInstruction GlslSMin; + public readonly FuncBinaryInstruction FMod; public readonly FuncBinaryInstruction FMul; public readonly FuncBinaryInstruction IMul; public readonly FuncBinaryInstruction FSub; @@ -174,6 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv GlslSMax = context.GlslSMax; GlslFMin = context.GlslFMin; GlslSMin = context.GlslSMin; + FMod = context.FMod; FMul = context.FMul; IMul = context.IMul; FSub = context.FSub; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index 3e11a9749..a55e09fd3 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -144,10 +144,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex) { - var function = info.Functions[funcIndex]; - - (_, var spvFunc) = context.GetFunction(funcIndex); + (var function, var spvFunc) = context.GetFunction(funcIndex); + context.CurrentFunction = function; context.AddFunction(spvFunc); context.StartFunction(); diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index 817755bbe..f7afe5071 100644 --- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation MemoryBarrier, Minimum, MinimumU32, + Modulo, Multiply, MultiplyHighS32, MultiplyHighU32, diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj index 3434e2a81..2efcbca4f 100644 --- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj +++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -4,10 +4,6 @@ net7.0 - - - - @@ -25,9 +21,6 @@ - - - diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs index a44f13cc0..6c27279c9 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -27,10 +27,5 @@ namespace Ryujinx.Graphics.Shader.StructuredIr CbufSlot = cbufSlot; Handle = handle; } - - public AstTextureOperation WithType(SamplerType type) - { - return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index); - } } } \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs index ab8132540..44f0fad95 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -104,6 +104,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); + Add(Instruction.Modulo, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32); Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs index 939a52f3e..6846245e6 100644 --- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { static class StructuredProgram { - public static StructuredProgramInfo MakeStructuredProgram(Function[] functions, ShaderConfig config) + public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList functions, ShaderConfig config) { StructuredProgramContext context = new StructuredProgramContext(config); - for (int funcIndex = 0; funcIndex < functions.Length; funcIndex++) + for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++) { Function function = functions[funcIndex]; diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs index 0c51b16f4..2786caaa0 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -49,13 +49,17 @@ namespace Ryujinx.Graphics.Shader.Translation private readonly List _operations; private readonly Dictionary _labels; - public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) + public EmitterContext() + { + _operations = new List(); + _labels = new Dictionary(); + } + + public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this() { Program = program; Config = config; IsNonMain = isNonMain; - _operations = new List(); - _labels = new Dictionary(); EmitStart(); } diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs index e41a28f12..6d4104cee 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -307,6 +307,11 @@ namespace Ryujinx.Graphics.Shader.Translation return context.Add(fpType | Instruction.Minimum, Local(), a, b); } + public static Operand FPModulo(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Modulo, Local(), a, b); + } + public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) { return context.Add(fpType | Instruction.Multiply, Local(), a, b); @@ -656,7 +661,6 @@ namespace Ryujinx.Graphics.Shader.Translation public static void Return(this EmitterContext context, Operand returnValue) { - context.PrepareForReturn(); context.Add(Instruction.Return, null, returnValue); } diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs new file mode 100644 index 000000000..206facd46 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs @@ -0,0 +1,134 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class HelperFunctionManager + { + private readonly List _functionList; + private readonly Dictionary _functionIds; + private readonly ShaderStage _stage; + + public HelperFunctionManager(List functionList, ShaderStage stage) + { + _functionList = functionList; + _functionIds = new Dictionary(); + _stage = stage; + } + + public int GetOrCreateFunctionId(HelperFunctionName functionName) + { + if (_functionIds.TryGetValue(functionName, out int functionId)) + { + return functionId; + } + + Function function = GenerateFunction(functionName); + functionId = _functionList.Count; + _functionList.Add(function); + _functionIds.Add(functionName, functionId); + + return functionId; + } + + private Function GenerateFunction(HelperFunctionName functionName) + { + return functionName switch + { + HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(), + HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(), + _ => throw new ArgumentException($"Invalid function name {functionName}") + }; + } + + private Function GenerateTexelFetchScaleFunction() + { + EmitterContext context = new EmitterContext(); + + Operand input = Argument(0); + Operand samplerIndex = Argument(1); + Operand index = GetScaleIndex(context, samplerIndex); + + Operand scale = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + int inArgumentsCount; + + if (_stage == ShaderStage.Fragment) + { + Operand scaleIsLessThanZero = context.FPCompareLess(scale, ConstF(0f)); + Operand lblScaleGreaterOrEqualZero = Label(); + + context.BranchIfFalse(lblScaleGreaterOrEqualZero, scaleIsLessThanZero); + + Operand negScale = context.FPNegate(scale); + Operand inputScaled = context.FPMultiply(context.IConvertS32ToFP32(input), negScale); + Operand fragCoordX = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0)); + Operand fragCoordY = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1)); + Operand fragCoord = context.ConditionalSelect(Argument(2), fragCoordY, fragCoordX); + Operand inputBias = context.FPModulo(fragCoord, negScale); + Operand inputWithBias = context.FPAdd(inputScaled, inputBias); + + context.Return(context.FP32ConvertToS32(inputWithBias)); + context.MarkLabel(lblScaleGreaterOrEqualZero); + + inArgumentsCount = 3; + } + else + { + inArgumentsCount = 2; + } + + Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputScaled2)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0); + } + + private Function GenerateTextureSizeUnscaleFunction() + { + EmitterContext context = new EmitterContext(); + + Operand input = Argument(0); + Operand samplerIndex = Argument(1); + Operand index = GetScaleIndex(context, samplerIndex); + + Operand scale = context.FPAbsolute(context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index)); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputUnscaled)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0); + } + + private Operand GetScaleIndex(EmitterContext context, Operand index) + { + switch (_stage) + { + case ShaderStage.Vertex: + Operand fragScaleCount = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.FragmentRenderScaleCount)); + return context.IAdd(Const(1), context.IAdd(index, fragScaleCount)); + default: + return context.IAdd(Const(1), index); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs new file mode 100644 index 000000000..5accdf65f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + enum HelperFunctionName + { + TexelFetchScale, + TextureSizeUnscale + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs b/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs index 711661c9b..6b55a484b 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Graphics.Shader.Translation { static class Rewriter { - public static void RunPass(BasicBlock[] blocks, ShaderConfig config) + public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config) { bool isVertexShader = config.Stage == ShaderStage.Vertex; bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters(); @@ -54,9 +54,14 @@ namespace Ryujinx.Graphics.Shader.Translation if (operation is TextureOperation texOp) { + node = InsertTexelFetchScale(hfm, node, config); + node = InsertTextureSizeUnscale(hfm, node, config); + if (texOp.Inst == Instruction.TextureSample) { - node = RewriteTextureSample(node, config); + node = InsertCoordNormalization(node, config); + node = InsertCoordGatherBias(node, config); + node = InsertConstOffsets(node, config); if (texOp.Type == SamplerType.TextureBuffer && !supportsSnormBufferTextureFormat) { @@ -344,10 +349,279 @@ namespace Ryujinx.Graphics.Shader.Translation return node; } - private static LinkedListNode RewriteTextureSample(LinkedListNode node, ShaderConfig config) + private static LinkedListNode InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode node, ShaderConfig config) { TextureOperation texOp = (TextureOperation)node.Value; + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + bool isImage = IsImageInstructionWithScale(texOp.Inst); + + if ((texOp.Inst == Instruction.TextureSample || isImage) && + intCoords && + !isBindless && + !isIndexed && + config.Stage.SupportsRenderScale() && + TypeSupportsScale(texOp.Type)) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); + int samplerIndex = isImage + ? config.GetTextureDescriptors().Length + config.FindImageDescriptorIndex(texOp) + : config.FindTextureDescriptorIndex(texOp); + + for (int index = 0; index < coordsCount; index++) + { + Operand scaledCoord = Local(); + Operand[] callArgs; + + if (config.Stage == ShaderStage.Fragment) + { + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) }; + } + else + { + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) }; + } + + node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs)); + + texOp.SetSource(coordsIndex + index, scaledCoord); + } + } + + return node; + } + + private static LinkedListNode InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode node, ShaderConfig config) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + if (texOp.Inst == Instruction.TextureSize && + texOp.Index < 2 && + !isBindless && + !isIndexed && + config.Stage.SupportsRenderScale() && + TypeSupportsScale(texOp.Type)) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale); + int samplerIndex = config.FindTextureDescriptorIndex(texOp, ignoreType: true); + + for (int index = texOp.DestsCount - 1; index >= 0; index--) + { + Operand dest = texOp.GetDest(index); + + Operand unscaledSize = Local(); + + // Replace all uses with the unscaled size value. + // This must be done before the call is added, since it also is a use of the original size. + foreach (INode useOp in dest.UseOps) + { + for (int srcIndex = 0; srcIndex < useOp.SourcesCount; srcIndex++) + { + if (useOp.GetSource(srcIndex) == dest) + { + useOp.SetSource(srcIndex, unscaledSize); + } + } + } + + Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) }; + + node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs)); + } + } + + return node; + } + + private static bool IsImageInstructionWithScale(Instruction inst) + { + // Currently, we don't support scaling images that are modified, + // so we only need to care about the load instruction. + return inst == Instruction.ImageLoad; + } + + private static bool TypeSupportsScale(SamplerType type) + { + return (type & SamplerType.Mask) == SamplerType.Texture2D; + } + + private static LinkedListNode InsertCoordNormalization(LinkedListNode node, ShaderConfig config) + { + // Emulate non-normalized coordinates by normalizing the coordinates on the shader. + // Without normalization, the coordinates are expected to the in the [0, W or H] range, + // and otherwise, it is expected to be in the [0, 1] range. + // We normalize by dividing the coords by the texture size. + + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot); + + if (isCoordNormalized || intCoords) + { + return node; + } + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + config.SetUsedFeature(FeatureFlags.IntegerSampling); + + int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; + + for (int index = 0; index < normCoordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.CbufSlot, + texOp.Handle, + index, + new[] { coordSize }, + texSizeSources)); + + config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle); + + Operand source = texOp.GetSource(coordsIndex + index); + + Operand coordNormalized = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); + + texOp.SetSource(coordsIndex + index, coordNormalized); + } + + return node; + } + + private static LinkedListNode InsertCoordGatherBias(LinkedListNode node, ShaderConfig config) + { + // The gather behavior when the coordinate sits right in the middle of two texels is not well defined. + // To ensure the correct texel is sampled, we add a small bias value to the coordinate. + // This value is calculated as the minimum value required to change the texel it will sample from, + // and is 0 if the host does not require the bias. + + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + + int gatherBiasPrecision = config.GpuAccessor.QueryHostGatherBiasPrecision(); + + if (!isGather || gatherBiasPrecision == 0) + { + return node; + } + + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + config.SetUsedFeature(FeatureFlags.IntegerSampling); + + int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; + + for (int index = 0; index < normCoordsCount; index++) + { + Operand coordSize = Local(); + Operand scaledSize = Local(); + Operand bias = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.CbufSlot, + texOp.Handle, + index, + new[] { coordSize }, + texSizeSources)); + + config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle); + + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Multiply, + scaledSize, + GenerateI2f(node, coordSize), + ConstF((float)(1 << (gatherBiasPrecision + 1))))); + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, bias, ConstF(1f), scaledSize)); + + Operand source = texOp.GetSource(coordsIndex + index); + + Operand coordBiased = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordBiased, source, bias)); + + texOp.SetSource(coordsIndex + index, coordBiased); + } + + return node; + } + + private static LinkedListNode InsertConstOffsets(LinkedListNode node, ShaderConfig config) + { + // Non-constant texture offsets are not allowed (according to the spec), + // however some GPUs does support that. + // For GPUs where it is not supported, we can replace the instruction with the following: + // For texture*Offset, we replace it by texture*, and add the offset to the P coords. + // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords). + // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly. + // For textureGatherOffset, we split the operation into up to 4 operations, one for each component + // that is accessed, where each textureGather operation has a different offset for each pixel. + + TextureOperation texOp = (TextureOperation)node.Value; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; @@ -355,9 +629,7 @@ namespace Ryujinx.Graphics.Shader.Translation bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; - bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot); - - if (!hasInvalidOffset && isCoordNormalized) + if (!hasInvalidOffset) { return node; } @@ -454,7 +726,7 @@ namespace Ryujinx.Graphics.Shader.Translation hasInvalidOffset &= !areAllOffsetsConstant; - if (!hasInvalidOffset && isCoordNormalized) + if (!hasInvalidOffset) { return node; } @@ -473,63 +745,6 @@ namespace Ryujinx.Graphics.Shader.Translation int componentIndex = texOp.Index; - Operand Float(Operand value) - { - Operand res = Local(); - - node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value)); - - return res; - } - - // Emulate non-normalized coordinates by normalizing the coordinates on the shader. - // Without normalization, the coordinates are expected to the in the [0, W or H] range, - // and otherwise, it is expected to be in the [0, 1] range. - // We normalize by dividing the coords by the texture size. - if (!isCoordNormalized && !intCoords) - { - config.SetUsedFeature(FeatureFlags.IntegerSampling); - - int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; - - for (int index = 0; index < normCoordsCount; index++) - { - Operand coordSize = Local(); - - Operand[] texSizeSources; - - if (isBindless || isIndexed) - { - texSizeSources = new Operand[] { sources[0], Const(0) }; - } - else - { - texSizeSources = new Operand[] { Const(0) }; - } - - node.List.AddBefore(node, new TextureOperation( - Instruction.TextureSize, - texOp.Type, - texOp.Format, - texOp.Flags, - texOp.CbufSlot, - texOp.Handle, - index, - new[] { coordSize }, - texSizeSources)); - - config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle); - - Operand source = sources[coordsIndex + index]; - - Operand coordNormalized = Local(); - - node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, Float(coordSize))); - - sources[coordsIndex + index] = coordNormalized; - } - } - Operand[] dests = new Operand[texOp.DestsCount]; for (int i = 0; i < texOp.DestsCount; i++) @@ -541,15 +756,7 @@ namespace Ryujinx.Graphics.Shader.Translation LinkedListNode oldNode = node; - // Technically, non-constant texture offsets are not allowed (according to the spec), - // however some GPUs does support that. - // For GPUs where it is not supported, we can replace the instruction with the following: - // For texture*Offset, we replace it by texture*, and add the offset to the P coords. - // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords). - // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly. - // For textureGatherOffset, we split the operation into up to 4 operations, one for each component - // that is accessed, where each textureGather operation has a different offset for each pixel. - if (hasInvalidOffset && isGather && !isShadow) + if (isGather && !isShadow) { config.SetUsedFeature(FeatureFlags.IntegerSampling); @@ -557,7 +764,7 @@ namespace Ryujinx.Graphics.Shader.Translation sources.CopyTo(newSources, 0); - Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount); + Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount); int destIndex = 0; @@ -576,7 +783,11 @@ namespace Ryujinx.Graphics.Shader.Translation Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)]; - node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index]))); + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Divide, + offset, + GenerateI2f(node, intOffset), + GenerateI2f(node, texSizes[index]))); Operand source = sources[coordsIndex + index]; @@ -603,45 +814,46 @@ namespace Ryujinx.Graphics.Shader.Translation } else { - if (hasInvalidOffset) + if (intCoords) { - if (intCoords) + for (int index = 0; index < coordsCount; index++) { - for (int index = 0; index < coordsCount; index++) - { - Operand source = sources[coordsIndex + index]; + Operand source = sources[coordsIndex + index]; - Operand coordPlusOffset = Local(); + Operand coordPlusOffset = Local(); - node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index])); + node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index])); - sources[coordsIndex + index] = coordPlusOffset; - } + sources[coordsIndex + index] = coordPlusOffset; } - else + } + else + { + config.SetUsedFeature(FeatureFlags.IntegerSampling); + + Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount); + + for (int index = 0; index < coordsCount; index++) { - config.SetUsedFeature(FeatureFlags.IntegerSampling); + config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle); - Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount); + Operand offset = Local(); - for (int index = 0; index < coordsCount; index++) - { - config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle); + Operand intOffset = offsets[index]; - Operand offset = Local(); + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Divide, + offset, + GenerateI2f(node, intOffset), + GenerateI2f(node, texSizes[index]))); - Operand intOffset = offsets[index]; + Operand source = sources[coordsIndex + index]; - node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index]))); + Operand coordPlusOffset = Local(); - Operand source = sources[coordsIndex + index]; + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset)); - Operand coordPlusOffset = Local(); - - node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset)); - - sources[coordsIndex + index] = coordPlusOffset; - } + sources[coordsIndex + index] = coordPlusOffset; } } @@ -669,22 +881,13 @@ namespace Ryujinx.Graphics.Shader.Translation return node; } - private static Operand[] InsertTextureSize( + private static Operand[] InsertTextureLod( LinkedListNode node, TextureOperation texOp, Operand[] lodSources, Operand bindlessHandle, int coordsCount) { - Operand Int(Operand value) - { - Operand res = Local(); - - node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value)); - - return res; - } - Operand[] texSizes = new Operand[coordsCount]; Operand lod = Local(); @@ -708,11 +911,11 @@ namespace Ryujinx.Graphics.Shader.Translation if (bindlessHandle != null) { - texSizeSources = new Operand[] { bindlessHandle, Int(lod) }; + texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) }; } else { - texSizeSources = new Operand[] { Int(lod) }; + texSizeSources = new Operand[] { GenerateF2i(node, lod) }; } node.List.AddBefore(node, new TextureOperation( @@ -796,6 +999,24 @@ namespace Ryujinx.Graphics.Shader.Translation return node; } + private static Operand GenerateI2f(LinkedListNode node, Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value)); + + return res; + } + + private static Operand GenerateF2i(LinkedListNode node, Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value)); + + return res; + } + private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode node, Operation operation) { Operand GenerateLoad(IoVariable ioVariable) diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs index 775607972..73525cb27 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -860,7 +860,7 @@ namespace Ryujinx.Graphics.Shader.Translation return descriptors; } - public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp) + public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp) { TextureDescriptor[] descriptors = GetTextureDescriptors(); @@ -872,11 +872,11 @@ namespace Ryujinx.Graphics.Shader.Translation descriptor.HandleIndex == texOp.Handle && descriptor.Format == texOp.Format) { - return (descriptor, i); + return descriptor; } } - return (default, -1); + return default; } private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp) @@ -897,12 +897,30 @@ namespace Ryujinx.Graphics.Shader.Translation return -1; } - public int FindTextureDescriptorIndex(AstTextureOperation texOp) + private static int FindDescriptorIndex(TextureDescriptor[] array, TextureOperation texOp, bool ignoreType = false) { - return FindDescriptorIndex(GetTextureDescriptors(), texOp); + for (int i = 0; i < array.Length; i++) + { + var descriptor = array[i]; + + if ((descriptor.Type == texOp.Type || ignoreType) && + descriptor.CbufSlot == texOp.CbufSlot && + descriptor.HandleIndex == texOp.Handle && + descriptor.Format == texOp.Format) + { + return i; + } + } + + return -1; } - public int FindImageDescriptorIndex(AstTextureOperation texOp) + public int FindTextureDescriptorIndex(TextureOperation texOp, bool ignoreType = false) + { + return FindDescriptorIndex(GetTextureDescriptors(), texOp, ignoreType); + } + + public int FindImageDescriptorIndex(TextureOperation texOp) { return FindDescriptorIndex(GetImageDescriptors(), texOp); } diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs index 53f1e8475..867e24379 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs @@ -1,11 +1,11 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; -using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using System.Collections.Generic; namespace Ryujinx.Graphics.Shader.Translation { static class ShaderIdentifier { - public static ShaderIdentification Identify(Function[] functions, ShaderConfig config) + public static ShaderIdentification Identify(IReadOnlyList functions, ShaderConfig config) { if (config.Stage == ShaderStage.Geometry && config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles && @@ -20,12 +20,12 @@ namespace Ryujinx.Graphics.Shader.Translation return ShaderIdentification.None; } - private static bool IsLayerPassthroughGeometryShader(Function[] functions, out int layerInputAttr) + private static bool IsLayerPassthroughGeometryShader(IReadOnlyList functions, out int layerInputAttr) { bool writesLayer = false; layerInputAttr = 0; - if (functions.Length != 1) + if (functions.Count != 1) { return false; } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs index 87d97e52e..5bbc00097 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using Ryujinx.Graphics.Shader.StructuredIr; using Ryujinx.Graphics.Shader.Translation.Optimizations; using System; +using System.Collections.Generic; using System.Linq; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; @@ -44,7 +45,14 @@ namespace Ryujinx.Graphics.Shader.Translation } } - Function[] funcs = new Function[functions.Length]; + List funcs = new List(functions.Length); + + for (int i = 0; i < functions.Length; i++) + { + funcs.Add(null); + } + + HelperFunctionManager hfm = new HelperFunctionManager(funcs, config.Stage); for (int i = 0; i < functions.Length; i++) { @@ -71,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation Ssa.Rename(cfg.Blocks); Optimizer.RunPass(cfg.Blocks, config); - Rewriter.RunPass(cfg.Blocks, config); + Rewriter.RunPass(hfm, cfg.Blocks, config); } funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);