/ src / Ryujinx.Graphics.Shader / CodeGen / Glsl / Declarations.cs
Declarations.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  3  using Ryujinx.Graphics.Shader.StructuredIr;
  4  using Ryujinx.Graphics.Shader.Translation;
  5  using System;
  6  using System.Collections.Generic;
  7  using System.Globalization;
  8  using System.Linq;
  9  
 10  namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
 11  {
 12      static class Declarations
 13      {
 14          public static void Declare(CodeGenContext context, StructuredProgramInfo info)
 15          {
 16              context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core");
 17              context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable");
 18  
 19              if (context.HostCapabilities.SupportsShaderBallot)
 20              {
 21                  context.AppendLine("#extension GL_ARB_shader_ballot : enable");
 22              }
 23              else
 24              {
 25                  context.AppendLine("#extension GL_KHR_shader_subgroup_basic : enable");
 26                  context.AppendLine("#extension GL_KHR_shader_subgroup_ballot : enable");
 27                  context.AppendLine("#extension GL_KHR_shader_subgroup_shuffle : enable");
 28              }
 29  
 30              context.AppendLine("#extension GL_ARB_shader_group_vote : enable");
 31              context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable");
 32              context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable");
 33  
 34              if (context.Definitions.Stage == ShaderStage.Compute)
 35              {
 36                  context.AppendLine("#extension GL_ARB_compute_shader : enable");
 37              }
 38              else if (context.Definitions.Stage == ShaderStage.Fragment)
 39              {
 40                  if (context.HostCapabilities.SupportsFragmentShaderInterlock)
 41                  {
 42                      context.AppendLine("#extension GL_ARB_fragment_shader_interlock : enable");
 43                  }
 44                  else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel)
 45                  {
 46                      context.AppendLine("#extension GL_INTEL_fragment_shader_ordering : enable");
 47                  }
 48              }
 49              else
 50              {
 51                  if (context.Definitions.Stage == ShaderStage.Vertex)
 52                  {
 53                      context.AppendLine("#extension GL_ARB_shader_draw_parameters : enable");
 54                  }
 55  
 56                  context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable");
 57              }
 58  
 59              if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
 60              {
 61                  context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
 62              }
 63  
 64              if (context.HostCapabilities.SupportsViewportMask)
 65              {
 66                  context.AppendLine("#extension GL_NV_viewport_array2 : enable");
 67              }
 68  
 69              context.AppendLine("#pragma optionNV(fastmath off)");
 70              context.AppendLine();
 71  
 72              context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;");
 73              context.AppendLine();
 74  
 75              DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values);
 76              DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values);
 77              DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false);
 78              DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true);
 79              DeclareSamplers(context, context.Properties.Textures.Values);
 80              DeclareImages(context, context.Properties.Images.Values);
 81  
 82              if (context.Definitions.Stage != ShaderStage.Compute)
 83              {
 84                  if (context.Definitions.Stage == ShaderStage.Geometry)
 85                  {
 86                      string inPrimitive = context.Definitions.InputTopology.ToGlslString();
 87  
 88                      context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;");
 89  
 90                      if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough)
 91                      {
 92                          context.AppendLine($"layout (passthrough) in gl_PerVertex");
 93                          context.EnterScope();
 94                          context.AppendLine("vec4 gl_Position;");
 95                          context.AppendLine("float gl_PointSize;");
 96                          context.AppendLine("float gl_ClipDistance[];");
 97                          context.LeaveScope(";");
 98                      }
 99                      else
100                      {
101                          string outPrimitive = context.Definitions.OutputTopology.ToGlslString();
102                          int maxOutputVertices = context.Definitions.MaxOutputVertices;
103  
104                          context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;");
105                      }
106  
107                      context.AppendLine();
108                  }
109                  else if (context.Definitions.Stage == ShaderStage.TessellationControl)
110                  {
111                      int threadsPerInputPrimitive = context.Definitions.ThreadsPerInputPrimitive;
112  
113                      context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;");
114                      context.AppendLine();
115                  }
116                  else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation)
117                  {
118                      bool tessCw = context.Definitions.TessCw;
119  
120                      if (context.TargetApi == TargetApi.Vulkan)
121                      {
122                          // We invert the front face on Vulkan backend, so we need to do that here aswell.
123                          tessCw = !tessCw;
124                      }
125  
126                      string patchType = context.Definitions.TessPatchType.ToGlsl();
127                      string spacing = context.Definitions.TessSpacing.ToGlsl();
128                      string windingOrder = tessCw ? "cw" : "ccw";
129  
130                      context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;");
131                      context.AppendLine();
132                  }
133  
134                  static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind)
135                  {
136                      return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined;
137                  }
138  
139                  static bool IsUserDefinedOutput(ShaderStage stage, IoDefinition ioDefinition)
140                  {
141                      IoVariable ioVariable = stage == ShaderStage.Fragment ? IoVariable.FragmentOutputColor : IoVariable.UserDefined;
142                      return ioDefinition.StorageKind == StorageKind.Output && ioDefinition.IoVariable == ioVariable;
143                  }
144  
145                  DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input)));
146                  DeclareOutputAttributes(context, info.IoDefinitions.Where(x => IsUserDefinedOutput(context.Definitions.Stage, x)));
147                  DeclareInputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.InputPerPatch)));
148                  DeclareOutputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.OutputPerPatch)));
149  
150                  if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
151                  {
152                      var tfOutput = context.Definitions.GetTransformFeedbackOutput(AttributeConsts.PositionX);
153                      if (tfOutput.Valid)
154                      {
155                          context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex");
156                          context.EnterScope();
157                          context.AppendLine("vec4 gl_Position;");
158                          context.LeaveScope(context.Definitions.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";");
159                      }
160                  }
161              }
162              else
163              {
164                  string localSizeX = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeX);
165                  string localSizeY = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeY);
166                  string localSizeZ = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeZ);
167  
168                  context.AppendLine(
169                      "layout (" +
170                      $"local_size_x = {localSizeX}, " +
171                      $"local_size_y = {localSizeY}, " +
172                      $"local_size_z = {localSizeZ}) in;");
173                  context.AppendLine();
174              }
175  
176              if (context.Definitions.Stage == ShaderStage.Fragment)
177              {
178                  if (context.Definitions.EarlyZForce)
179                  {
180                      context.AppendLine("layout (early_fragment_tests) in;");
181                      context.AppendLine();
182                  }
183  
184                  if (context.Definitions.OriginUpperLeft)
185                  {
186                      context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;");
187                      context.AppendLine();
188                  }
189              }
190  
191              if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
192              {
193                  AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
194              }
195  
196              if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighU32) != 0)
197              {
198                  AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl");
199              }
200  
201              if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
202              {
203                  AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl");
204              }
205          }
206  
207          public static void DeclareLocals(CodeGenContext context, StructuredFunction function)
208          {
209              foreach (AstOperand decl in function.Locals)
210              {
211                  string name = context.OperandManager.DeclareLocal(decl);
212  
213                  context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";");
214              }
215          }
216  
217          public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true)
218          {
219              if (context.HostCapabilities.ReducedPrecision)
220              {
221                  precise = false;
222              }
223  
224              return type switch
225              {
226                  AggregateType.Void => "void",
227                  AggregateType.Bool => "bool",
228                  AggregateType.FP32 => precise ? "precise float" : "float",
229                  AggregateType.FP64 => "double",
230                  AggregateType.S32 => "int",
231                  AggregateType.U32 => "uint",
232                  AggregateType.Vector2 | AggregateType.Bool => "bvec2",
233                  AggregateType.Vector2 | AggregateType.FP32 => precise ? "precise vec2" : "vec2",
234                  AggregateType.Vector2 | AggregateType.FP64 => "dvec2",
235                  AggregateType.Vector2 | AggregateType.S32 => "ivec2",
236                  AggregateType.Vector2 | AggregateType.U32 => "uvec2",
237                  AggregateType.Vector3 | AggregateType.Bool => "bvec3",
238                  AggregateType.Vector3 | AggregateType.FP32 => precise ? "precise vec3" : "vec3",
239                  AggregateType.Vector3 | AggregateType.FP64 => "dvec3",
240                  AggregateType.Vector3 | AggregateType.S32 => "ivec3",
241                  AggregateType.Vector3 | AggregateType.U32 => "uvec3",
242                  AggregateType.Vector4 | AggregateType.Bool => "bvec4",
243                  AggregateType.Vector4 | AggregateType.FP32 => precise ? "precise vec4" : "vec4",
244                  AggregateType.Vector4 | AggregateType.FP64 => "dvec4",
245                  AggregateType.Vector4 | AggregateType.S32 => "ivec4",
246                  AggregateType.Vector4 | AggregateType.U32 => "uvec4",
247                  _ => throw new ArgumentException($"Invalid variable type \"{type}\"."),
248              };
249          }
250  
251          private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
252          {
253              DeclareBuffers(context, buffers, "uniform");
254          }
255  
256          private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
257          {
258              DeclareBuffers(context, buffers, "buffer");
259          }
260  
261          private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, string declType)
262          {
263              foreach (BufferDefinition buffer in buffers)
264              {
265                  string layout = buffer.Layout switch
266                  {
267                      BufferLayout.Std140 => "std140",
268                      _ => "std430",
269                  };
270  
271                  string set = string.Empty;
272  
273                  if (context.TargetApi == TargetApi.Vulkan)
274                  {
275                      set = $"set = {buffer.Set}, ";
276                  }
277  
278                  context.AppendLine($"layout ({set}binding = {buffer.Binding}, {layout}) {declType} _{buffer.Name}");
279                  context.EnterScope();
280  
281                  foreach (StructureField field in buffer.Type.Fields)
282                  {
283                      if (field.Type.HasFlag(AggregateType.Array))
284                      {
285                          string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
286  
287                          if (field.ArrayLength > 0)
288                          {
289                              string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
290  
291                              context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
292                          }
293                          else
294                          {
295                              context.AppendLine($"{typeName} {field.Name}[];");
296                          }
297                      }
298                      else
299                      {
300                          string typeName = GetVarTypeName(context, field.Type);
301  
302                          context.AppendLine($"{typeName} {field.Name};");
303                      }
304                  }
305  
306                  context.LeaveScope($" {buffer.Name};");
307                  context.AppendLine();
308              }
309          }
310  
311          private static void DeclareMemories(CodeGenContext context, IEnumerable<MemoryDefinition> memories, bool isShared)
312          {
313              string prefix = isShared ? "shared " : string.Empty;
314  
315              foreach (MemoryDefinition memory in memories)
316              {
317                  string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array);
318  
319                  if (memory.Type.HasFlag(AggregateType.Array))
320                  {
321                      if (memory.ArrayLength > 0)
322                      {
323                          string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture);
324  
325                          context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];");
326                      }
327                      else
328                      {
329                          context.AppendLine($"{prefix}{typeName} {memory.Name}[];");
330                      }
331                  }
332                  else
333                  {
334                      context.AppendLine($"{prefix}{typeName} {memory.Name};");
335                  }
336              }
337          }
338  
339          private static void DeclareSamplers(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
340          {
341              foreach (var definition in definitions)
342              {
343                  string arrayDecl = string.Empty;
344  
345                  if (definition.ArrayLength > 1)
346                  {
347                      arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
348                  }
349                  else if (definition.ArrayLength == 0)
350                  {
351                      arrayDecl = "[]";
352                  }
353  
354                  string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType();
355  
356                  string layout = string.Empty;
357  
358                  if (context.TargetApi == TargetApi.Vulkan)
359                  {
360                      layout = $", set = {definition.Set}";
361                  }
362  
363                  context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};");
364              }
365          }
366  
367          private static void DeclareImages(CodeGenContext context, IEnumerable<TextureDefinition> definitions)
368          {
369              foreach (var definition in definitions)
370              {
371                  string arrayDecl = string.Empty;
372  
373                  if (definition.ArrayLength > 1)
374                  {
375                      arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]";
376                  }
377                  else if (definition.ArrayLength == 0)
378                  {
379                      arrayDecl = "[]";
380                  }
381  
382                  string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType());
383  
384                  if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent))
385                  {
386                      imageTypeName = "coherent " + imageTypeName;
387                  }
388  
389                  string layout = definition.Format.ToGlslFormat();
390  
391                  if (!string.IsNullOrEmpty(layout))
392                  {
393                      layout = ", " + layout;
394                  }
395  
396                  if (context.TargetApi == TargetApi.Vulkan)
397                  {
398                      layout = $", set = {definition.Set}{layout}";
399                  }
400  
401                  context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};");
402              }
403          }
404  
405          private static void DeclareInputAttributes(CodeGenContext context, IEnumerable<IoDefinition> inputs)
406          {
407              if (context.Definitions.IaIndexing)
408              {
409                  string suffix = context.Definitions.Stage == ShaderStage.Geometry ? "[]" : string.Empty;
410  
411                  context.AppendLine($"layout (location = 0) in vec4 {DefaultNames.IAttributePrefix}{suffix}[{Constants.MaxAttributes}];");
412                  context.AppendLine();
413              }
414              else
415              {
416                  foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
417                  {
418                      DeclareInputAttribute(context, ioDefinition.Location, ioDefinition.Component);
419                  }
420  
421                  if (inputs.Any())
422                  {
423                      context.AppendLine();
424                  }
425              }
426          }
427  
428          private static void DeclareInputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> inputs)
429          {
430              foreach (var ioDefinition in inputs.OrderBy(x => x.Location))
431              {
432                  DeclareInputAttributePerPatch(context, ioDefinition.Location);
433              }
434  
435              if (inputs.Any())
436              {
437                  context.AppendLine();
438              }
439          }
440  
441          private static void DeclareInputAttribute(CodeGenContext context, int location, int component)
442          {
443              string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: false) ? "[]" : string.Empty;
444              string iq = string.Empty;
445  
446              if (context.Definitions.Stage == ShaderStage.Fragment)
447              {
448                  iq = context.Definitions.ImapTypes[location].GetFirstUsedType() switch
449                  {
450                      PixelImap.Constant => "flat ",
451                      PixelImap.ScreenLinear => "noperspective ",
452                      _ => string.Empty,
453                  };
454              }
455  
456              string name = $"{DefaultNames.IAttributePrefix}{location}";
457  
458              if (context.Definitions.TransformFeedbackEnabled && context.Definitions.Stage == ShaderStage.Fragment)
459              {
460                  bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: false);
461  
462                  if (hasComponent)
463                  {
464                      char swzMask = "xyzw"[component];
465  
466                      context.AppendLine($"layout (location = {location}, component = {component}) {iq}in float {name}_{swzMask}{suffix};");
467                  }
468                  else
469                  {
470                      int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
471  
472                      string type = components switch
473                      {
474                          2 => "vec2",
475                          3 => "vec3",
476                          4 => "vec4",
477                          _ => "float",
478                      };
479  
480                      context.AppendLine($"layout (location = {location}) in {type} {name};");
481                  }
482              }
483              else
484              {
485                  bool passthrough = (context.AttributeUsage.PassthroughAttributes & (1 << location)) != 0;
486                  string pass = passthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough ? "passthrough, " : string.Empty;
487                  string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(location, isOutput: false), false);
488  
489                  context.AppendLine($"layout ({pass}location = {location}) {iq}in {type} {name}{suffix};");
490              }
491          }
492  
493          private static void DeclareInputAttributePerPatch(CodeGenContext context, int patchLocation)
494          {
495              int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
496              string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
497  
498              context.AppendLine($"layout (location = {location}) patch in vec4 {name};");
499          }
500  
501          private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable<IoDefinition> outputs)
502          {
503              if (context.Definitions.OaIndexing)
504              {
505                  context.AppendLine($"layout (location = 0) out vec4 {DefaultNames.OAttributePrefix}[{Constants.MaxAttributes}];");
506                  context.AppendLine();
507              }
508              else
509              {
510                  outputs = outputs.OrderBy(x => x.Location);
511  
512                  if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend)
513                  {
514                      IoDefinition firstOutput = outputs.ElementAtOrDefault(0);
515                      IoDefinition secondOutput = outputs.ElementAtOrDefault(1);
516  
517                      if (firstOutput.Location + 1 == secondOutput.Location)
518                      {
519                          DeclareOutputDualSourceBlendAttribute(context, firstOutput.Location);
520                          outputs = outputs.Skip(2);
521                      }
522                  }
523  
524                  foreach (var ioDefinition in outputs)
525                  {
526                      DeclareOutputAttribute(context, ioDefinition.Location, ioDefinition.Component);
527                  }
528  
529                  if (outputs.Any())
530                  {
531                      context.AppendLine();
532                  }
533              }
534          }
535  
536          private static void DeclareOutputAttribute(CodeGenContext context, int location, int component)
537          {
538              string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: true) ? "[]" : string.Empty;
539              string name = $"{DefaultNames.OAttributePrefix}{location}{suffix}";
540  
541              if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline)
542              {
543                  bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: true);
544  
545                  if (hasComponent)
546                  {
547                      char swzMask = "xyzw"[component];
548  
549                      string xfb = string.Empty;
550  
551                      var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, component);
552                      if (tfOutput.Valid)
553                      {
554                          xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
555                      }
556  
557                      context.AppendLine($"layout (location = {location}, component = {component}{xfb}) out float {name}_{swzMask};");
558                  }
559                  else
560                  {
561                      int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0);
562  
563                      string type = components switch
564                      {
565                          2 => "vec2",
566                          3 => "vec3",
567                          4 => "vec4",
568                          _ => "float",
569                      };
570  
571                      string xfb = string.Empty;
572  
573                      var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, 0);
574                      if (tfOutput.Valid)
575                      {
576                          xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}";
577                      }
578  
579                      context.AppendLine($"layout (location = {location}{xfb}) out {type} {name};");
580                  }
581              }
582              else
583              {
584                  string type = context.Definitions.Stage != ShaderStage.Fragment ? "vec4" :
585                      GetVarTypeName(context, context.Definitions.GetFragmentOutputColorType(location), false);
586  
587                  if (context.HostCapabilities.ReducedPrecision && context.Definitions.Stage == ShaderStage.Vertex && location == 0)
588                  {
589                      context.AppendLine($"layout (location = {location}) invariant out {type} {name};");
590                  }
591                  else
592                  {
593                      context.AppendLine($"layout (location = {location}) out {type} {name};");
594                  }
595              }
596          }
597  
598          private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int location)
599          {
600              string name = $"{DefaultNames.OAttributePrefix}{location}";
601              string name2 = $"{DefaultNames.OAttributePrefix}{(location + 1)}";
602  
603              context.AppendLine($"layout (location = {location}, index = 0) out vec4 {name};");
604              context.AppendLine($"layout (location = {location}, index = 1) out vec4 {name2};");
605          }
606  
607          private static void DeclareOutputAttributesPerPatch(CodeGenContext context, IEnumerable<IoDefinition> outputs)
608          {
609              foreach (var ioDefinition in outputs)
610              {
611                  DeclareOutputAttributePerPatch(context, ioDefinition.Location);
612              }
613  
614              if (outputs.Any())
615              {
616                  context.AppendLine();
617              }
618          }
619  
620          private static void DeclareOutputAttributePerPatch(CodeGenContext context, int patchLocation)
621          {
622              int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation);
623              string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}";
624  
625              context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
626          }
627  
628          private static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
629          {
630              if (isOutAttr)
631              {
632                  return stage == ShaderStage.TessellationControl;
633              }
634              else
635              {
636                  return stage == ShaderStage.TessellationControl ||
637                         stage == ShaderStage.TessellationEvaluation ||
638                         stage == ShaderStage.Geometry;
639              }
640          }
641  
642          private static void AppendHelperFunction(CodeGenContext context, string filename)
643          {
644              string code = EmbeddedResources.ReadAllText(filename);
645  
646              code = code.Replace("\t", CodeGenContext.Tab);
647  
648              if (context.HostCapabilities.SupportsShaderBallot)
649              {
650                  code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB");
651                  code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB");
652              }
653              else
654              {
655                  code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubgroupInvocationID");
656                  code = code.Replace("$SUBGROUP_BROADCAST$", "subgroupBroadcast");
657              }
658  
659              context.AppendLine(code);
660              context.AppendLine();
661          }
662      }
663  }