Program.cs
  1  using OpenTK.Graphics.OpenGL;
  2  using Ryujinx.Common.Logging;
  3  using Ryujinx.Graphics.GAL;
  4  using Ryujinx.Graphics.Shader;
  5  using Ryujinx.Graphics.Shader.Translation;
  6  using System;
  7  using System.Buffers.Binary;
  8  
  9  namespace Ryujinx.Graphics.OpenGL
 10  {
 11      class Program : IProgram
 12      {
 13          private const int MaxShaderLogLength = 2048;
 14  
 15          public int Handle { get; private set; }
 16  
 17          public bool IsLinked
 18          {
 19              get
 20              {
 21                  if (_status == ProgramLinkStatus.Incomplete)
 22                  {
 23                      CheckProgramLink(true);
 24                  }
 25  
 26                  return _status == ProgramLinkStatus.Success;
 27              }
 28          }
 29  
 30          private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
 31          private int[] _shaderHandles;
 32  
 33          public int FragmentOutputMap { get; }
 34  
 35          public Program(ShaderSource[] shaders, int fragmentOutputMap)
 36          {
 37              Handle = GL.CreateProgram();
 38  
 39              GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
 40  
 41              _shaderHandles = new int[shaders.Length];
 42              bool hasFragmentShader = false;
 43  
 44              for (int index = 0; index < shaders.Length; index++)
 45              {
 46                  ShaderSource shader = shaders[index];
 47  
 48                  if (shader.Stage == ShaderStage.Fragment)
 49                  {
 50                      hasFragmentShader = true;
 51                  }
 52  
 53                  int shaderHandle = GL.CreateShader(shader.Stage.Convert());
 54  
 55                  switch (shader.Language)
 56                  {
 57                      case TargetLanguage.Glsl:
 58                          GL.ShaderSource(shaderHandle, shader.Code);
 59                          GL.CompileShader(shaderHandle);
 60                          break;
 61                      case TargetLanguage.Spirv:
 62                          GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
 63                          GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
 64                          break;
 65                  }
 66  
 67                  GL.AttachShader(Handle, shaderHandle);
 68  
 69                  _shaderHandles[index] = shaderHandle;
 70              }
 71  
 72              GL.LinkProgram(Handle);
 73  
 74              FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
 75          }
 76  
 77          public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
 78          {
 79              Handle = GL.CreateProgram();
 80  
 81              if (code.Length >= 4)
 82              {
 83                  BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
 84  
 85                  unsafe
 86                  {
 87                      fixed (byte* ptr = code)
 88                      {
 89                          GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
 90                      }
 91                  }
 92              }
 93  
 94              FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
 95          }
 96  
 97          public void Bind()
 98          {
 99              GL.UseProgram(Handle);
100          }
101  
102          public ProgramLinkStatus CheckProgramLink(bool blocking)
103          {
104              if (!blocking && HwCapabilities.SupportsParallelShaderCompile)
105              {
106                  GL.GetProgram(Handle, (GetProgramParameterName)ArbParallelShaderCompile.CompletionStatusArb, out int completed);
107  
108                  if (completed == 0)
109                  {
110                      return ProgramLinkStatus.Incomplete;
111                  }
112              }
113  
114              GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status);
115              DeleteShaders();
116  
117              if (status == 0)
118              {
119                  _status = ProgramLinkStatus.Failure;
120  
121                  string log = GL.GetProgramInfoLog(Handle);
122  
123                  if (log.Length > MaxShaderLogLength)
124                  {
125                      log = log[..MaxShaderLogLength] + "...";
126                  }
127  
128                  Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{log}");
129              }
130              else
131              {
132                  _status = ProgramLinkStatus.Success;
133              }
134  
135              return _status;
136          }
137  
138          public byte[] GetBinary()
139          {
140              GL.GetProgram(Handle, (GetProgramParameterName)All.ProgramBinaryLength, out int size);
141  
142              byte[] data = new byte[size + 4];
143  
144              GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
145  
146              BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat);
147  
148              return data;
149          }
150  
151          private void DeleteShaders()
152          {
153              if (_shaderHandles != null)
154              {
155                  foreach (int shaderHandle in _shaderHandles)
156                  {
157                      GL.DetachShader(Handle, shaderHandle);
158                      GL.DeleteShader(shaderHandle);
159                  }
160  
161                  _shaderHandles = null;
162              }
163          }
164  
165          public void Dispose()
166          {
167              if (Handle != 0)
168              {
169                  DeleteShaders();
170                  GL.DeleteProgram(Handle);
171  
172                  Handle = 0;
173              }
174          }
175      }
176  }