From e0edaa177ec73ac6aa417a27959370d0db9c2df6 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Sat, 31 Jul 2021 16:51:07 +0100 Subject: [PATCH] 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. --- Ryujinx.Graphics.Vulkan/PipelineBase.cs | 6 +- Ryujinx.Graphics.Vulkan/Shader.cs | 118 ++++++++++++-------- Ryujinx.Graphics.Vulkan/ShaderCollection.cs | 80 +++++++++++-- 3 files changed, 148 insertions(+), 56 deletions(-) diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs index c14daed31..c02b13517 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -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; } diff --git a/Ryujinx.Graphics.Vulkan/Shader.cs b/Ryujinx.Graphics.Vulkan/Shader.cs index 93264b987..9ec4bac7f 100644 --- a/Ryujinx.Graphics.Vulkan/Shader.cs +++ b/Ryujinx.Graphics.Vulkan/Shader.cs @@ -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((void*)scr.CodePointer, (int)scr.CodeLength); - - uint[] code = new uint[(scr.CodeLength + 3) / 4]; - - spirvBytes.CopyTo(MemoryMarshal.Cast(new Span(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((void*)scr.CodePointer, (int)scr.CodeLength); + + uint[] code = new uint[(scr.CodeLength + 3) / 4]; + + spirvBytes.CopyTo(MemoryMarshal.Cast(new Span(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) diff --git a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 7af1c2c26..05beee70f 100644 --- a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -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> _graphicsPipelineCache; private Auto _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; }