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 }