aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs')
-rw-r--r--Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs412
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);