Shader.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.Graphics.GAL; 3 using Ryujinx.Graphics.Shader; 4 using shaderc; 5 using Silk.NET.Vulkan; 6 using System; 7 using System.Runtime.InteropServices; 8 using System.Threading.Tasks; 9 10 namespace Ryujinx.Graphics.Vulkan 11 { 12 class Shader : IDisposable 13 { 14 // The shaderc.net dependency's Options constructor and dispose are not thread safe. 15 // Take this lock when using them. 16 private static readonly object _shaderOptionsLock = new(); 17 18 private static readonly IntPtr _ptrMainEntryPointName = Marshal.StringToHGlobalAnsi("main"); 19 20 private readonly Vk _api; 21 private readonly Device _device; 22 private readonly ShaderStageFlags _stage; 23 24 private bool _disposed; 25 private ShaderModule _module; 26 27 public ShaderStageFlags StageFlags => _stage; 28 29 public ProgramLinkStatus CompileStatus { private set; get; } 30 31 public readonly Task CompileTask; 32 33 public unsafe Shader(Vk api, Device device, ShaderSource shaderSource) 34 { 35 _api = api; 36 _device = device; 37 38 CompileStatus = ProgramLinkStatus.Incomplete; 39 40 _stage = shaderSource.Stage.Convert(); 41 42 CompileTask = Task.Run(() => 43 { 44 byte[] spirv = shaderSource.BinaryCode; 45 46 if (spirv == null) 47 { 48 spirv = GlslToSpirv(shaderSource.Code, shaderSource.Stage); 49 50 if (spirv == null) 51 { 52 CompileStatus = ProgramLinkStatus.Failure; 53 54 return; 55 } 56 } 57 58 fixed (byte* pCode = spirv) 59 { 60 var shaderModuleCreateInfo = new ShaderModuleCreateInfo 61 { 62 SType = StructureType.ShaderModuleCreateInfo, 63 CodeSize = (uint)spirv.Length, 64 PCode = (uint*)pCode, 65 }; 66 67 api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError(); 68 } 69 70 CompileStatus = ProgramLinkStatus.Success; 71 }); 72 } 73 74 private unsafe static byte[] GlslToSpirv(string glsl, ShaderStage stage) 75 { 76 Options options; 77 78 lock (_shaderOptionsLock) 79 { 80 options = new Options(false) 81 { 82 SourceLanguage = SourceLanguage.Glsl, 83 TargetSpirVVersion = new SpirVVersion(1, 5), 84 }; 85 } 86 87 options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2); 88 Compiler compiler = new(options); 89 var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage)); 90 91 lock (_shaderOptionsLock) 92 { 93 options.Dispose(); 94 } 95 96 if (scr.Status != Status.Success) 97 { 98 Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}"); 99 100 return null; 101 } 102 103 var spirvBytes = new Span<byte>((void*)scr.CodePointer, (int)scr.CodeLength); 104 105 byte[] code = new byte[(scr.CodeLength + 3) & ~3]; 106 107 spirvBytes.CopyTo(code.AsSpan()[..(int)scr.CodeLength]); 108 109 return code; 110 } 111 112 private static ShaderKind GetShaderCShaderStage(ShaderStage stage) 113 { 114 switch (stage) 115 { 116 case ShaderStage.Vertex: 117 return ShaderKind.GlslVertexShader; 118 case ShaderStage.Geometry: 119 return ShaderKind.GlslGeometryShader; 120 case ShaderStage.TessellationControl: 121 return ShaderKind.GlslTessControlShader; 122 case ShaderStage.TessellationEvaluation: 123 return ShaderKind.GlslTessEvaluationShader; 124 case ShaderStage.Fragment: 125 return ShaderKind.GlslFragmentShader; 126 case ShaderStage.Compute: 127 return ShaderKind.GlslComputeShader; 128 } 129 130 Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ShaderStage)} enum value: {stage}."); 131 132 return ShaderKind.GlslVertexShader; 133 } 134 135 public unsafe PipelineShaderStageCreateInfo GetInfo() 136 { 137 return new PipelineShaderStageCreateInfo 138 { 139 SType = StructureType.PipelineShaderStageCreateInfo, 140 Stage = _stage, 141 Module = _module, 142 PName = (byte*)_ptrMainEntryPointName, 143 }; 144 } 145 146 public void WaitForCompile() 147 { 148 CompileTask.Wait(); 149 } 150 151 public unsafe void Dispose() 152 { 153 if (!_disposed) 154 { 155 _api.DestroyShaderModule(_device, _module, null); 156 _disposed = true; 157 } 158 } 159 } 160 }