Implement parallel host shader cache compilation.

This commit is contained in:
riperiperi 2021-03-29 21:52:25 +01:00
parent bb43219f1d
commit ddf4b92a9c
7 changed files with 452 additions and 232 deletions

View file

@ -4,6 +4,8 @@ namespace Ryujinx.Graphics.GAL
{
public interface IProgram : IDisposable
{
ProgramLinkStatus CheckProgramLink(bool blocking);
byte[] GetBinary();
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.Graphics.GAL
{
public enum ProgramLinkStatus
{
Incomplete,
Success,
Failure
}
}

View file

@ -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,8 +103,21 @@ namespace Ryujinx.Graphics.Gpu.Shader
progressReportThread.Start(progressReportEvent);
}
for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
// Make sure these are initialized before doing compilation.
Capabilities caps = _context.Capabilities;
int maxTaskCount = Math.Min(Environment.ProcessorCount, 8);
int programIndex = 0;
List<ShaderCompileTask> activeTasks = new List<ShaderCompileTask>();
// 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.
while (programIndex < guestProgramList.Length || activeTasks.Count > 0)
{
if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length)
{
// Begin a new shader compilation.
Hash128 key = guestProgramList[programIndex];
byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key);
@ -144,11 +158,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
}
bool isHostProgramValid = hostProgram != null;
ShaderCompileTask task = new ShaderCompileTask();
activeTasks.Add(task);
ShaderProgram program;
ShaderProgramInfo shaderProgramInfo;
task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
{
ShaderProgram program = null;
ShaderProgramInfo shaderProgramInfo = null;
Task compileTask = Task.Run(() =>
{
// Reconstruct code holder.
if (isHostProgramValid)
{
@ -161,11 +180,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo);
}
});
task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
{
ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
// 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)
if (!isHostProgramValid)
{
Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
@ -173,6 +195,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
if (!isNewProgramValid)
{
return true;
}
// As the host program was invalidated, save the new entry in the cache.
hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
@ -189,9 +218,25 @@ namespace Ryujinx.Graphics.Gpu.Shader
_cacheManager.AddHostProgram(ref key, hostProgramBinary);
}
}
}
_cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
return true;
});
return false; // Not finished: still need to compile the host program.
}
else
{
_cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
return true;
}
});
return false; // Not finished: translating the shaders.
});
}
else
{
@ -221,12 +266,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
}
bool isHostProgramValid = hostProgram != null;
ShaderCompileTask task = new ShaderCompileTask();
activeTasks.Add(task);
// Reconstruct code holder.
for (int i = 0; i < cachedShaderEntries.Length; i++)
GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray();
task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) =>
{
GuestShaderCacheEntry entry = cachedShaderEntries[i];
Task compileTask = Task.Run(() =>
{
// Reconstruct code holder.
for (int i = 0; i < entries.Length; i++)
{
GuestShaderCacheEntry entry = entries[i];
if (entry == null)
{
@ -281,7 +333,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
shaderPrograms.Add(program);
}
});
task.OnTask(compileTask, (bool _, ShaderCompileTask task) =>
{
// If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
if (!isHostProgramValid)
{
@ -308,6 +363,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) =>
{
if (!isNewProgramValid)
{
return true;
}
// As the host program was invalidated, save the new entry in the cache.
hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
@ -324,12 +386,44 @@ namespace Ryujinx.Graphics.Gpu.Shader
_cacheManager.AddHostProgram(ref key, hostProgramBinary);
}
}
}
_gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
return true;
});
return false; // Not finished: still need to compile the host program.
}
else
{
_gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
return true;
}
});
return false; // Not finished: translating the shaders.
});
}
_shaderCount = programIndex + 1;
_shaderCount = ++programIndex;
}
// Process the queue.
for (int i = 0; i < activeTasks.Count; i++)
{
ShaderCompileTask task = activeTasks[i];
if (task.IsDone())
{
activeTasks.RemoveAt(i--);
}
}
if (activeTasks.Count == maxTaskCount)
{
Thread.Sleep(1);
}
}
if (!isReadOnly)
@ -458,6 +552,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 +694,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);

View file

