diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs')
| -rw-r--r-- | Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 412 |
1 files changed, 253 insertions, 159 deletions
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 164960d0..f6dcf052 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using System.Threading.Tasks; namespace Ryujinx.Graphics.Gpu.Shader { @@ -102,234 +103,323 @@ namespace Ryujinx.Graphics.Gpu.Shader progressReportThread.Start(progressReportEvent); } - for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++) - { - Hash128 key = guestProgramList[programIndex]; + // Make sure these are initialized before doing compilation. + Capabilities caps = _context.Capabilities; - byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key); - bool hasHostCache = hostProgramBinary != null; + int maxTaskCount = Math.Min(Environment.ProcessorCount, 8); + int programIndex = 0; + List<ShaderCompileTask> activeTasks = new List<ShaderCompileTask>(); - IProgram hostProgram = null; + AutoResetEvent taskDoneEvent = new AutoResetEvent(false); - // If the program sources aren't in the cache, compile from saved guest program. - byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key); + // This thread dispatches tasks to do shader translation, and creates programs that OpenGL will link in the background. + // The program link status is checked in a non-blocking manner so that multiple shaders can be compiled at once. - if (guestProgram == null) + while (programIndex < guestProgramList.Length || activeTasks.Count > 0) + { + if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length) { - Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); - - // Should not happen, but if someone messed with the cache it's better to catch it. - invalidEntries?.Add(key); + // Begin a new shader compilation. + Hash128 key = guestProgramList[programIndex]; - continue; - } + byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key); + bool hasHostCache = hostProgramBinary != null; - ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram; + IProgram hostProgram = null; - ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); + // If the program sources aren't in the cache, compile from saved guest program. + byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key); - if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) - { - Debug.Assert(cachedShaderEntries.Length == 1); + if (guestProgram == null) + { + Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); - GuestShaderCacheEntry entry = cachedShaderEntries[0]; + // Should not happen, but if someone messed with the cache it's better to catch it. + invalidEntries?.Add(key); - HostShaderCacheEntry[] hostShaderEntries = null; + _shaderCount = ++programIndex; - // Try loading host shader binary. - if (hasHostCache) - { - hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan); - hostProgramBinary = hostProgramBinarySpan.ToArray(); - hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); + continue; } - bool isHostProgramValid = hostProgram != null; + ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram; - ShaderProgram program; - ShaderProgramInfo shaderProgramInfo; + ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); - // Reconstruct code holder. - if (isHostProgramValid) - { - program = new ShaderProgram(entry.Header.Stage, ""); - shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); - } - else + if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) { - IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); + Debug.Assert(cachedShaderEntries.Length == 1); - program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo); - } - - ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); + GuestShaderCacheEntry entry = cachedShaderEntries[0]; - // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. - if (hostProgram == null) - { - Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); + HostShaderCacheEntry[] hostShaderEntries = null; - // Compile shader and create program as the shader program binary got invalidated. - shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code); - hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); + // Try loading host shader binary. + if (hasHostCache) + { + hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan); + hostProgramBinary = hostProgramBinarySpan.ToArray(); + hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); + } - // As the host program was invalidated, save the new entry in the cache. - hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); + ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); + activeTasks.Add(task); - if (!isReadOnly) + task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => { - if (hasHostCache) + ShaderProgram program = null; + ShaderProgramInfo shaderProgramInfo = null; + + if (isHostProgramValid) { - _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); + // Reconstruct code holder. + + program = new ShaderProgram(entry.Header.Stage, ""); + shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); + + ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); + + _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); + + return true; } else { - Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); + // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. - _cacheManager.AddHostProgram(ref key, hostProgramBinary); - } - } - } + Task compileTask = Task.Run(() => + { + IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); - _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); - } - else - { - Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); + program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo); + }); - ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length]; - List<ShaderProgram> shaderPrograms = new List<ShaderProgram>(); + task.OnTask(compileTask, (bool _, ShaderCompileTask task) => + { + ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); - TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); + Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); - TranslationFlags flags = DefaultFlags; + // Compile shader and create program as the shader program binary got invalidated. + shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code); + hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); - if (tfd != null) - { - flags |= TranslationFlags.Feedback; - } + task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => + { + // As the host program was invalidated, save the new entry in the cache. + hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); - TranslationCounts counts = new TranslationCounts(); + if (!isReadOnly) + { + if (hasHostCache) + { + _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); - HostShaderCacheEntry[] hostShaderEntries = null; + _cacheManager.AddHostProgram(ref key, hostProgramBinary); + } + } - // Try loading host shader binary. - if (hasHostCache) - { - hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan); - hostProgramBinary = hostProgramBinarySpan.ToArray(); - hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); - } + _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); - bool isHostProgramValid = hostProgram != null; + return true; + }); - // Reconstruct code holder. - for (int i = 0; i < cachedShaderEntries.Length; i++) + return false; // Not finished: still need to compile the host program. + }); + + return false; // Not finished: translating the program. + } + }); + } + else { - GuestShaderCacheEntry entry = cachedShaderEntries[i]; + Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); - if (entry == null) - { - continue; - } + ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length]; + List<ShaderProgram> shaderPrograms = new List<ShaderProgram>(); + + TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); - ShaderProgram program; + TranslationFlags flags = DefaultFlags; - if (entry.Header.SizeA != 0) + if (tfd != null) { - ShaderProgramInfo shaderProgramInfo; + flags |= TranslationFlags.Feedback; + } - if (isHostProgramValid) - { - program = new ShaderProgram(entry.Header.Stage, ""); - shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); - } - else - { - IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); + TranslationCounts counts = new TranslationCounts(); - TranslatorContext translatorContext = Translator.CreateContext(0, gpuAccessor, flags, counts); - TranslatorContext translatorContext2 = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, flags | TranslationFlags.VertexA, counts); + HostShaderCacheEntry[] hostShaderEntries = null; - program = translatorContext.Translate(out shaderProgramInfo, translatorContext2); - } + // Try loading host shader binary. + if (hasHostCache) + { + hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan<byte> hostProgramBinarySpan); + hostProgramBinary = hostProgramBinarySpan.ToArray(); + hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); + } - // NOTE: Vertex B comes first in the shader cache. - byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray(); - byte[] code2 = entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray(); + ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); + activeTasks.Add(task); - shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); - } - else - { - ShaderProgramInfo shaderProgramInfo; + GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray(); - if (isHostProgramValid) + task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => + { + Task compileTask = Task.Run(() => { - program = new ShaderProgram(entry.Header.Stage, ""); - shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); - } - else + // Reconstruct code holder. + for (int i = 0; i < entries.Length; i++) + { + GuestShaderCacheEntry entry = entries[i]; + + if (entry == null) + { + continue; + } + + ShaderProgram program; + + if (entry.Header.SizeA != 0) + { + ShaderProgramInfo shaderProgramInfo; + + if (isHostProgramValid) + { + program = new ShaderProgram(entry.Header.Stage, ""); + shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); + } + else + { + IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); + + TranslatorContext translatorContext = Translator.CreateContext(0, gpuAccessor, flags, counts); + TranslatorContext translatorContext2 = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, flags | TranslationFlags.VertexA, counts); + + program = translatorContext.Translate(out shaderProgramInfo, translatorContext2); + } + + // NOTE: Vertex B comes first in the shader cache. + byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray(); + byte[] code2 = entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray(); + + shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); + } + else + { + ShaderProgramInfo shaderProgramInfo; + + if (isHostProgramValid) + { + program = new ShaderProgram(entry.Header.Stage, ""); + shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); + } + else + { + IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); + + program = Translator.CreateContext(0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo); + } + + shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); + } + + shaderPrograms.Add(program); + } + }); + + task.OnTask(compileTask, (bool _, ShaderCompileTask task) => { - IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); + // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. + if (!isHostProgramValid) + { + Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); - program = Translator.CreateContext(0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo); - } + List<IShader> hostShaders = new List<IShader>(); - shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); - } + // Compile shaders and create program as the shader program binary got invalidated. + for (int stage = 0; stage < Constants.ShaderStages; stage++) + { + ShaderProgram program = shaders[stage]?.Program; - shaderPrograms.Add(program); - } + if (program == null) + { + continue; + } - // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. - if (!isHostProgramValid) - { - Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); + IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); - List<IShader> hostShaders = new List<IShader>(); + shaders[stage].HostShader = hostShader; - // Compile shaders and create program as the shader program binary got invalidated. - for (int stage = 0; stage < Constants.ShaderStages; stage++) - { - ShaderProgram program = shaders[stage]?.Program; + hostShaders.Add(hostShader); + } - if (program == null) - { - continue; - } + hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); - IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); + task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => + { + // As the host program was invalidated, save the new entry in the cache. + hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); - shaders[stage].HostShader = hostShader; + if (!isReadOnly) + { + if (hasHostCache) + { + _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); - hostShaders.Add(hostShader); - } + _cacheManager.AddHostProgram(ref key, hostProgramBinary); + } + } - hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); + _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); - // As the host program was invalidated, save the new entry in the cache. - hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); + return true; + }); - if (!isReadOnly) - { - if (hasHostCache) - { - _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); - } - else - { - Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); + return false; // Not finished: still need to compile the host program. + } + else + { + _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); - _cacheManager.AddHostProgram(ref key, hostProgramBinary); - } - } + return true; + } + }); + + return false; // Not finished: translating the program. + }); } - _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); + _shaderCount = ++programIndex; + } + + // Process the queue. + for (int i = 0; i < activeTasks.Count; i++) + { + ShaderCompileTask task = activeTasks[i]; + + if (task.IsDone()) + { + activeTasks.RemoveAt(i--); + } } - _shaderCount = programIndex + 1; + if (activeTasks.Count == maxTaskCount) + { + // Wait for a task to be done, or for 1ms. + // Host shader compilation cannot signal when it is done, + // so the 1ms timeout is required to poll status. + + taskDoneEvent.WaitOne(1); + } } if (!isReadOnly) @@ -458,6 +548,8 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); + hostProgram.CheckProgramLink(true); + byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); cpShader = new ShaderBundle(hostProgram, shader); @@ -598,6 +690,8 @@ namespace Ryujinx.Graphics.Gpu.Shader IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); + hostProgram.CheckProgramLink(true); + byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); gpShaders = new ShaderBundle(hostProgram, shaders); |
