InstGenMemory.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  2. using Ryujinx.Graphics.Shader.StructuredIr;
  3. using System;
  4. using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper;
  5. using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
  6. namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
  7. {
  8. static class InstGenMemory
  9. {
  10. public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
  11. {
  12. AstTextureOperation texOp = (AstTextureOperation)operation;
  13. bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
  14. // TODO: Bindless texture support. For now we just return 0/do nothing.
  15. if (isBindless)
  16. {
  17. return texOp.Inst == Instruction.ImageLoad ? NumberFormatter.FormatFloat(0) : "// imageStore(bindless)";
  18. }
  19. bool isArray = (texOp.Type & SamplerType.Array) != 0;
  20. bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
  21. string texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";
  22. int srcIndex = isBindless ? 1 : 0;
  23. string Src(VariableType type)
  24. {
  25. return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
  26. }
  27. string indexExpr = null;
  28. if (isIndexed)
  29. {
  30. indexExpr = Src(VariableType.S32);
  31. }
  32. string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);
  33. texCall += "(" + imageName;
  34. int coordsCount = texOp.Type.GetDimensions();
  35. int pCount = coordsCount + (isArray ? 1 : 0);
  36. void Append(string str)
  37. {
  38. texCall += ", " + str;
  39. }
  40. string ApplyScaling(string vector)
  41. {
  42. if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
  43. texOp.Inst == Instruction.ImageLoad &&
  44. !isBindless &&
  45. !isIndexed)
  46. {
  47. // Image scales start after texture ones.
  48. int scaleIndex = context.Config.GetTextureDescriptors().Length + context.FindImageDescriptorIndex(texOp);
  49. if (pCount == 3 && isArray)
  50. {
  51. // The array index is not scaled, just x and y.
  52. vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + scaleIndex + "), (" + vector + ").z)";
  53. }
  54. else if (pCount == 2 && !isArray)
  55. {
  56. vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")";
  57. }
  58. }
  59. return vector;
  60. }
  61. if (pCount > 1)
  62. {
  63. string[] elems = new string[pCount];
  64. for (int index = 0; index < pCount; index++)
  65. {
  66. elems[index] = Src(VariableType.S32);
  67. }
  68. Append(ApplyScaling("ivec" + pCount + "(" + string.Join(", ", elems) + ")"));
  69. }
  70. else
  71. {
  72. Append(Src(VariableType.S32));
  73. }
  74. if (texOp.Inst == Instruction.ImageStore)
  75. {
  76. int texIndex = context.FindImageDescriptorIndex(texOp);
  77. VariableType type = texOp.Format.GetComponentType();
  78. string[] cElems = new string[4];
  79. for (int index = 0; index < 4; index++)
  80. {
  81. if (srcIndex < texOp.SourcesCount)
  82. {
  83. cElems[index] = Src(type);
  84. }
  85. else
  86. {
  87. cElems[index] = type switch
  88. {
  89. VariableType.S32 => NumberFormatter.FormatInt(0),
  90. VariableType.U32 => NumberFormatter.FormatUint(0),
  91. _ => NumberFormatter.FormatFloat(0)
  92. };
  93. }
  94. }
  95. string prefix = type switch
  96. {
  97. VariableType.S32 => "i",
  98. VariableType.U32 => "u",
  99. _ => string.Empty
  100. };
  101. Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
  102. }
  103. texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMask(texOp.Index) : "");
  104. return texCall;
  105. }
  106. public static string LoadAttribute(CodeGenContext context, AstOperation operation)
  107. {
  108. IAstNode src1 = operation.GetSource(0);
  109. IAstNode src2 = operation.GetSource(1);
  110. IAstNode src3 = operation.GetSource(2);
  111. if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
  112. {
  113. throw new InvalidOperationException($"First input of {nameof(Instruction.LoadAttribute)} must be a constant operand.");
  114. }
  115. string indexExpr = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
  116. if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
  117. {
  118. return OperandManager.GetAttributeName(baseAttr.Value + (operand.Value << 2), context.Config, isOutAttr: false, indexExpr);
  119. }
  120. else
  121. {
  122. string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
  123. attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
  124. return OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: false, indexExpr);
  125. }
  126. }
  127. public static string LoadConstant(CodeGenContext context, AstOperation operation)
  128. {
  129. IAstNode src1 = operation.GetSource(0);
  130. IAstNode src2 = operation.GetSource(1);
  131. string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
  132. offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true);
  133. var config = context.Config;
  134. bool indexElement = !config.GpuAccessor.QueryHostHasVectorIndexingBug();
  135. if (src1 is AstOperand operand && operand.Type == OperandType.Constant)
  136. {
  137. bool cbIndexable = config.UsedFeatures.HasFlag(Translation.FeatureFlags.CbIndexing);
  138. return OperandManager.GetConstantBufferName(operand.Value, offsetExpr, config.Stage, cbIndexable, indexElement);
  139. }
  140. else
  141. {
  142. string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
  143. return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, config.Stage, indexElement);
  144. }
  145. }
  146. public static string LoadLocal(CodeGenContext context, AstOperation operation)
  147. {
  148. return LoadLocalOrShared(context, operation, DefaultNames.LocalMemoryName);
  149. }
  150. public static string LoadShared(CodeGenContext context, AstOperation operation)
  151. {
  152. return LoadLocalOrShared(context, operation, DefaultNames.SharedMemoryName);
  153. }
  154. private static string LoadLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName)
  155. {
  156. IAstNode src1 = operation.GetSource(0);
  157. string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
  158. return $"{arrayName}[{offsetExpr}]";
  159. }
  160. public static string LoadStorage(CodeGenContext context, AstOperation operation)
  161. {
  162. IAstNode src1 = operation.GetSource(0);
  163. IAstNode src2 = operation.GetSource(1);
  164. string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
  165. string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
  166. return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
  167. }
  168. public static string Lod(CodeGenContext context, AstOperation operation)
  169. {
  170. AstTextureOperation texOp = (AstTextureOperation)operation;
  171. int coordsCount = texOp.Type.GetDimensions();
  172. bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
  173. // TODO: Bindless texture support. For now we just return 0.
  174. if (isBindless)
  175. {
  176. return NumberFormatter.FormatFloat(0);
  177. }
  178. bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
  179. string indexExpr = null;
  180. if (isIndexed)
  181. {
  182. indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32);
  183. }
  184. string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
  185. int coordsIndex = isBindless || isIndexed ? 1 : 0;
  186. string coordsExpr;
  187. if (coordsCount > 1)
  188. {
  189. string[] elems = new string[coordsCount];
  190. for (int index = 0; index < coordsCount; index++)
  191. {
  192. elems[index] = GetSoureExpr(context, texOp.GetSource(coordsIndex + index), VariableType.F32);
  193. }
  194. coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")";
  195. }
  196. else
  197. {
  198. coordsExpr = GetSoureExpr(context, texOp.GetSource(coordsIndex), VariableType.F32);
  199. }
  200. return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}";
  201. }
  202. public static string StoreAttribute(CodeGenContext context, AstOperation operation)
  203. {
  204. IAstNode src1 = operation.GetSource(0);
  205. IAstNode src2 = operation.GetSource(1);
  206. IAstNode src3 = operation.GetSource(2);
  207. if (!(src1 is AstOperand baseAttr) || baseAttr.Type != OperandType.Constant)
  208. {
  209. throw new InvalidOperationException($"First input of {nameof(Instruction.StoreAttribute)} must be a constant operand.");
  210. }
  211. string attrName;
  212. if (src2 is AstOperand operand && operand.Type == OperandType.Constant)
  213. {
  214. attrName = OperandManager.GetAttributeName(baseAttr.Value + (operand.Value << 2), context.Config, isOutAttr: true);
  215. }
  216. else
  217. {
  218. string attrExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
  219. attrExpr = Enclose(attrExpr, src2, Instruction.ShiftRightS32, isLhs: true);
  220. attrName = OperandManager.GetAttributeName(attrExpr, context.Config, isOutAttr: true);
  221. }
  222. string value = GetSoureExpr(context, src3, GetSrcVarType(operation.Inst, 2));
  223. return $"{attrName} = {value}";
  224. }
  225. public static string StoreLocal(CodeGenContext context, AstOperation operation)
  226. {
  227. return StoreLocalOrShared(context, operation, DefaultNames.LocalMemoryName);
  228. }
  229. public static string StoreShared(CodeGenContext context, AstOperation operation)
  230. {
  231. return StoreLocalOrShared(context, operation, DefaultNames.SharedMemoryName);
  232. }
  233. private static string StoreLocalOrShared(CodeGenContext context, AstOperation operation, string arrayName)
  234. {
  235. IAstNode src1 = operation.GetSource(0);
  236. IAstNode src2 = operation.GetSource(1);
  237. string offsetExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
  238. VariableType srcType = OperandManager.GetNodeDestType(context, src2);
  239. string src = TypeConversion.ReinterpretCast(context, src2, srcType, VariableType.U32);
  240. return $"{arrayName}[{offsetExpr}] = {src}";
  241. }
  242. public static string StoreStorage(CodeGenContext context, AstOperation operation)
  243. {
  244. IAstNode src1 = operation.GetSource(0);
  245. IAstNode src2 = operation.GetSource(1);
  246. IAstNode src3 = operation.GetSource(2);
  247. string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
  248. string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
  249. VariableType srcType = OperandManager.GetNodeDestType(context, src3);
  250. string src = TypeConversion.ReinterpretCast(context, src3, srcType, VariableType.U32);
  251. string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
  252. return $"{sb} = {src}";
  253. }
  254. public static string TextureSample(CodeGenContext context, AstOperation operation)
  255. {
  256. AstTextureOperation texOp = (AstTextureOperation)operation;
  257. bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
  258. bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
  259. bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
  260. bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
  261. bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0;
  262. bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
  263. bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
  264. bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
  265. bool isArray = (texOp.Type & SamplerType.Array) != 0;
  266. bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
  267. bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
  268. bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
  269. SamplerType type = texOp.Type & SamplerType.Mask;
  270. bool is2D = type == SamplerType.Texture2D;
  271. bool isCube = type == SamplerType.TextureCube;
  272. // 2D Array and Cube shadow samplers with LOD level or bias requires an extension.
  273. // If the extension is not supported, just remove the LOD parameter.
  274. if (isArray && isShadow && (is2D || isCube) && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
  275. {
  276. hasLodBias = false;
  277. hasLodLevel = false;
  278. }
  279. // Cube shadow samplers with LOD level requires an extension.
  280. // If the extension is not supported, just remove the LOD level parameter.
  281. if (isShadow && isCube && !context.Config.GpuAccessor.QueryHostSupportsTextureShadowLod())
  282. {
  283. hasLodLevel = false;
  284. }
  285. // TODO: Bindless texture support. For now we just return 0.
  286. if (isBindless)
  287. {
  288. return NumberFormatter.FormatFloat(0);
  289. }
  290. string texCall = intCoords ? "texelFetch" : "texture";
  291. if (isGather)
  292. {
  293. texCall += "Gather";
  294. }
  295. else if (hasDerivatives)
  296. {
  297. texCall += "Grad";
  298. }
  299. else if (hasLodLevel && !intCoords)
  300. {
  301. texCall += "Lod";
  302. }
  303. if (hasOffset)
  304. {
  305. texCall += "Offset";
  306. }
  307. else if (hasOffsets)
  308. {
  309. texCall += "Offsets";
  310. }
  311. int srcIndex = isBindless ? 1 : 0;
  312. string Src(VariableType type)
  313. {
  314. return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
  315. }
  316. string indexExpr = null;
  317. if (isIndexed)
  318. {
  319. indexExpr = Src(VariableType.S32);
  320. }
  321. string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
  322. texCall += "(" + samplerName;
  323. int coordsCount = texOp.Type.GetDimensions();
  324. int pCount = coordsCount;
  325. int arrayIndexElem = -1;
  326. if (isArray)
  327. {
  328. arrayIndexElem = pCount++;
  329. }
  330. // The sampler 1D shadow overload expects a
  331. // dummy value on the middle of the vector, who knows why...
  332. bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow);
  333. if (hasDummy1DShadowElem)
  334. {
  335. pCount++;
  336. }
  337. if (isShadow && !isGather)
  338. {
  339. pCount++;
  340. }
  341. // On textureGather*, the comparison value is
  342. // always specified as an extra argument.
  343. bool hasExtraCompareArg = isShadow && isGather;
  344. if (pCount == 5)
  345. {
  346. pCount = 4;
  347. hasExtraCompareArg = true;
  348. }
  349. void Append(string str)
  350. {
  351. texCall += ", " + str;
  352. }
  353. VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32;
  354. string AssemblePVector(int count)
  355. {
  356. if (count > 1)
  357. {
  358. string[] elems = new string[count];
  359. for (int index = 0; index < count; index++)
  360. {
  361. if (arrayIndexElem == index)
  362. {
  363. elems[index] = Src(VariableType.S32);
  364. if (!intCoords)
  365. {
  366. elems[index] = "float(" + elems[index] + ")";
  367. }
  368. }
  369. else if (index == 1 && hasDummy1DShadowElem)
  370. {
  371. elems[index] = NumberFormatter.FormatFloat(0);
  372. }
  373. else
  374. {
  375. elems[index] = Src(coordType);
  376. }
  377. }
  378. string prefix = intCoords ? "i" : string.Empty;
  379. return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")";
  380. }
  381. else
  382. {
  383. return Src(coordType);
  384. }
  385. }
  386. string ApplyScaling(string vector)
  387. {
  388. if (intCoords)
  389. {
  390. if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
  391. !isBindless &&
  392. !isIndexed)
  393. {
  394. int index = context.FindTextureDescriptorIndex(texOp);
  395. if (pCount == 3 && isArray)
  396. {
  397. // The array index is not scaled, just x and y.
  398. vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + index + "), (" + vector + ").z)";
  399. }
  400. else if (pCount == 2 && !isArray)
  401. {
  402. vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")";
  403. }
  404. }
  405. }
  406. return vector;
  407. }
  408. Append(ApplyScaling(AssemblePVector(pCount)));
  409. string AssembleDerivativesVector(int count)
  410. {
  411. if (count > 1)
  412. {
  413. string[] elems = new string[count];
  414. for (int index = 0; index < count; index++)
  415. {
  416. elems[index] = Src(VariableType.F32);
  417. }
  418. return "vec" + count + "(" + string.Join(", ", elems) + ")";
  419. }
  420. else
  421. {
  422. return Src(VariableType.F32);
  423. }
  424. }
  425. if (hasExtraCompareArg)
  426. {
  427. Append(Src(VariableType.F32));
  428. }
  429. if (hasDerivatives)
  430. {
  431. Append(AssembleDerivativesVector(coordsCount)); // dPdx
  432. Append(AssembleDerivativesVector(coordsCount)); // dPdy
  433. }
  434. if (isMultisample)
  435. {
  436. Append(Src(VariableType.S32));
  437. }
  438. else if (hasLodLevel)
  439. {
  440. Append(Src(coordType));
  441. }
  442. string AssembleOffsetVector(int count)
  443. {
  444. if (count > 1)
  445. {
  446. string[] elems = new string[count];
  447. for (int index = 0; index < count; index++)
  448. {
  449. elems[index] = Src(VariableType.S32);
  450. }
  451. return "ivec" + count + "(" + string.Join(", ", elems) + ")";
  452. }
  453. else
  454. {
  455. return Src(VariableType.S32);
  456. }
  457. }
  458. if (hasOffset)
  459. {
  460. Append(AssembleOffsetVector(coordsCount));
  461. }
  462. else if (hasOffsets)
  463. {
  464. texCall += $", ivec{coordsCount}[4](";
  465. texCall += AssembleOffsetVector(coordsCount) + ", ";
  466. texCall += AssembleOffsetVector(coordsCount) + ", ";
  467. texCall += AssembleOffsetVector(coordsCount) + ", ";
  468. texCall += AssembleOffsetVector(coordsCount) + ")";
  469. }
  470. if (hasLodBias)
  471. {
  472. Append(Src(VariableType.F32));
  473. }
  474. // textureGather* optional extra component index,
  475. // not needed for shadow samplers.
  476. if (isGather && !isShadow)
  477. {
  478. Append(Src(VariableType.S32));
  479. }
  480. texCall += ")" + (isGather || !isShadow ? GetMask(texOp.Index) : "");
  481. return texCall;
  482. }
  483. public static string TextureSize(CodeGenContext context, AstOperation operation)
  484. {
  485. AstTextureOperation texOp = (AstTextureOperation)operation;
  486. bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
  487. // TODO: Bindless texture support. For now we just return 0.
  488. if (isBindless)
  489. {
  490. return NumberFormatter.FormatInt(0);
  491. }
  492. bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
  493. string indexExpr = null;
  494. if (isIndexed)
  495. {
  496. indexExpr = GetSoureExpr(context, texOp.GetSource(0), VariableType.S32);
  497. }
  498. string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
  499. int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
  500. IAstNode lod = operation.GetSource(lodSrcIndex);
  501. string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
  502. if (texOp.Index == 3)
  503. {
  504. return $"textureQueryLevels({samplerName})";
  505. }
  506. else
  507. {
  508. string texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
  509. if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
  510. !isBindless &&
  511. !isIndexed)
  512. {
  513. int index = context.FindTextureDescriptorIndex(texOp);
  514. texCall = "Helper_TextureSizeUnscale(" + texCall + ", " + index + ")";
  515. }
  516. return texCall;
  517. }
  518. }
  519. private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
  520. {
  521. string sbName = OperandManager.GetShaderStagePrefix(stage);
  522. sbName += "_" + DefaultNames.StorageNamePrefix;
  523. return $"{sbName}[{slotExpr}].{DefaultNames.DataName}[{offsetExpr}]";
  524. }
  525. private static string GetMask(int index)
  526. {
  527. return '.' + "rgba".Substring(index, 1);
  528. }
  529. }
  530. }