Shader invalidation and cache

This commit is contained in:
ReinUsesLisp 2018-08-24 02:39:35 -03:00
commit 68f47db918
10 changed files with 192 additions and 104 deletions

View file

@ -4,9 +4,7 @@ namespace Ryujinx.Graphics.Gal
{ {
public interface IGalShader public interface IGalShader
{ {
void Create(IGalMemory Memory, long Key, GalShaderType Type); void Create(long Key, byte[] BinaryA, byte[] BinaryB, GalShaderType Type);
void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type);
IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long Key); IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long Key);
IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key); IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key);

View file

@ -15,7 +15,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public OGLShaderProgram Current; public OGLShaderProgram Current;
private ConcurrentDictionary<long, OGLShaderStage> Stages; private Dictionary<long, List<OGLShaderStage>> Stages;
private Dictionary<long, OGLShaderStage> TopStages;
private Dictionary<OGLShaderProgram, int> Programs; private Dictionary<OGLShaderProgram, int> Programs;
@ -29,60 +31,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
this.Buffer = Buffer; this.Buffer = Buffer;
Stages = new ConcurrentDictionary<long, OGLShaderStage>(); Stages = new Dictionary<long, List<OGLShaderStage>>();
TopStages = new Dictionary<long, OGLShaderStage>();
Programs = new Dictionary<OGLShaderProgram, int>(); Programs = new Dictionary<OGLShaderProgram, int>();
} }
public void Create(IGalMemory Memory, long Key, GalShaderType Type) public void Create(
{ long Key,
Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, Key, 0, false, Type)); byte[] BinaryA,
} byte[] BinaryB,
public void Create(IGalMemory Memory, long VpAPos, long Key, GalShaderType Type)
{
Stages.GetOrAdd(Key, (Stage) => ShaderStageFactory(Memory, VpAPos, Key, true, Type));
}
private OGLShaderStage ShaderStageFactory(
IGalMemory Memory,
long Position,
long PositionB,
bool IsDualVp,
GalShaderType Type) GalShaderType Type)
{ {
if (Stages.TryGetValue(Key, out List<OGLShaderStage> Cache))
{
OGLShaderStage CachedStage = Cache.Find((OGLShaderStage Stage) => Stage.EqualsBinary(BinaryA, BinaryB));
if (CachedStage != null)
{
TopStages[Key] = CachedStage;
return;
}
}
else
{
Cache = new List<OGLShaderStage>();
Stages.Add(Key, Cache);
}
GlslProgram Program; GlslProgram Program;
GlslDecompiler Decompiler = new GlslDecompiler(); GlslDecompiler Decompiler = new GlslDecompiler();
if (IsDualVp) if (BinaryA != null)
{ {
ShaderDumper.Dump(Memory, Position, Type, "a"); ShaderHelper.Dump(BinaryA, Type, "a");
ShaderDumper.Dump(Memory, PositionB, Type, "b"); ShaderHelper.Dump(BinaryB, Type, "b");
Program = Decompiler.Decompile( Program = Decompiler.Decompile(BinaryA, BinaryB, Type);
Memory,
Position,
PositionB,
Type);
} }
else else
{ {
ShaderDumper.Dump(Memory, Position, Type); ShaderHelper.Dump(BinaryB, Type);
Program = Decompiler.Decompile(Memory, Position, Type); Program = Decompiler.Decompile(BinaryB, Type);
} }
return new OGLShaderStage( OGLShaderStage NewStage = new OGLShaderStage(
Type, Type,
BinaryA,
BinaryB,
Program.Code, Program.Code,
Program.Uniforms, Program.Uniforms,
Program.Textures); Program.Textures);
Cache.Add(NewStage);
TopStages[Key] = NewStage;
} }
public IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long Key) public IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long Key)
{ {
if (Stages.TryGetValue(Key, out OGLShaderStage Stage)) if (TopStages.TryGetValue(Key, out OGLShaderStage Stage))
{ {
return Stage.ConstBufferUsage; return Stage.ConstBufferUsage;
} }
@ -92,7 +105,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key) public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key)
{ {
if (Stages.TryGetValue(Key, out OGLShaderStage Stage)) if (TopStages.TryGetValue(Key, out OGLShaderStage Stage))
{ {
return Stage.TextureUsage; return Stage.TextureUsage;
} }
@ -121,7 +134,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void Bind(long Key) public void Bind(long Key)
{ {
if (Stages.TryGetValue(Key, out OGLShaderStage Stage)) if (TopStages.TryGetValue(Key, out OGLShaderStage Stage))
{ {
Bind(Stage); Bind(Stage);
} }

View file

@ -1,6 +1,7 @@
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.Gal.OpenGL namespace Ryujinx.Graphics.Gal.OpenGL
{ {
@ -17,8 +18,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{ {
public int Handle { get; private set; } public int Handle { get; private set; }
public bool IsCompiled { get; private set; }
public GalShaderType Type { get; private set; } public GalShaderType Type { get; private set; }
public string Code { get; private set; } public string Code { get; private set; }
@ -26,13 +25,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public IEnumerable<ShaderDeclInfo> ConstBufferUsage { get; private set; } public IEnumerable<ShaderDeclInfo> ConstBufferUsage { get; private set; }
public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; } public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; }
private byte[] BinaryA;
private byte[] BinaryB;
public OGLShaderStage( public OGLShaderStage(
GalShaderType Type, GalShaderType Type,
byte[] BinaryA,
byte[] BinaryB,
string Code, string Code,
IEnumerable<ShaderDeclInfo> ConstBufferUsage, IEnumerable<ShaderDeclInfo> ConstBufferUsage,
IEnumerable<ShaderDeclInfo> TextureUsage) IEnumerable<ShaderDeclInfo> TextureUsage)
{ {
this.Type = Type; this.Type = Type;
this.BinaryA = BinaryA;
this.BinaryB = BinaryB;
this.Code = Code; this.Code = Code;
this.ConstBufferUsage = ConstBufferUsage; this.ConstBufferUsage = ConstBufferUsage;
this.TextureUsage = TextureUsage; this.TextureUsage = TextureUsage;
@ -63,6 +69,21 @@ namespace Ryujinx.Graphics.Gal.OpenGL
} }
} }
public bool EqualsBinary(byte[] BinaryA, byte[] BinaryB)
{
if (!BinaryB.SequenceEqual(this.BinaryB))
{
return false;
}
if (BinaryA != null)
{
return BinaryA.SequenceEqual(this.BinaryA);
}
return true;
}
public static void CompileAndCheck(int Handle, string Code) public static void CompileAndCheck(int Handle, string Code)
{ {
GL.ShaderSource(Handle, Code); GL.ShaderSource(Handle, Code);

View file

@ -109,16 +109,15 @@ namespace Ryujinx.Graphics.Gal.Shader
} }
public GlslProgram Decompile( public GlslProgram Decompile(
IGalMemory Memory, byte[] BinaryA,
long VpAPosition, byte[] BinaryB,
long VpBPosition,
GalShaderType ShaderType) GalShaderType ShaderType)
{ {
Header = new ShaderHeader(Memory, VpAPosition); Header = new ShaderHeader(BinaryA);
HeaderB = new ShaderHeader(Memory, VpBPosition); HeaderB = new ShaderHeader(BinaryB);
Blocks = ShaderDecoder.Decode(Memory, VpAPosition); Blocks = ShaderDecoder.Decode(BinaryA);
BlocksB = ShaderDecoder.Decode(Memory, VpBPosition); BlocksB = ShaderDecoder.Decode(BinaryB);
GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType, Header); GlslDecl DeclVpA = new GlslDecl(Blocks, ShaderType, Header);
GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType, HeaderB); GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType, HeaderB);
@ -128,12 +127,12 @@ namespace Ryujinx.Graphics.Gal.Shader
return Decompile(); return Decompile();
} }
public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType) public GlslProgram Decompile(byte[] Binary, GalShaderType ShaderType)
{ {
Header = new ShaderHeader(Memory, Position); Header = new ShaderHeader(Binary);
HeaderB = null; HeaderB = null;
Blocks = ShaderDecoder.Decode(Memory, Position); Blocks = ShaderDecoder.Decode(Binary);
BlocksB = null; BlocksB = null;
Decl = new GlslDecl(Blocks, ShaderType, Header); Decl = new GlslDecl(Blocks, ShaderType, Header);

View file

@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gal.Shader
private const bool AddDbgComments = true; private const bool AddDbgComments = true;
public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start) public static ShaderIrBlock[] Decode(byte[] Binary)
{ {
Dictionary<long, ShaderIrBlock> Visited = new Dictionary<long, ShaderIrBlock>(); Dictionary<long, ShaderIrBlock> Visited = new Dictionary<long, ShaderIrBlock>();
Dictionary<long, ShaderIrBlock> VisitedEnd = new Dictionary<long, ShaderIrBlock>(); Dictionary<long, ShaderIrBlock> VisitedEnd = new Dictionary<long, ShaderIrBlock>();
@ -34,13 +34,13 @@ namespace Ryujinx.Graphics.Gal.Shader
return Output; return Output;
} }
ShaderIrBlock Entry = Enqueue(Start + HeaderSize); ShaderIrBlock Entry = Enqueue(HeaderSize);
while (Blocks.Count > 0) while (Blocks.Count > 0)
{ {
ShaderIrBlock Current = Blocks.Dequeue(); ShaderIrBlock Current = Blocks.Dequeue();
FillBlock(Memory, Current, Start + HeaderSize); FillBlock(Binary, Current, HeaderSize);
//Set child blocks. "Branch" is the block the branch instruction //Set child blocks. "Branch" is the block the branch instruction
//points to (when taken), "Next" is the block at the next address, //points to (when taken), "Next" is the block at the next address,
@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.Gal.Shader
return Graph; return Graph;
} }
private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block, long Beginning) private static void FillBlock(byte[] Binary, ShaderIrBlock Block, long Beginning)
{ {
long Position = Block.Position; long Position = Block.Position;
@ -138,13 +138,10 @@ namespace Ryujinx.Graphics.Gal.Shader
continue; continue;
} }
uint Word0 = (uint)Memory.ReadInt32(Position + 0); long OpCode = ReadQWord(Binary, Position);
uint Word1 = (uint)Memory.ReadInt32(Position + 4);
Position += 8; Position += 8;
long OpCode = Word0 | (long)Word1 << 32;
ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode); ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode);
if (AddDbgComments) if (AddDbgComments)
@ -202,5 +199,18 @@ namespace Ryujinx.Graphics.Gal.Shader
return Op.Inst != ShaderIrInst.Exit && return Op.Inst != ShaderIrInst.Exit &&
Op.Inst != ShaderIrInst.Bra; Op.Inst != ShaderIrInst.Bra;
} }
private static unsafe long ReadQWord(byte[] Bytes, long Position)
{
if (Position >= Bytes.Length)
{
throw new System.InvalidOperationException();
}
fixed (byte* Pointer = Bytes)
{
return *((long*)(Pointer + Position));
}
}
} }
} }

