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  }