Support non-constant texture offsets on non-NVIDIA gpus
This commit is contained in:
parent
3323a3a042
commit
f2c85c5d58
11 changed files with 319 additions and 33 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,11 +11,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
private static Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
|
||||
private static Lazy<int> _storageBufferOffsetAlignment = new Lazy<int>(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
|
||||
|
||||
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
|
||||
private static Lazy<bool> _isNvidiaDriver = new Lazy<bool>(() => 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,6 +63,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
{
|
||||
return new Capabilities(
|
||||
HwCapabilities.SupportsAstcCompression,
|
||||
HwCapabilities.SupportsNonConstantTextureOffset,
|
||||
HwCapabilities.MaximumViewportDimensions,
|
||||
HwCapabilities.MaximumComputeSharedMemorySize,
|
||||
HwCapabilities.StorageBufferOffsetAlignment);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -71,6 +71,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||
LoadLocal,
|
||||
LoadShared,
|
||||
LoadStorage,
|
||||
Lod,
|
||||
LogarithmB2,
|
||||
LogicalAnd,
|
||||
LogicalExclusiveOr,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<INode> LowerGlobal(LinkedListNode<INode> node, ShaderConfig config)
|
||||
private static LinkedListNode<INode> RewriteGlobalAccess(LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
Operation operation = (Operation)node.Value;
|
||||
|
||||
|
@ -117,5 +123,217 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> RewriteTextureSample(LinkedListNode<INode> 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<INode> oldNode = node;
|
||||
|
||||
node = node.List.AddBefore(node, newTexOp);
|
||||
|
||||
node.List.Remove(oldNode);
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue