/ src / Ryujinx.Graphics.Shader / Instructions / InstEmitAttribute.cs
InstEmitAttribute.cs
  1  using Ryujinx.Graphics.Shader.Decoders;
  2  using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  3  using Ryujinx.Graphics.Shader.Translation;
  4  
  5  using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
  6  using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
  7  
  8  namespace Ryujinx.Graphics.Shader.Instructions
  9  {
 10      static partial class InstEmit
 11      {
 12          public static void Al2p(EmitterContext context)
 13          {
 14              InstAl2p op = context.GetOp<InstAl2p>();
 15  
 16              context.Copy(GetDest(op.Dest), context.IAdd(GetSrcReg(context, op.SrcA), Const(op.Imm11)));
 17          }
 18  
 19          public static void Ald(EmitterContext context)
 20          {
 21              InstAld op = context.GetOp<InstAld>();
 22  
 23              // Some of those attributes are per invocation,
 24              // so we should ignore any primitive vertex indexing for those.
 25              bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.TranslatorContext.Definitions.Stage, op.O) && !op.P;
 26  
 27              if (!op.Phys)
 28              {
 29                  hasPrimitiveVertex &= HasPrimitiveVertex(op.Imm11);
 30              }
 31  
 32              Operand primVertex = hasPrimitiveVertex ? context.Copy(GetSrcReg(context, op.SrcB)) : null;
 33  
 34              for (int index = 0; index < (int)op.AlSize + 1; index++)
 35              {
 36                  Register rd = new(op.Dest + index, RegisterType.Gpr);
 37  
 38                  if (rd.IsRZ)
 39                  {
 40                      break;
 41                  }
 42  
 43                  if (op.Phys)
 44                  {
 45                      Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
 46                      Operand vecIndex = context.ShiftRightU32(offset, Const(4));
 47                      Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
 48  
 49                      StorageKind storageKind = op.O ? StorageKind.Output : StorageKind.Input;
 50  
 51                      context.Copy(Register(rd), context.Load(storageKind, IoVariable.UserDefined, primVertex, vecIndex, elemIndex));
 52                  }
 53                  else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P)
 54                  {
 55                      int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
 56                      bool isOutput = op.O && CanLoadOutput(offset);
 57  
 58                      if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value))
 59                      {
 60                          context.Copy(Register(rd), value);
 61                      }
 62                      else
 63                      {
 64                          value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P);
 65  
 66                          if ((!context.TranslatorContext.Definitions.SupportsScaledVertexFormats || context.VertexAsCompute) &&
 67                              context.TranslatorContext.Stage == ShaderStage.Vertex &&
 68                              !op.O &&
 69                              offset >= 0x80 &&
 70                              offset < 0x280)
 71                          {
 72                              // The host does not support scaled vertex formats,
 73                              // the emulator should use a integer format, and
 74                              // we compensate here inserting the conversion to float.
 75  
 76                              AttributeType type = context.TranslatorContext.Definitions.GetAttributeType((offset - 0x80) >> 4);
 77  
 78                              if (type == AttributeType.Sscaled)
 79                              {
 80                                  value = context.IConvertS32ToFP32(value);
 81                              }
 82                              else if (type == AttributeType.Uscaled)
 83                              {
 84                                  value = context.IConvertU32ToFP32(value);
 85                              }
 86                          }
 87                          else if (offset == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
 88                          {
 89                              value = context.ShiftRightS32(value, Const(1));
 90                          }
 91  
 92                          context.Copy(Register(rd), value);
 93                      }
 94                  }
 95                  else
 96                  {
 97                      int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O);
 98                      bool isOutput = op.O && CanLoadOutput(offset);
 99  
