diff options
author | gdk <gab.dark.100@gmail.com> | 2019-11-14 15:26:40 -0300 |
---|---|---|
committer | Thog <thog@protonmail.com> | 2020-01-09 02:13:00 +0100 |
commit | 6e399061ceefb532561c4c5a5cd2681228e1231e (patch) | |
tree | 18d2248f10c975939e4f9c8af787d677c36b0250 /Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | |
parent | f0a59f345c633b757ebd2a22fca23d7dab0f9f99 (diff) |
Invalidate shaders when they are modified
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs')
-rw-r--r-- | Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs new file mode 100644 index 00000000..3fdd28b9 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -0,0 +1,350 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.State; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + class ShaderCache + { + private const int MaxProgramSize = 0x100000; + + private GpuContext _context; + + private ShaderDumper _dumper; + + private Dictionary<ulong, List<ComputeShader>> _cpPrograms; + + private Dictionary<ShaderAddresses, List<GraphicsShader>> _gpPrograms; + + public ShaderCache(GpuContext context) + { + _context = context; + + _dumper = new ShaderDumper(context); + + _cpPrograms = new Dictionary<ulong, List<ComputeShader>>(); + + _gpPrograms = new Dictionary<ShaderAddresses, List<GraphicsShader>>(); + } + + public ComputeShader GetComputeShader(ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ) + { + bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ComputeShader> list); + + if (isCached) + { + foreach (ComputeShader cachedCpShader in list) + { + if (!IsShaderDifferent(cachedCpShader, gpuVa)) + { + return cachedCpShader; + } + } + } + + CachedShader shader = TranslateComputeShader(gpuVa, localSizeX, localSizeY, localSizeZ); + + IShader hostShader = _context.Renderer.CompileShader(shader.Program); + + IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { hostShader }); + + ulong address = _context.MemoryManager.Translate(gpuVa); + + ComputeShader cpShader = new ComputeShader(hostProgram, shader); + + if (!isCached) + { + list = new List<ComputeShader>(); + + _cpPrograms.Add(gpuVa, list); + } + + list.Add(cpShader); + + return cpShader; + } + + public GraphicsShader GetGraphicsShader(ShaderAddresses addresses) + { + bool isCached = _gpPrograms.TryGetValue(addresses, out List<GraphicsShader> list); + + if (isCached) + { + foreach (GraphicsShader cachedGpShaders in list) + { + if (!IsShaderDifferent(cachedGpShaders, addresses)) + { + return cachedGpShaders; + } + } + } + + GraphicsShader gpShaders = new GraphicsShader(); + + if (addresses.VertexA != 0) + { + gpShaders.Shader[0] = TranslateGraphicsShader(addresses.Vertex, addresses.VertexA); + } + else + { + gpShaders.Shader[0] = TranslateGraphicsShader(addresses.Vertex); + } + + gpShaders.Shader[1] = TranslateGraphicsShader(addresses.TessControl); + gpShaders.Shader[2] = TranslateGraphicsShader(addresses.TessEvaluation); + gpShaders.Shader[3] = TranslateGraphicsShader(addresses.Geometry); + gpShaders.Shader[4] = TranslateGraphicsShader(addresses.Fragment); + + BackpropQualifiers(gpShaders); + + List<IShader> hostShaders = new List<IShader>(); + + for (int stage = 0; stage < gpShaders.Shader.Length; stage++) + { + ShaderProgram program = gpShaders.Shader[stage].Program; + + if (program == null) + { + continue; + } + + IShader hostShader = _context.Renderer.CompileShader(program); + + gpShaders.Shader[stage].Shader = hostShader; + + hostShaders.Add(hostShader); + } + + gpShaders.HostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray()); + + if (!isCached) + { + list = new List<GraphicsShader>(); + + _gpPrograms.Add(addresses, list); + } + + list.Add(gpShaders); + + return gpShaders; + } + + private bool IsShaderDifferent(ComputeShader cpShader, ulong gpuVa) + { + return IsShaderDifferent(cpShader.Shader, gpuVa); + } + + private bool IsShaderDifferent(GraphicsShader gpShaders, ShaderAddresses addresses) + { + for (int stage = 0; stage < gpShaders.Shader.Length; stage++) + { + CachedShader shader = gpShaders.Shader[stage]; + + if (shader.Code == null) + { + continue; + } + + ulong gpuVa = 0; + + switch (stage) + { + case 0: gpuVa = addresses.Vertex; break; + case 1: gpuVa = addresses.TessControl; break; + case 2: gpuVa = addresses.TessEvaluation; break; + case 3: gpuVa = addresses.Geometry; break; + case 4: gpuVa = addresses.Fragment; break; + } + + if (IsShaderDifferent(shader, gpuVa)) + { + return true; + } + } + + return false; + } + + private bool IsShaderDifferent(CachedShader shader, ulong gpuVa) + { + for (int offset = 0; offset < shader.Code.Length; offset += 4) + { + if (_context.MemoryAccessor.ReadInt32(gpuVa + (ulong)offset) != shader.Code[offset / 4]) + { + return true; + } + } + + return false; + } + + private CachedShader TranslateComputeShader(ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ) + { + if (gpuVa == 0) + { + return null; + } + + ShaderProgram program; + + const TranslationFlags flags = + TranslationFlags.Compute | + TranslationFlags.DebugMode | + TranslationFlags.Unspecialized; + + TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags); + + Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize); + + program = Translator.Translate(code, translationConfig); + + int[] codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray(); + + program.Replace(DefineNames.LocalSizeX, localSizeX.ToString(CultureInfo.InvariantCulture)); + program.Replace(DefineNames.LocalSizeY, localSizeY.ToString(CultureInfo.InvariantCulture)); + program.Replace(DefineNames.LocalSizeZ, localSizeZ.ToString(CultureInfo.InvariantCulture)); + + _dumper.Dump(code, compute: true, out string fullPath, out string codePath); + + if (fullPath != null && codePath != null) + { + program.Prepend("// " + codePath); + program.Prepend("// " + fullPath); + } + + return new CachedShader(program, codeCached); + } + + private CachedShader TranslateGraphicsShader(ulong gpuVa, ulong gpuVaA = 0) + { + if (gpuVa == 0) + { + return new CachedShader(null, null); + } + + ShaderProgram program; + + const TranslationFlags flags = + TranslationFlags.DebugMode | + TranslationFlags.Unspecialized; + + TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags); + + int[] codeCached = null; + + if (gpuVaA != 0) + { + Span<byte> codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize); + Span<byte> codeB = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize); + + program = Translator.Translate(codeA, codeB, translationConfig); + + // TODO: We should also check "codeA" into account. + codeCached = MemoryMarshal.Cast<byte, int>(codeB.Slice(0, program.Size)).ToArray(); + + _dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA); + _dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB); + + if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null) + { + program.Prepend("// " + codePathB); + program.Prepend("// " + fullPathB); + program.Prepend("// " + codePathA); + program.Prepend("// " + fullPathA); + } + } + else + { + Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize); + + program = Translator.Translate(code, translationConfig); + + codeCached = MemoryMarshal.Cast<byte, int>(code.Slice(0, program.Size)).ToArray(); + + _dumper.Dump(code, compute: false, out string fullPath, out string codePath); + + if (fullPath != null && codePath != null) + { + program.Prepend("// " + codePath); + program.Prepend("// " + fullPath); + } + } + + if (program.Stage == ShaderStage.Geometry) + { + PrimitiveType primitiveType = _context.Methods.PrimitiveType; + + string inPrimitive = "points"; + + switch (primitiveType) + { + case PrimitiveType.Points: + inPrimitive = "points"; + break; + case PrimitiveType.Lines: + case PrimitiveType.LineLoop: + case PrimitiveType.LineStrip: + inPrimitive = "lines"; + break; + case PrimitiveType.LinesAdjacency: + case PrimitiveType.LineStripAdjacency: + inPrimitive = "lines_adjacency"; + break; + case PrimitiveType.Triangles: + case PrimitiveType.TriangleStrip: + case PrimitiveType.TriangleFan: + inPrimitive = "triangles"; + break; + case PrimitiveType.TrianglesAdjacency: + case PrimitiveType.TriangleStripAdjacency: + inPrimitive = "triangles_adjacency"; + break; + } + + program.Replace(DefineNames.InputTopologyName, inPrimitive); + } + + ulong address = _context.MemoryManager.Translate(gpuVa); + + return new CachedShader(program, codeCached); + } + + private void BackpropQualifiers(GraphicsShader program) + { + ShaderProgram fragmentShader = program.Shader[4].Program; + + bool isFirst = true; + + for (int stage = 3; stage >= 0; stage--) + { + if (program.Shader[stage].Program == null) + { + continue; + } + + // We need to iterate backwards, since we do name replacement, + // and it would otherwise replace a subset of the longer names. + for (int attr = 31; attr >= 0; attr--) + { + string iq = fragmentShader?.Info.InterpolationQualifiers[attr].ToGlslQualifier() ?? string.Empty; + + if (isFirst && iq != string.Empty) + { + program.Shader[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq); + } + else + { + program.Shader[stage].Program.Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty); + } + } + + isFirst = false; + } + } + } +}
\ No newline at end of file |