View file

@ -59,13 +59,13 @@ namespace Ryujinx.Graphics.Gal.Shader
public bool OmapSampleMask { get; private set; } public bool OmapSampleMask { get; private set; }
public bool OmapDepth { get; private set; } public bool OmapDepth { get; private set; }
public ShaderHeader(IGalMemory Memory, long Position) public unsafe ShaderHeader(byte[] Binary)
{ {
uint CommonWord0 = (uint)Memory.ReadInt32(Position + 0); uint CommonWord0 = ReadWord(Binary, 0);
uint CommonWord1 = (uint)Memory.ReadInt32(Position + 4); uint CommonWord1 = ReadWord(Binary, 4);
uint CommonWord2 = (uint)Memory.ReadInt32(Position + 8); uint CommonWord2 = ReadWord(Binary, 8);
uint CommonWord3 = (uint)Memory.ReadInt32(Position + 12); uint CommonWord3 = ReadWord(Binary, 12);
uint CommonWord4 = (uint)Memory.ReadInt32(Position + 16); uint CommonWord4 = ReadWord(Binary, 16);
SphType = ReadBits(CommonWord0, 0, 5); SphType = ReadBits(CommonWord0, 0, 5);
Version = ReadBits(CommonWord0, 5, 5); Version = ReadBits(CommonWord0, 5, 5);
@ -92,8 +92,8 @@ namespace Ryujinx.Graphics.Gal.Shader
StoreReqEnd = ReadBits(CommonWord4, 24, 8); StoreReqEnd = ReadBits(CommonWord4, 24, 8);
//Type 2 (fragment?) reading //Type 2 (fragment?) reading
uint Type2OmapTarget = (uint)Memory.ReadInt32(Position + 72); uint Type2OmapTarget = ReadWord(Binary, 72);
uint Type2Omap = (uint)Memory.ReadInt32(Position + 76); uint Type2Omap = ReadWord(Binary, 76);
OmapTargets = new OmapTarget[8]; OmapTargets = new OmapTarget[8];
@ -136,6 +136,14 @@ namespace Ryujinx.Graphics.Gal.Shader
} }
} }
private static unsafe uint ReadWord(byte[] Bytes, long Position)
{
fixed (byte* Pointer = Bytes)
{
return *((uint*)(Pointer + Position));
}
}
private static int ReadBits(uint Word, int Offset, int BitWidth) private static int ReadBits(uint Word, int Offset, int BitWidth)
{ {
uint Mask = (1u << BitWidth) - 1u; uint Mask = (1u << BitWidth) - 1u;

View file

@ -3,13 +3,13 @@ using System.IO;
namespace Ryujinx.Graphics.Gal namespace Ryujinx.Graphics.Gal
{ {
static class ShaderDumper public static class ShaderHelper
{ {
private static string RuntimeDir; private static string RuntimeDir;
private static int DumpIndex = 1; private static int DumpIndex = 1;
public static void Dump(IGalMemory Memory, long Position, GalShaderType Type, string ExtSuffix = "") public static void Dump(byte[] Binary, GalShaderType Type, string ExtSuffix = "")
{ {
if (string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath)) if (string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath))
{ {
@ -23,46 +23,22 @@ namespace Ryujinx.Graphics.Gal
DumpIndex++; DumpIndex++;
using (FileStream FullFile = File.Create(FullPath)) File.WriteAllBytes(FullPath, Binary);
using (FileStream CodeFile = File.Create(CodePath)) using (FileStream CodeFile = File.Create(CodePath))
using (BinaryWriter FullWriter = new BinaryWriter(FullFile)) using (BinaryWriter Writer = new BinaryWriter(CodeFile))
using (BinaryWriter CodeWriter = new BinaryWriter(CodeFile))
{ {
for (long i = 0; i < 0x50; i += 4) long Offset;
for (Offset = 0; Offset + 0x50 < Binary.LongLength; Offset++)
{ {
FullWriter.Write(Memory.ReadInt32(Position + i)); Writer.Write(Binary[Offset + 0x50]);
}
long Offset = 0;
ulong Instruction = 0;
//Dump until a NOP instruction is found
while ((Instruction >> 52 & 0xfff8) != 0x50b0)
{
uint Word0 = (uint)Memory.ReadInt32(Position + 0x50 + Offset + 0);
uint Word1 = (uint)Memory.ReadInt32(Position + 0x50 + Offset + 4);
Instruction = Word0 | (ulong)Word1 << 32;
//Zero instructions (other kind of NOP) stop immediatly,
//this is to avoid two rows of zeroes
if (Instruction == 0)
{
break;
}
FullWriter.Write(Instruction);
CodeWriter.Write(Instruction);
Offset += 8;
} }
//Align to meet nvdisasm requeriments //Align to meet nvdisasm requeriments
while (Offset % 0x20 != 0) while (Offset % 0x20 != 0)
{ {
FullWriter.Write(0); Writer.Write(0);
CodeWriter.Write(0);
Offset += 4; Offset += 4;
} }

View file

@ -29,6 +29,8 @@ namespace Ryujinx.HLE.Gpu.Engines
private int CurrentInstance = 0; private int CurrentInstance = 0;
private Dictionary<long, long> ShadersSize;
public NvGpuEngine3d(NvGpu Gpu) public NvGpuEngine3d(NvGpu Gpu)
{ {
this.Gpu = Gpu; this.Gpu = Gpu;
@ -68,6 +70,8 @@ namespace Ryujinx.HLE.Gpu.Engines
{ {
UploadedKeys[i] = new List<long>(); UploadedKeys[i] = new List<long>();
} }
ShadersSize = new Dictionary<long, long>();
} }
public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry) public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
@ -270,7 +274,14 @@ namespace Ryujinx.HLE.Gpu.Engines
Keys[(int)GalShaderType.Vertex] = VpBPos; Keys[(int)GalShaderType.Vertex] = VpBPos;
Gpu.Renderer.Shader.Create(Vmm, VpAPos, VpBPos, GalShaderType.Vertex); if (IsShaderModified(Vmm, VpAPos) || IsShaderModified(Vmm, VpBPos))
{
byte[] BinaryA = ReadShaderBinary(Vmm, VpAPos);
byte[] BinaryB = ReadShaderBinary(Vmm, VpBPos);
Gpu.Renderer.Shader.Create(VpBPos, BinaryA, BinaryB, GalShaderType.Vertex);
}
Gpu.Renderer.Shader.Bind(VpBPos); Gpu.Renderer.Shader.Bind(VpBPos);
Index = 2; Index = 2;
@ -297,7 +308,13 @@ namespace Ryujinx.HLE.Gpu.Engines
Keys[(int)Type] = Key; Keys[(int)Type] = Key;
Gpu.Renderer.Shader.Create(Vmm, Key, Type); if (IsShaderModified(Vmm, Key))
{
byte[] Binary = ReadShaderBinary(Vmm, Key);
Gpu.Renderer.Shader.Create(Key, null, Binary, Type);
}
Gpu.Renderer.Shader.Bind(Key); Gpu.Renderer.Shader.Bind(Key);
} }
@ -870,5 +887,53 @@ namespace Ryujinx.HLE.Gpu.Engines
return Vmm.IsRegionModified(Key, Size, Type); return Vmm.IsRegionModified(Key, Size, Type);
} }
private bool IsShaderModified(NvGpuVmm Vmm, long Key)
{
long Address = Vmm.GetPhysicalAddress(Key);
if (ShadersSize.TryGetValue(Address, out long Size))
{
if (!QueryKeyUpload(Vmm, Address, Size, NvGpuBufferType.Shader))
{
return false;
}
}
return true;
}
private byte[] ReadShaderBinary(NvGpuVmm Vmm, long Key)
{
long Size = GetShaderSize(Vmm, Key);
long Address = Vmm.GetPhysicalAddress(Key);
ShadersSize[Address] = Size;
return Vmm.ReadBytes(Key, Size);
}
private static long GetShaderSize(NvGpuVmm Vmm, long Position)
{
const int NopInst = 0x50b0;
long Offset = 0x50;
ulong OpCode = 0;
do
{
uint Word0 = (uint)Vmm.ReadInt32(Position + Offset + 0);
uint Word1 = (uint)Vmm.ReadInt32(Position + Offset + 4);
OpCode = Word0 | (ulong)Word1 << 32;
Offset += 8;
}
while ((OpCode >> 52 & 0xfff8) != NopInst && OpCode != 0);
return Offset - 8;
}
} }
} }

View file

@ -2,6 +2,7 @@ namespace Ryujinx.HLE.Gpu.Memory
{ {
enum NvGpuBufferType enum NvGpuBufferType
{ {
Shader,
Index, Index,
Vertex, Vertex,
Texture, Texture,

View file

@ -24,14 +24,11 @@ namespace Ryujinx.ShaderTools
case "f": ShaderType = GalShaderType.Fragment; break; case "f": ShaderType = GalShaderType.Fragment; break;
} }
using (FileStream FS = new FileStream(args[1], FileMode.Open, FileAccess.Read)) byte[] Binary = File.ReadAllBytes(args[1]);
{
Memory Mem = new Memory(FS);
GlslProgram Program = Decompiler.Decompile(Mem, 0, ShaderType); GlslProgram Program = Decompiler.Decompile(Binary, ShaderType);
Console.WriteLine(Program.Code); Console.WriteLine(Program.Code);
}
} }
else else
{ {