diff --git a/Ryujinx.Graphics.GAL/Capabilities.cs b/Ryujinx.Graphics.GAL/Capabilities.cs index 8a2dd8b6..f06c3c4b 100644 --- a/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/Ryujinx.Graphics.GAL/Capabilities.cs @@ -2,7 +2,8 @@ namespace Ryujinx.Graphics.GAL { public struct Capabilities { - public bool SupportsAstcCompression { get; } + public bool SupportsAstcCompression { get; } + public bool SupportsNonConstantTextureOffset { get; } public int MaximumViewportDimensions { get; } public int MaximumComputeSharedMemorySize { get; } @@ -10,14 +11,16 @@ namespace Ryujinx.Graphics.GAL public Capabilities( bool supportsAstcCompression, + bool supportsNonConstantTextureOffset, int maximumViewportDimensions, int maximumComputeSharedMemorySize, int storageBufferOffsetAlignment) { - SupportsAstcCompression = supportsAstcCompression; - MaximumViewportDimensions = maximumViewportDimensions; - MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; - StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + SupportsAstcCompression = supportsAstcCompression; + SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + MaximumViewportDimensions = maximumViewportDimensions; + MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; + StorageBufferOffsetAlignment = storageBufferOffsetAlignment; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index d54c1942..648e073c 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -355,7 +355,8 @@ namespace Ryujinx.Graphics.Gpu.Shader return new ShaderCapabilities( _context.Capabilities.MaximumViewportDimensions, _context.Capabilities.MaximumComputeSharedMemorySize, - _context.Capabilities.StorageBufferOffsetAlignment); + _context.Capabilities.StorageBufferOffsetAlignment, + _context.Capabilities.SupportsNonConstantTextureOffset); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs index 7524dc1d..dc147484 100644 --- a/Ryujinx.Graphics.OpenGL/HwCapabilities.cs +++ b/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -11,11 +11,14 @@ namespace Ryujinx.Graphics.OpenGL private static Lazy _maximumComputeSharedMemorySize = new Lazy(() => GetLimit(All.MaxComputeSharedMemorySize)); private static Lazy _storageBufferOffsetAlignment = new Lazy(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); - public static bool SupportsAstcCompression => _supportsAstcCompression.Value; + private static Lazy _isNvidiaDriver = new Lazy(() => IsNvidiaDriver()); - public static int MaximumViewportDimensions => _maximumViewportDimensions.Value; - public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; - public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; + public static bool SupportsAstcCompression => _supportsAstcCompression.Value; + public static bool SupportsNonConstantTextureOffset => _isNvidiaDriver.Value; + + public static int MaximumViewportDimensions => _maximumViewportDimensions.Value; + public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; + public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; private static bool HasExtension(string name) { @@ -36,5 +39,10 @@ namespace Ryujinx.Graphics.OpenGL { return GL.GetInteger((GetPName)name); } + + private static bool IsNvidiaDriver() + { + return GL.GetString(StringName.Vendor).Equals("NVIDIA Corporation"); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs index eec3e320..ac16a37f 100644 --- a/Ryujinx.Graphics.OpenGL/Renderer.cs +++ b/Ryujinx.Graphics.OpenGL/Renderer.cs @@ -63,6 +63,7 @@ namespace Ryujinx.Graphics.OpenGL { return new Capabilities( HwCapabilities.SupportsAstcCompression, + HwCapabilities.SupportsNonConstantTextureOffset, HwCapabilities.MaximumViewportDimensions, HwCapabilities.MaximumComputeSharedMemorySize, HwCapabilities.StorageBufferOffsetAlignment); diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs index b6cdd7f6..73a71f9e 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -133,6 +133,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions case Instruction.LoadStorage: return InstGenMemory.LoadStorage(context, operation); + case Instruction.Lod: + return InstGenMemory.Lod(context, operation); + case Instruction.PackHalf2x16: return InstGenPacking.PackHalf2x16(context, operation); diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs index 2b4ae7f1..ef998fdd 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -73,6 +73,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions Add(Instruction.LoadLocal, InstType.Special); Add(Instruction.LoadShared, InstType.Special); Add(Instruction.LoadStorage, InstType.Special); + Add(Instruction.Lod, InstType.Special); Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs index ffed4c71..5687ce7e 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -148,6 +148,48 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage); } + public static string Lod(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int coordsCount = texOp.Type.GetDimensions(); + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + + string indexExpr = null; + + if (isIndexed) + { + indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32); + } + + string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + string coordsExpr; + + if (coordsCount > 1) + { + string[] elems = new string[coordsCount]; + + for (int index = 0; index < coordsCount; index++) + { + elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), VariableType.F32); + } + + coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; + } + else + { + coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), VariableType.F32); + } + + return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; + } + public static string StoreLocal(CodeGenContext context, AstOperation operation) { return StoreLocalOrShared(context, operation, DefaultNames.LocalMemoryName); @@ -359,17 +401,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions } } + if (hasExtraCompareArg) + { + Append(Src(VariableType.F32)); + } + if (hasDerivatives) { Append(AssembleDerivativesVector(coordsCount)); // dPdx Append(AssembleDerivativesVector(coordsCount)); // dPdy } - if (hasExtraCompareArg) - { - Append(Src(VariableType.F32)); - } - if (isMultisample) { Append(Src(VariableType.S32)); @@ -446,11 +488,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr); - IAstNode src0 = operation.GetSource(isBindless || isIndexed ? 1 : 0); + int lodSrcIndex = isBindless || isIndexed ? 1 : 0; - string src0Expr = GetSoureExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + IAstNode lod = operation.GetSource(lodSrcIndex); - return $"textureSize({samplerName}, {src0Expr}){GetMask(texOp.Index)}"; + string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex)); + + return $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; } private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage) diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs index 5f0407c2..bffdd0fa 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -71,6 +71,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation LoadLocal, LoadShared, LoadStorage, + Lod, LogarithmB2, LogicalAnd, LogicalExclusiveOr, diff --git a/Ryujinx.Graphics.Shader/ShaderCapabilities.cs b/Ryujinx.Graphics.Shader/ShaderCapabilities.cs index 8e0c95e9..809481b5 100644 --- a/Ryujinx.Graphics.Shader/ShaderCapabilities.cs +++ b/Ryujinx.Graphics.Shader/ShaderCapabilities.cs @@ -3,22 +3,25 @@ namespace Ryujinx.Graphics.Shader public struct ShaderCapabilities { // Initialize with default values for Maxwell. - private static readonly ShaderCapabilities _default = new ShaderCapabilities(32768, 49152, 16); + private static readonly ShaderCapabilities _default = new ShaderCapabilities(0x8000, 0xc000, 16, true); public static ShaderCapabilities Default => _default; - public int MaximumViewportDimensions { get; } - public int MaximumComputeSharedMemorySize { get; } - public int StorageBufferOffsetAlignment { get; } + public int MaximumViewportDimensions { get; } + public int MaximumComputeSharedMemorySize { get; } + public int StorageBufferOffsetAlignment { get; } + public bool SupportsNonConstantTextureOffset { get; } public ShaderCapabilities( - int maximumViewportDimensions, - int maximumComputeSharedMemorySize, - int storageBufferOffsetAlignment) + int maximumViewportDimensions, + int maximumComputeSharedMemorySize, + int storageBufferOffsetAlignment, + bool supportsNonConstantTextureOffset) { - MaximumViewportDimensions = maximumViewportDimensions; - MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; - StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + MaximumViewportDimensions = maximumViewportDimensions; + MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; + StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs index 9614b659..c276959a 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -85,6 +85,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr Add(Instruction.LoadLocal, VariableType.U32, VariableType.S32); Add(Instruction.LoadShared, VariableType.U32, VariableType.S32); Add(Instruction.LoadStorage, VariableType.U32, VariableType.S32, VariableType.S32); + Add(Instruction.Lod, VariableType.F32); Add(Instruction.LogarithmB2, VariableType.Scalar, VariableType.Scalar); Add(Instruction.LogicalAnd, VariableType.Bool, VariableType.Bool, VariableType.Bool); Add(Instruction.LogicalExclusiveOr, VariableType.Bool, VariableType.Bool, VariableType.Bool); @@ -139,9 +140,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr { // TODO: Return correct type depending on source index, // that can improve the decompiler output. - if (inst == Instruction.TextureSample || - inst == Instruction.ImageLoad || - inst == Instruction.ImageStore) + if ( + inst == Instruction.ImageLoad || + inst == Instruction.ImageStore || + inst == Instruction.Lod || + inst == Instruction.TextureSample) { return VariableType.F32; } diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs index 9a17dd83..1cd1df37 100644 --- a/Ryujinx.Graphics.Shader/Translation/Lowering.cs +++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System.Collections.Generic; +using System.Diagnostics; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; @@ -23,13 +24,18 @@ namespace Ryujinx.Graphics.Shader.Translation if (UsesGlobalMemory(operation.Inst)) { - node = LowerGlobal(node, config); + node = RewriteGlobalAccess(node, config); + } + + if (!config.Capabilities.SupportsNonConstantTextureOffset && operation.Inst == Instruction.TextureSample) + { + node = RewriteTextureSample(node); } } } } - private static LinkedListNode LowerGlobal(LinkedListNode node, ShaderConfig config) + private static LinkedListNode RewriteGlobalAccess(LinkedListNode node, ShaderConfig config) { Operation operation = (Operation)node.Value; @@ -117,5 +123,217 @@ namespace Ryujinx.Graphics.Shader.Translation return node; } + + private static LinkedListNode RewriteTextureSample(LinkedListNode node) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + if (!(hasOffset || hasOffsets)) + { + return node; + } + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + + int offsetsCount = coordsCount * (hasOffsets ? 4 : 1); + + Operand[] offsets = new Operand[offsetsCount]; + Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; + + int srcIndex = 0; + int dstIndex = 0; + + int copyCount = 0; + + if (isBindless || isIndexed) + { + copyCount++; + } + + Operand[] lodSources = new Operand[copyCount + coordsCount]; + + for (int index = 0; index < lodSources.Length; index++) + { + lodSources[index] = texOp.GetSource(index); + } + + copyCount += coordsCount; + + if (isArray) + { + copyCount++; + } + + if (isShadow) + { + copyCount++; + } + + if (hasDerivatives) + { + copyCount += coordsCount * 2; + } + + if (isMultisample) + { + copyCount++; + } + else if (hasLodLevel) + { + copyCount++; + } + + for (int index = 0; index < copyCount; index++) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + bool areAllOffsetsConstant = true; + + for (int index = 0; index < offsetsCount; index++) + { + Operand offset = texOp.GetSource(srcIndex++); + + areAllOffsetsConstant &= offset.Type == OperandType.Constant; + + offsets[index] = offset; + } + + if (areAllOffsetsConstant) + { + return node; + } + + if (hasLodBias) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + if (isGather && !isShadow) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + Operand Int(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertFPToS32, res, value)); + + return res; + } + + Operand Float(Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP, res, value)); + + return res; + } + + Operand lod = Local(); + + node.List.AddBefore(node, new TextureOperation( + Instruction.Lod, + texOp.Type, + texOp.Flags, + texOp.Handle, + 1, + lod, + lodSources)); + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + for (int index = 0; index < coordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { sources[0], Int(lod) }; + } + else + { + texSizeSources = new Operand[] { Int(lod) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSize, + texOp.Type, + texOp.Flags, + texOp.Handle, + index, + coordSize, + texSizeSources)); + + Operand offset = Local(); + + Operand intOffset = offsets[index + (hasOffsets ? texOp.Index * coordsCount : 0)]; + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Divide, offset, Float(intOffset), Float(coordSize))); + + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP | Instruction.Add, coordPlusOffset, source, offset)); + + sources[coordsIndex + index] = coordPlusOffset; + } + + int componentIndex; + + if (isGather && !isShadow) + { + Operand gatherComponent = sources[dstIndex - 1]; + + Debug.Assert(gatherComponent.Type == OperandType.Constant); + + componentIndex = gatherComponent.Value; + } + else + { + componentIndex = texOp.Index; + } + + TextureOperation newTexOp = new TextureOperation( + Instruction.TextureSample, + texOp.Type, + texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), + texOp.Handle, + componentIndex, + texOp.Dest, + sources); + + for (int index = 0; index < texOp.SourcesCount; index++) + { + texOp.SetSource(index, null); + } + + LinkedListNode oldNode = node; + + node = node.List.AddBefore(node, newTexOp); + + node.List.Remove(oldNode); + + return node; + } } } \ No newline at end of file