Allow multithreading shaderc and vkCreateShaderModule

You'll only really see the benefit here with threaded-gal or parallel shader cache compile.

Fix shaderc multithreaded changes

Thread safety for shaderc Options constructor

Dunno how they managed to make a constructor not thread safe, but you do you. May avoid some freezes.
This commit is contained in:
riperiperi 2021-07-31 16:51:07 +01:00
parent c6fbaf844e
commit e0edaa177e
3 changed files with 148 additions and 56 deletions

View file

@ -223,7 +223,7 @@ namespace Ryujinx.Graphics.Vulkan
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
{
if (_program.LinkStatus != ProgramLinkStatus.Success)
if (!_program.IsLinked)
{
return;
}
@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan
{
// System.Console.WriteLine("draw");
if (_program.LinkStatus != ProgramLinkStatus.Success)
if (!_program.IsLinked)
{
return;
}
@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan
{
// System.Console.WriteLine("draw indexed");
if (_program.LinkStatus != ProgramLinkStatus.Success)
if (!_program.IsLinked)
{
return;
}

View file

@ -6,72 +6,95 @@ using Silk.NET.Vulkan;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx.Graphics.Vulkan
{
class Shader : IShader
{
// The shaderc.net dependency's Options constructor and dispose are not thread safe.
// Take this lock when using them.
private static object _shaderOptionsLock = new object();
private readonly Vk _api;
private readonly Device _device;
private readonly ShaderModule _module;
private readonly ShaderStageFlags _stage;
private readonly Task _compileTask;
private IntPtr _entryPointName;
private ShaderModule _module;
public ShaderStageFlags StageFlags => _stage;
public ShaderBindings Bindings { get; }
public bool Valid { get; }
public ProgramLinkStatus CompileStatus { private set; get; }
public unsafe Shader(Vk api, Device device, ShaderStage stage, ShaderBindings bindings, string glsl)
{
_api = api;
_device = device;
Bindings = bindings;
glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)");
glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)");
// System.Console.WriteLine(glsl);
Options options = new Options(false)
{
SourceLanguage = SourceLanguage.Glsl,
TargetSpirVVersion = new SpirVVersion(1, 5)
};
options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2);
Compiler compiler = new Compiler(options);
var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage));
if (scr.Status != Status.Success)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}");
return;
}
Valid = true;
var spirvBytes = new Span<byte>((void*)scr.CodePointer, (int)scr.CodeLength);
uint[] code = new uint[(scr.CodeLength + 3) / 4];
spirvBytes.CopyTo(MemoryMarshal.Cast<uint, byte>(new Span<uint>(code)).Slice(0, (int)scr.CodeLength));
fixed (uint* pCode = code)
{
var shaderModuleCreateInfo = new ShaderModuleCreateInfo()
{
SType = StructureType.ShaderModuleCreateInfo,
CodeSize = scr.CodeLength,
PCode = pCode
};
api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
}
_stage = stage.Convert();
_entryPointName = Marshal.StringToHGlobalAnsi("main");
Bindings = bindings;
_compileTask = Task.Run(() =>
{
glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)");
glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)");
// System.Console.WriteLine(glsl);
Options options;
lock (_shaderOptionsLock)
{
options = new Options(false)
{
SourceLanguage = SourceLanguage.Glsl,
TargetSpirVVersion = new SpirVVersion(1, 5)
};
}
options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2);
Compiler compiler = new Compiler(options);
var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage));
lock (_shaderOptionsLock)
{
options.Dispose();
}
if (scr.Status != Status.Success)
{
Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}");
CompileStatus = ProgramLinkStatus.Failure;
return;
}
var spirvBytes = new Span<byte>((void*)scr.CodePointer, (int)scr.CodeLength);
uint[] code = new uint[(scr.CodeLength + 3) / 4];
spirvBytes.CopyTo(MemoryMarshal.Cast<uint, byte>(new Span<uint>(code)).Slice(0, (int)scr.CodeLength));
fixed (uint* pCode = code)
{
var shaderModuleCreateInfo = new ShaderModuleCreateInfo()
{
SType = StructureType.ShaderModuleCreateInfo,
CodeSize = scr.CodeLength,
PCode = pCode
};
api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
}
CompileStatus = ProgramLinkStatus.Success;
});
}
public unsafe Shader(Vk api, Device device, ShaderStage stage, ShaderBindings bindings, byte[] spirv)
@ -80,7 +103,7 @@ namespace Ryujinx.Graphics.Vulkan
_device = device;
Bindings = bindings;
Valid = true;
CompileStatus = ProgramLinkStatus.Success;
fixed (byte* pCode = spirv)
{
@ -144,6 +167,11 @@ namespace Ryujinx.Graphics.Vulkan
};
}
public void WaitForCompile()
{
_compileTask.Wait();
}
public unsafe void Dispose()
{
if (_entryPointName != IntPtr.Zero)

View file

@ -19,13 +19,27 @@ namespace Ryujinx.Graphics.Vulkan
public int[][][] Bindings { get; }
public ProgramLinkStatus LinkStatus { get; }
public ProgramLinkStatus LinkStatus { private set; get; }
public bool IsLinked
{
get
{
if (LinkStatus == ProgramLinkStatus.Incomplete)
{
CheckProgramLink(true);
}
return LinkStatus == ProgramLinkStatus.Success;
}
}
private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
private Auto<DisposablePipeline> _computePipeline;
private VulkanGraphicsDevice _gd;
private Device _device;
private bool _initialized;
public ShaderCollection(VulkanGraphicsDevice gd, Device device, IShader[] shaders)
{
@ -39,17 +53,13 @@ namespace Ryujinx.Graphics.Vulkan
_infos = new PipelineShaderStageCreateInfo[shaders.Length];
LinkStatus = ProgramLinkStatus.Success;
LinkStatus = ProgramLinkStatus.Incomplete;
uint stages = 0;
for (int i = 0; i < shaders.Length; i++)
{
var shader = (Shader)shaders[i];
if (!shader.Valid)
{
LinkStatus = ProgramLinkStatus.Failure;
}
stages |= 1u << shader.StageFlags switch
{
@ -61,8 +71,6 @@ namespace Ryujinx.Graphics.Vulkan
};
internalShaders[i] = shader;
_infos[i] = internalShaders[i].GetInfo();
}
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, stages);
@ -95,13 +103,69 @@ namespace Ryujinx.Graphics.Vulkan
};
}
private void EnsureShadersReady()
{
if (!_initialized)
{
CheckProgramLink(true);
ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
for (int i = 0; i < _shaders.Length; i++)
{
var shader = (Shader)_shaders[i];
if (shader.CompileStatus != ProgramLinkStatus.Success)
{
resultStatus = ProgramLinkStatus.Failure;
}
_infos[i] = shader.GetInfo();
}
LinkStatus = resultStatus;
_initialized = true;
}
}
public PipelineShaderStageCreateInfo[] GetInfos()
{
EnsureShadersReady();
return _infos;
}
public ProgramLinkStatus CheckProgramLink(bool blocking)
{
if (LinkStatus == ProgramLinkStatus.Incomplete)
{
ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
foreach (Shader shader in _shaders)
{
if (shader.CompileStatus == ProgramLinkStatus.Incomplete)
{
if (blocking)
{
// Wait for this shader to finish compiling.
shader.WaitForCompile();
if (shader.CompileStatus != ProgramLinkStatus.Success)
{
resultStatus = ProgramLinkStatus.Failure;
}
}
else
{
return ProgramLinkStatus.Incomplete;
}
}
}
return resultStatus;
}
return LinkStatus;
}