100                      context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false));
101                  }
102              }
103          }
104  
105          public static void Ast(EmitterContext context)
106          {
107              InstAst op = context.GetOp<InstAst>();
108  
109              for (int index = 0; index < (int)op.AlSize + 1; index++)
110              {
111                  if (op.SrcB + index > RegisterConsts.RegisterZeroIndex)
112                  {
113                      break;
114                  }
115  
116                  Register rd = new(op.SrcB + index, RegisterType.Gpr);
117  
118                  if (op.Phys)
119                  {
120                      Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
121                      Operand vecIndex = context.ShiftRightU32(offset, Const(4));
122                      Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
123                      Operand invocationId = AttributeMap.HasInvocationId(context.TranslatorContext.Definitions.Stage, isOutput: true)
124                          ? context.Load(StorageKind.Input, IoVariable.InvocationId)
125                          : null;
126  
127                      context.Store(StorageKind.Output, IoVariable.UserDefined, invocationId, vecIndex, elemIndex, Register(rd));
128                  }
129                  else
130                  {
131                      // TODO: Support indirect stores using Ra.
132  
133                      int offset = op.Imm11 + index * 4;
134  
135                      if (!context.TranslatorContext.AttributeUsage.IsUsedOutputAttribute(offset))
136                      {
137                          return;
138                      }
139  
140                      offset = FixedFuncToUserAttribute(context.TranslatorContext, offset, isOutput: true);
141                      AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd));
142                  }
143              }
144          }
145  
146          public static void Ipa(EmitterContext context)
147          {
148              InstIpa op = context.GetOp<InstIpa>();
149  
150              Operand res;
151  
152              bool isFixedFunc = false;
153  
154              if (op.Idx)
155              {
156                  Operand offset = context.ISubtract(GetSrcReg(context, op.SrcA), Const(AttributeConsts.UserAttributeBase));
157                  Operand vecIndex = context.ShiftRightU32(offset, Const(4));
158                  Operand elemIndex = context.BitwiseAnd(context.ShiftRightU32(offset, Const(2)), Const(3));
159  
160                  res = context.Load(StorageKind.Input, IoVariable.UserDefined, null, vecIndex, elemIndex);
161                  res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
162              }
163              else
164              {
165                  isFixedFunc = TryFixedFuncToUserAttributeIpa(context, op.Imm10, out res);
166  
167                  if (op.Imm10 >= AttributeConsts.UserAttributeBase && op.Imm10 < AttributeConsts.UserAttributeEnd)
168                  {
169                      int index = (op.Imm10 - AttributeConsts.UserAttributeBase) >> 4;
170  
171                      if (context.TranslatorContext.Definitions.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective)
172                      {
173                          res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3)));
174                      }
175                  }
176                  else if (op.Imm10 == AttributeConsts.PositionX || op.Imm10 == AttributeConsts.PositionY)
177                  {
178                      // FragCoord X/Y must be divided by the render target scale, if resolution scaling is active,
179                      // because the shader code is not expecting scaled values.
180                      res = context.FPDivide(res, context.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0)));
181  
182                      if (op.Imm10 == AttributeConsts.PositionY && context.TranslatorContext.Options.TargetApi != TargetApi.OpenGL)
183                      {
184                          // If YNegate is enabled, we need to flip the fragment coordinates vertically, unless
185                          // the API supports changing the origin (only OpenGL does).
186                          if (context.TranslatorContext.Definitions.YNegateEnabled)
187                          {
188                              Operand viewportHeight = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.ViewportSize), Const(1));
189  
190                              res = context.FPSubtract(viewportHeight, res);
191                          }
192                      }
193                  }
194                  else if (op.Imm10 == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId)
195                  {
196                      // If quads are used, but the host does not support them, they need to be converted to triangles.
197                      // Since each quad becomes 2 triangles, we need to compensate here and divide primitive ID by 2.
198                      res = context.ShiftRightS32(res, Const(1));
199                  }
200                  else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.GpuAccessor.QueryHostHasFrontFacingBug())
201                  {
202                      // gl_FrontFacing sometimes has incorrect (flipped) values depending how it is accessed on Intel GPUs.
203                      // This weird trick makes it behave.
204                      res = context.ICompareLess(context.INegate(context.FP32ConvertToS32(context.ConditionalSelect(res, ConstF(1f), ConstF(0f)))), Const(0));
205                  }
206              }
207  
208              if (op.IpaOp == IpaOp.Multiply && !isFixedFunc)
209              {
210                  Operand srcB = GetSrcReg(context, op.SrcB);
211  
212                  res = context.FPMultiply(res, srcB);
213              }
214  
215              res = context.FPSaturate(res, op.Sat);
216  
217              context.Copy(GetDest(op.Dest), res);
218          }
219  
220          public static void Isberd(EmitterContext context)
221          {
222              InstIsberd op = context.GetOp<InstIsberd>();
223  
224              // This instruction performs a load from ISBE (Internal Stage Buffer Entry) memory.
225              // Here, we just propagate the offset, as the result from this instruction is usually
226              // used with ALD to perform vertex load on geometry or tessellation shaders.
227              // The offset is calculated as (PrimitiveIndex * VerticesPerPrimitive) + VertexIndex.
228              // Since we hardcode PrimitiveIndex to zero, then the offset will be just VertexIndex.
229              context.Copy(GetDest(op.Dest), GetSrcReg(context, op.SrcA));
230          }
231  
232          public static void OutR(EmitterContext context)
233          {
234              InstOutR op = context.GetOp<InstOutR>();
235  
236              EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut));
237          }
238  
239          public static void OutI(EmitterContext context)
240          {
241              InstOutI op = context.GetOp<InstOutI>();
242  
243              EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut));
244          }
245  
246          public static void OutC(EmitterContext context)
247          {
248              InstOutC op = context.GetOp<InstOutC>();
249  
250              EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut));
251          }
252  
253          private static void EmitOut(EmitterContext context, bool emit, bool cut)
254          {
255              if (!(emit || cut))
256              {
257                  context.TranslatorContext.GpuAccessor.Log("Invalid OUT encoding.");
258              }
259  
260              if (emit)
261              {
262                  if (context.TranslatorContext.Definitions.LastInVertexPipeline)
263                  {
264                      context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal);
265  
266                      context.EmitVertex();
267  
268                      // Restore output position value before transformation.
269  
270                      if (tempXLocal != null)
271                      {
272                          context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(0)), tempXLocal);
273                      }
274  
275                      if (tempYLocal != null)
276                      {
277                          context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(1)), tempYLocal);
278                      }
279  
280                      if (tempZLocal != null)
281                      {
282                          context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(2)), tempZLocal);
283                      }
284                  }
285                  else
286                  {
287                      context.EmitVertex();
288                  }
289              }
290  
291              if (cut)
292              {
293                  context.EndPrimitive();
294              }
295          }
296  
297          private static bool HasPrimitiveVertex(int attr)
298          {
299              return attr != AttributeConsts.PrimitiveId &&
300                     attr != AttributeConsts.TessCoordX &&
301                     attr != AttributeConsts.TessCoordY;
302          }
303  
304          private static bool CanLoadOutput(int attr)
305          {
306              return attr != AttributeConsts.TessCoordX && attr != AttributeConsts.TessCoordY;
307          }
308  
309          private static bool TryFixedFuncToUserAttributeIpa(EmitterContext context, int attr, out Operand selectedAttr)
310          {
311              if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.BackColorDiffuseR)
312              {
313                  // TODO: If two sided rendering is enabled, then this should return
314                  // FrontColor if the fragment is front facing, and back color otherwise.
315                  selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
316                  return true;
317              }
318              else if (attr == AttributeConsts.FogCoord)
319              {
320                  // TODO: We likely need to emulate the fixed-function functionality for FogCoord here.
321                  selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
322                  return true;
323              }
324              else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0)
325              {
326                  selectedAttr = ConstF(((attr >> 2) & 3) == 3 ? 1f : 0f);
327                  return true;
328              }
329              else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
330              {
331                  selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false));
332                  return true;
333              }
334  
335              selectedAttr = GenerateIpaLoad(context, attr);
336              return false;
337          }
338  
339          private static Operand GenerateIpaLoad(EmitterContext context, int offset)
340          {
341              return AttributeMap.GenerateAttributeLoad(context, null, offset, isOutput: false, isPerPatch: false);
342          }
343  
344          private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, bool isOutput)
345          {
346              bool supportsLayerFromVertexOrTess = translatorContext.GpuAccessor.QueryHostSupportsLayerVertexTessellation();
347              int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1;
348  
349              if (attr == AttributeConsts.Layer && translatorContext.Definitions.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess)
350              {
351                  attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.Layer, 0, isOutput);
352                  translatorContext.SetLayerOutputAttribute(attr);
353              }
354              else if (attr == AttributeConsts.FogCoord)
355              {
356                  attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput);
357              }
358              else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0)
359              {
360                  attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput);
361              }
362              else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd)
363              {
364                  attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput);
365              }
366  
367              return attr;
368          }
369  
370          private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, int baseAttr, int baseIndex, bool isOutput)
371          {
372              int index = (attr - baseAttr) >> 4;
373              int userAttrIndex = translatorContext.AttributeUsage.GetFreeUserAttribute(isOutput, baseIndex + index);
374  
375              if ((uint)userAttrIndex < Constants.MaxAttributes)
376              {
377                  attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf);
378              }
379              else
380              {
381                  translatorContext.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}.");
382              }
383  
384              return attr;
385          }
386  
387          private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value)
388          {
389              if (context.TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
390              {
391                  if (attr == AttributeConsts.InstanceId)
392                  {
393                      value = context.ISubtract(
394                          context.Load(StorageKind.Input, IoVariable.InstanceIndex),
395                          context.Load(StorageKind.Input, IoVariable.BaseInstance));
396                      return true;
397                  }
398                  else if (attr == AttributeConsts.VertexId)
399                  {
400                      value = context.Load(StorageKind.Input, IoVariable.VertexIndex);
401                      return true;
402                  }
403              }
404  
405              value = null;
406              return false;
407          }
408      }
409  }