@ -0,0 +1,81 @@
using Ryujinx.Graphics.GAL;
using System;
using System.Threading.Tasks;
namespace Ryujinx.Graphics.Gpu.Shader
{
delegate bool ShaderCompileTaskCallback(bool success, ShaderCompileTask task);
/// <summary>
/// A class that represents a shader compilation.
/// </summary>
class ShaderCompileTask
{
private bool _compiling;
private Task _programsTask;
private IProgram _program;
private ShaderCompileTaskCallback _action;
/// <summary>
/// Check the completion status of the shader compile task, and run callbacks on step completion.
/// Calling this periodically is required to progress through steps of the compilation.
/// </summary>
/// <returns>True if the task is complete, false if it is in progress</returns>
public bool IsDone()
{
if (_compiling)
{
ProgramLinkStatus status = _program.CheckProgramLink(false);
if (status != ProgramLinkStatus.Incomplete)
{
return _action(status == ProgramLinkStatus.Success, this);
}
}
else
{
// Waiting on the task.
if (_programsTask.IsCompleted)
{
return _action(true, this);
}
}
return false;
}
/// <summary>
/// Run a callback when the specified task has completed.
/// </summary>
/// <param name="task">The task object that needs to complete</param>
/// <param name="action">The action to perform when it is complete</param>
public void OnTask(Task task, ShaderCompileTaskCallback action)
{
_compiling = false;
_programsTask = task;
_action = action;
}
/// <summary>
/// Run a callback when the specified program has been linked.
/// </summary>
/// <param name="task">The program that needs to be linked</param>
/// <param name="action">The action to perform when linking is complete</param>
public void OnCompiled(IProgram program, ShaderCompileTaskCallback action)
{
_compiling = true;
_program = program;
_action = action;
if (program == null)
{
action(false, this);
}
}
}
}

View file

@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.OpenGL
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
private static readonly Lazy<int> _storageBufferOffsetAlignment = new Lazy<int>(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
@ -34,6 +35,7 @@ namespace Ryujinx.Graphics.OpenGL
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;
public static bool RequiresSyncFlush => _gpuVendor.Value == GpuVendor.Amd || _gpuVendor.Value == GpuVendor.IntelWindows || _gpuVendor.Value == GpuVendor.IntelUnix;

View file

@ -13,11 +13,26 @@ namespace Ryujinx.Graphics.OpenGL
{
public int Handle { get; private set; }
public int FragmentIsBgraUniform { get; }
public int FragmentRenderScaleUniform { get; }
public int ComputeRenderScaleUniform { get; }
public int FragmentIsBgraUniform { get; private set; }
public int FragmentRenderScaleUniform { get; private set; }
public int ComputeRenderScaleUniform { get; private set; }
public bool IsLinked { get; private set; }
public bool IsLinked
{
get
{
if (_status == ProgramLinkStatus.Incomplete)
{
CheckProgramLink(true);
}
return _status == ProgramLinkStatus.Success;
}
}
private bool _initialized;
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
private IShader[] _shaders;
public Program(IShader[] shaders, TransformFeedbackDescriptor[] transformFeedbackDescriptors)
{
@ -82,18 +97,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.LinkProgram(Handle);
for (int index = 0; index < shaders.Length; index++)
{
int shaderHandle = ((Shader)shaders[index]).Handle;
GL.DetachShader(Handle, shaderHandle);
}
CheckProgramLink();
FragmentIsBgraUniform = GL.GetUniformLocation(Handle, "is_bgra");
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
_shaders = shaders;
}
public Program(ReadOnlySpan<byte> code)
@ -109,32 +113,60 @@ namespace Ryujinx.Graphics.OpenGL
GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
}
}
CheckProgramLink();
FragmentIsBgraUniform = GL.GetUniformLocation(Handle, "is_bgra");
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
}
public void Bind()
{
if (!_initialized)
{
FragmentIsBgraUniform = GL.GetUniformLocation(Handle, "is_bgra");
FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
_initialized = true;
}
GL.UseProgram(Handle);
}
private void CheckProgramLink()
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
if (!blocking && HwCapabilities.SupportsParallelShaderCompile)
{
GL.GetProgram(Handle, (GetProgramParameterName)ArbParallelShaderCompile.CompletionStatusArb, out int completed);
if (completed == 0)
{
return ProgramLinkStatus.Incomplete;
}
}
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
if (_shaders != null)
{
for (int index = 0; index < _shaders.Length; index++)
{
int shaderHandle = ((Shader)_shaders[index]).Handle;
GL.DetachShader(Handle, shaderHandle);
}
_shaders = null;
}
if (status == 0)
{
// Use GL.GetProgramInfoLog(Handle), it may be too long to print on the log.
_status = ProgramLinkStatus.Failure;
Logger.Debug?.Print(LogClass.Gpu, "Shader linking failed.");
}
else
{
IsLinked = true;
_status = ProgramLinkStatus.Success;
}
return _status;
}
public byte[] GetBinary()

View file

@ -130,6 +130,11 @@ namespace Ryujinx.Graphics.OpenGL
PrintGpuInformation();
if (HwCapabilities.SupportsParallelShaderCompile)
{
GL.Arb.MaxShaderCompilerThreads(Math.Min(Environment.ProcessorCount, 8));
}
_counters.Initialize();
}
@ -177,16 +182,7 @@ namespace Ryujinx.Graphics.OpenGL
public IProgram LoadProgramBinary(byte[] programBinary)
{
Program program = new Program(programBinary);
if (program.IsLinked)
{
return program;
}
program.Dispose();
return null;
return new Program(programBinary);
}
public void CreateSync(ulong id)