ShaderDump.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <AnKi/ShaderCompiler/ShaderDump.h>
  6. #include <AnKi/ShaderCompiler/MaliOfflineCompiler.h>
  7. #include <AnKi/ShaderCompiler/Dxc.h>
  8. #include <AnKi/ShaderCompiler/RadeonGpuAnalyzer.h>
  9. #include <AnKi/Util/Serializer.h>
  10. #include <AnKi/Util/StringList.h>
  11. #include <SpirvCross/spirv_glsl.hpp>
  12. #include <ThirdParty/SpirvTools/include/spirv-tools/libspirv.h>
  13. namespace anki {
  14. #define ANKI_TAB " "
  15. class MaliOfflineCompilerStats
  16. {
  17. public:
  18. F64 m_fma = 0.0;
  19. F64 m_cvt = 0.0;
  20. F64 m_sfu = 0.0;
  21. F64 m_loadStore = 0.0;
  22. F64 m_varying = 0.0;
  23. F64 m_texture = 0.0;
  24. F64 m_workRegisters = 0.0;
  25. F64 m_spilling = 0.0;
  26. F64 m_fp16ArithmeticPercentage = 0.0;
  27. MaliOfflineCompilerStats() = default;
  28. MaliOfflineCompilerStats(const MaliOfflineCompilerOut& in)
  29. {
  30. *this = in;
  31. }
  32. MaliOfflineCompilerStats& operator=(const MaliOfflineCompilerOut& in)
  33. {
  34. m_fma = F64(in.m_fma);
  35. m_cvt = F64(in.m_cvt);
  36. m_sfu = F64(in.m_sfu);
  37. m_loadStore = F64(in.m_loadStore);
  38. m_varying = F64(in.m_varying);
  39. m_texture = F64(in.m_texture);
  40. m_workRegisters = F64(in.m_workRegisters);
  41. m_spilling = F64(in.m_spilling);
  42. m_fp16ArithmeticPercentage = F64(in.m_fp16ArithmeticPercentage);
  43. return *this;
  44. }
  45. MaliOfflineCompilerStats operator+(const MaliOfflineCompilerStats& b) const
  46. {
  47. MaliOfflineCompilerStats out;
  48. out.m_fma = m_fma + b.m_fma;
  49. out.m_cvt = m_cvt + b.m_cvt;
  50. out.m_sfu = m_sfu + b.m_sfu;
  51. out.m_loadStore = m_loadStore + b.m_loadStore;
  52. out.m_varying = m_varying + b.m_varying;
  53. out.m_texture = m_texture + b.m_texture;
  54. out.m_workRegisters = m_workRegisters + b.m_workRegisters;
  55. out.m_spilling = m_spilling + b.m_spilling;
  56. out.m_fp16ArithmeticPercentage = m_fp16ArithmeticPercentage + b.m_fp16ArithmeticPercentage;
  57. return out;
  58. }
  59. MaliOfflineCompilerStats operator*(F64 val) const
  60. {
  61. MaliOfflineCompilerStats out;
  62. out.m_fma = m_fma * val;
  63. out.m_cvt = m_cvt * val;
  64. out.m_sfu = m_sfu * val;
  65. out.m_loadStore = m_loadStore * val;
  66. out.m_varying = m_varying * val;
  67. out.m_texture = m_texture * val;
  68. out.m_workRegisters = m_workRegisters * val;
  69. out.m_spilling = m_spilling * val;
  70. out.m_fp16ArithmeticPercentage = m_fp16ArithmeticPercentage * val;
  71. return out;
  72. }
  73. MaliOfflineCompilerStats max(const MaliOfflineCompilerStats& b) const
  74. {
  75. MaliOfflineCompilerStats out;
  76. out.m_fma = anki::max(m_fma, b.m_fma);
  77. out.m_cvt = anki::max(m_cvt, b.m_cvt);
  78. out.m_sfu = anki::max(m_sfu, b.m_sfu);
  79. out.m_loadStore = anki::max(m_loadStore, b.m_loadStore);
  80. out.m_varying = anki::max(m_varying, b.m_varying);
  81. out.m_texture = anki::max(m_texture, b.m_texture);
  82. out.m_workRegisters = anki::max(m_workRegisters, b.m_workRegisters);
  83. out.m_spilling = anki::max(m_spilling, b.m_spilling);
  84. out.m_fp16ArithmeticPercentage = anki::max(m_fp16ArithmeticPercentage, b.m_fp16ArithmeticPercentage);
  85. return out;
  86. }
  87. String toString() const
  88. {
  89. String str;
  90. str.sprintf("Regs %f Spilling %f FMA %f CVT %f SFU %f LS %f VAR %f TEX %f FP16 %f%%", m_workRegisters, m_spilling, m_fma, m_cvt, m_sfu,
  91. m_loadStore, m_varying, m_texture, m_fp16ArithmeticPercentage);
  92. return str;
  93. }
  94. };
  95. class RgaStats
  96. {
  97. public:
  98. F64 m_vgprCount = 0.0;
  99. F64 m_sgprCount = 0.0;
  100. F64 m_isaSize = 0.0;
  101. RgaStats() = default;
  102. RgaStats(const RgaOutput& b)
  103. {
  104. *this = b;
  105. }
  106. RgaStats& operator=(const RgaOutput& b)
  107. {
  108. m_vgprCount = F64(b.m_vgprCount);
  109. m_sgprCount = F64(b.m_sgprCount);
  110. m_isaSize = F64(b.m_isaSize);
  111. return *this;
  112. }
  113. RgaStats operator+(const RgaStats& b) const
  114. {
  115. RgaStats out;
  116. out.m_vgprCount = m_vgprCount + b.m_vgprCount;
  117. out.m_sgprCount = m_sgprCount + b.m_sgprCount;
  118. out.m_isaSize = m_isaSize + b.m_isaSize;
  119. return out;
  120. }
  121. RgaStats operator*(F64 val) const
  122. {
  123. RgaStats out;
  124. out.m_vgprCount = m_vgprCount * val;
  125. out.m_sgprCount = m_sgprCount * val;
  126. out.m_isaSize = m_isaSize * val;
  127. return out;
  128. }
  129. RgaStats max(const RgaStats& b) const
  130. {
  131. RgaStats out;
  132. out.m_vgprCount = anki::max(m_vgprCount, b.m_vgprCount);
  133. out.m_sgprCount = anki::max(m_sgprCount, b.m_sgprCount);
  134. out.m_isaSize = anki::max(m_isaSize, b.m_isaSize);
  135. return out;
  136. }
  137. String toString() const
  138. {
  139. return String().sprintf("VGPRs %f SGPRs %f ISA size %f", m_vgprCount, m_sgprCount, m_isaSize);
  140. }
  141. };
  142. class PerStageDumpStats
  143. {
  144. public:
  145. class
  146. {
  147. public:
  148. MaliOfflineCompilerStats m_mali;
  149. RgaStats m_amd;
  150. } m_average;
  151. class
  152. {
  153. public:
  154. MaliOfflineCompilerStats m_mali;
  155. RgaStats m_amd;
  156. } m_max;
  157. };
  158. class DumpStats
  159. {
  160. public:
  161. Array<PerStageDumpStats, U32(ShaderType::kCount)> m_stages;
  162. };
  163. void dumpShaderBinary(const ShaderDumpOptions& options, const ShaderBinary& binary, ShaderCompilerString& humanReadable)
  164. {
  165. ShaderCompilerStringList lines;
  166. lines.pushBackSprintf("# BINARIES (%u)\n\n", binary.m_codeBlocks.getSize());
  167. Array<MaliOfflineCompilerStats, U32(ShaderType::kCount)> maliAverages;
  168. Array<MaliOfflineCompilerStats, U32(ShaderType::kCount)> maliMaxes;
  169. Array<RgaStats, U32(ShaderType::kCount)> rgaAverages;
  170. Array<RgaStats, U32(ShaderType::kCount)> rgaMaxes;
  171. Array<U32, U32(ShaderType::kCount)> shadersPerStage = {};
  172. U32 count = 0;
  173. ShaderTypeBit stagesInUse = ShaderTypeBit::kNone;
  174. for(const ShaderBinaryCodeBlock& code : binary.m_codeBlocks)
  175. {
  176. // Rewrite spir-v because of the decorations we ask DXC to put
  177. Bool bRequiresMeshShaders = false;
  178. DynamicArray<U8> newSpirv;
  179. newSpirv.resize(code.m_binary.getSize());
  180. memcpy(newSpirv.getBegin(), code.m_binary.getBegin(), code.m_binary.getSizeInBytes());
  181. ShaderType shaderType = ShaderType::kCount;
  182. Error visitErr = Error::kNone;
  183. visitSpirv(WeakArray<U32>(reinterpret_cast<U32*>(newSpirv.getBegin()), U32(newSpirv.getSizeInBytes() / sizeof(U32))),
  184. [&](U32 cmd, WeakArray<U32> instructions) {
  185. if(cmd == spv::OpDecorate && instructions[1] == spv::DecorationDescriptorSet
  186. && instructions[2] == ANKI_VK_BINDLESS_TEXTURES_DESCRIPTOR_SET)
  187. {
  188. // Bindless set, rewrite its set
  189. instructions[2] = kMaxRegisterSpaces;
  190. }
  191. else if(cmd == spv::OpCapability && instructions[0] == spv::CapabilityMeshShadingEXT)
  192. {
  193. bRequiresMeshShaders = true;
  194. }
  195. else if(cmd == spv::OpEntryPoint)
  196. {
  197. switch(instructions[0])
  198. {
  199. case spv::ExecutionModelVertex:
  200. shaderType = ShaderType::kVertex;
  201. break;
  202. case spv::ExecutionModelTessellationControl:
  203. shaderType = ShaderType::kHull;
  204. break;
  205. case spv::ExecutionModelTessellationEvaluation:
  206. shaderType = ShaderType::kDomain;
  207. break;
  208. case spv::ExecutionModelGeometry:
  209. shaderType = ShaderType::kGeometry;
  210. break;
  211. case spv::ExecutionModelTaskEXT:
  212. case spv::ExecutionModelTaskNV:
  213. shaderType = ShaderType::kAmplification;
  214. break;
  215. case spv::ExecutionModelMeshEXT:
  216. case spv::ExecutionModelMeshNV:
  217. shaderType = ShaderType::kMesh;
  218. break;
  219. case spv::ExecutionModelFragment:
  220. shaderType = ShaderType::kPixel;
  221. break;
  222. case spv::ExecutionModelGLCompute:
  223. shaderType = ShaderType::kCompute;
  224. break;
  225. case spv::ExecutionModelRayGenerationKHR:
  226. shaderType = ShaderType::kRayGen;
  227. break;
  228. case spv::ExecutionModelAnyHitKHR:
  229. shaderType = ShaderType::kAnyHit;
  230. break;
  231. case spv::ExecutionModelClosestHitKHR:
  232. shaderType = ShaderType::kClosestHit;
  233. break;
  234. case spv::ExecutionModelMissKHR:
  235. shaderType = ShaderType::kMiss;
  236. break;
  237. case spv::ExecutionModelIntersectionKHR:
  238. shaderType = ShaderType::kIntersection;
  239. break;
  240. case spv::ExecutionModelCallableKHR:
  241. shaderType = ShaderType::kCallable;
  242. break;
  243. default:
  244. ANKI_SHADER_COMPILER_LOGE("Unrecognized SPIRV execution model: %u", instructions[0]);
  245. visitErr = Error::kFunctionFailed;
  246. }
  247. }
  248. });
  249. stagesInUse |= ShaderTypeBit(1 << shaderType);
  250. ++shadersPerStage[shaderType];
  251. lines.pushBackSprintf("## bin%05u (%s)\n", count, g_shaderTypeNames[shaderType].cstr());
  252. if(options.m_maliStats || options.m_amdStats)
  253. {
  254. lines.pushBack("### Stats\n");
  255. }
  256. if(options.m_maliStats && !visitErr)
  257. {
  258. lines.pushBack("```\n");
  259. }
  260. if(options.m_maliStats && !visitErr)
  261. {
  262. if((shaderType == ShaderType::kVertex || shaderType == ShaderType::kPixel || shaderType == ShaderType::kCompute
  263. || (shaderType >= ShaderType::kFirstRayTracing && shaderType <= ShaderType::kLastRayTracing))
  264. && !bRequiresMeshShaders)
  265. {
  266. MaliOfflineCompilerOut maliocOut;
  267. const Error err = runMaliOfflineCompiler(newSpirv, shaderType, maliocOut);
  268. if(err)
  269. {
  270. ANKI_LOGE("Mali offline compiler failed");
  271. lines.pushBackSprintf("Mali: *malioc failed* \n");
  272. }
  273. else
  274. {
  275. lines.pushBackSprintf("Mali: %s \n", maliocOut.toString().cstr());
  276. maliAverages[shaderType] = maliAverages[shaderType] + maliocOut;
  277. maliMaxes[shaderType] = maliMaxes[shaderType].max(maliocOut);
  278. }
  279. }
  280. }
  281. if(options.m_amdStats && !visitErr)
  282. {
  283. if(shaderType == ShaderType::kVertex || shaderType == ShaderType::kPixel || shaderType == ShaderType::kCompute
  284. || shaderType == ShaderType::kMesh)
  285. {
  286. RgaOutput rgaOut = {};
  287. const Error err = runRadeonGpuAnalyzer(newSpirv, shaderType, rgaOut);
  288. if(err)
  289. {
  290. ANKI_LOGE("RGA failed");
  291. lines.pushBackSprintf("AMD: *RGA failed* \n");
  292. }
  293. else
  294. {
  295. lines.pushBackSprintf("AMD: %s \n", rgaOut.toString().cstr());
  296. rgaAverages[shaderType] = rgaAverages[shaderType] + rgaOut;
  297. rgaMaxes[shaderType] = rgaMaxes[shaderType].max(rgaOut);
  298. }
  299. }
  300. }
  301. if(options.m_maliStats && !visitErr)
  302. {
  303. lines.pushBack("```\n");
  304. }
  305. String reflectionStr;
  306. code.m_reflection.toString().join("\n", reflectionStr);
  307. lines.pushBack("### Reflection\n");
  308. lines.pushBackSprintf("```\n%s\n```\n", reflectionStr.cstr());
  309. if(options.m_writeGlsl)
  310. {
  311. lines.pushBack("### GLSL\n");
  312. spirv_cross::CompilerGLSL::Options options;
  313. options.vulkan_semantics = true;
  314. options.version = 460;
  315. options.force_temporary = true;
  316. const unsigned int* spvb = reinterpret_cast<const unsigned int*>(code.m_binary.getBegin());
  317. ANKI_ASSERT((code.m_binary.getSize() % (sizeof(unsigned int))) == 0);
  318. std::vector<unsigned int> spv(spvb, spvb + code.m_binary.getSize() / sizeof(unsigned int));
  319. spirv_cross::CompilerGLSL compiler(spv);
  320. compiler.set_common_options(options);
  321. std::string glsl = compiler.compile();
  322. StringList sourceLines;
  323. sourceLines.splitString(glsl.c_str(), '\n');
  324. String newGlsl;
  325. sourceLines.join("\n", newGlsl);
  326. lines.pushBackSprintf("```GLSL\n%s\n```\n", newGlsl.cstr());
  327. }
  328. if(options.m_writeSpirv)
  329. {
  330. lines.pushBack("### SPIR-V\n");
  331. spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_5);
  332. const U32 disOptions = SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
  333. spv_text text = nullptr;
  334. const spv_result_t error = spvBinaryToText(context, reinterpret_cast<const U32*>(code.m_binary.getBegin()),
  335. code.m_binary.getSizeInBytes() / 4, disOptions, &text, nullptr);
  336. spvContextDestroy(context);
  337. if(!error)
  338. {
  339. StringList spvlines;
  340. spvlines.splitString(text->str, '\n');
  341. String final;
  342. spvlines.join("\n", final);
  343. lines.pushBackSprintf("```\n%s\n```\n", final.cstr());
  344. }
  345. else
  346. {
  347. lines.pushBackSprintf("*error in spiv-dis*\n");
  348. }
  349. spvTextDestroy(text);
  350. }
  351. ++count;
  352. }
  353. // Mutators
  354. lines.pushBackSprintf("\n# MUTATORS (%u)\n\n", binary.m_mutators.getSize());
  355. if(binary.m_mutators.getSize() > 0)
  356. {
  357. lines.pushBackSprintf("| %-32s | %-18s |\n", "Name", "Values");
  358. lines.pushBackSprintf("| -------------------------------- | ------------------ |\n");
  359. for(const ShaderBinaryMutator& mutator : binary.m_mutators)
  360. {
  361. ShaderCompilerStringList valuesStrl;
  362. for(U32 i = 0; i < mutator.m_values.getSize(); ++i)
  363. {
  364. valuesStrl.pushBackSprintf("%d", mutator.m_values[i]);
  365. }
  366. ShaderCompilerString valuesStr;
  367. valuesStrl.join(", ", valuesStr);
  368. lines.pushBackSprintf("| %-32s | %-18s |\n", &mutator.m_name[0], valuesStr.cstr());
  369. }
  370. }
  371. // Techniques
  372. lines.pushBackSprintf("\n# TECHNIQUES (%u)\n\n", binary.m_techniques.getSize());
  373. lines.pushBackSprintf("| %-32s | %-12s |\n", "Name", "Shader Types");
  374. lines.pushBackSprintf("| -------------------------------- | ------------ |\n");
  375. count = 0;
  376. for(const ShaderBinaryTechnique& t : binary.m_techniques)
  377. {
  378. lines.pushBackSprintf("| %-32s | 0x%010x |\n", t.m_name.getBegin(), U32(t.m_shaderTypes));
  379. }
  380. // Mutations
  381. U32 skippedMutations = 0;
  382. for(const ShaderBinaryMutation& mutation : binary.m_mutations)
  383. {
  384. skippedMutations += mutation.m_variantIndex == kMaxU32;
  385. }
  386. lines.pushBackSprintf("\n# MUTATIONS (total %u, skipped %u)\n\n", binary.m_mutations.getSize(), skippedMutations);
  387. lines.pushBackSprintf("| %-8s | %-8s | %-18s |", "Mutation", "Variant", "Hash");
  388. if(binary.m_mutators.getSize() > 0)
  389. {
  390. for(U32 i = 0; i < binary.m_mutators.getSize(); ++i)
  391. {
  392. lines.pushBackSprintf(" %-32s |", binary.m_mutators[i].m_name.getBegin());
  393. }
  394. }
  395. lines.pushBack("\n");
  396. lines.pushBackSprintf("| -------- | -------- | ------------------ |");
  397. if(binary.m_mutators.getSize() > 0)
  398. {
  399. for(U32 i = 0; i < binary.m_mutators.getSize(); ++i)
  400. {
  401. lines.pushBackSprintf(" -------------------------------- |");
  402. }
  403. }
  404. lines.pushBack("\n");
  405. count = 0;
  406. for(const ShaderBinaryMutation& mutation : binary.m_mutations)
  407. {
  408. if(mutation.m_variantIndex != kMaxU32)
  409. {
  410. lines.pushBackSprintf("| mut%05u | var%05u | 0x%016" PRIX64 " | ", count++, mutation.m_variantIndex, mutation.m_hash);
  411. }
  412. else
  413. {
  414. lines.pushBackSprintf("| mut%05u | - | 0x%016" PRIX64 " | ", count++, mutation.m_hash);
  415. }
  416. if(mutation.m_values.getSize() > 0)
  417. {
  418. for(U32 i = 0; i < mutation.m_values.getSize(); ++i)
  419. {
  420. lines.pushBackSprintf("%-32d | ", I32(mutation.m_values[i]));
  421. }
  422. }
  423. lines.pushBack("\n");
  424. }
  425. // Variants
  426. lines.pushBackSprintf("\n# SHADER VARIANTS (%u)\n\n", binary.m_variants.getSize());
  427. lines.pushBackSprintf("| Variant | %-32s | ", "Technique");
  428. for(ShaderType s : EnumBitsIterable<ShaderType, ShaderTypeBit>(stagesInUse))
  429. {
  430. lines.pushBackSprintf("%-13s | ", g_shaderTypeNames[s].cstr());
  431. }
  432. lines.pushBack("\n");
  433. lines.pushBackSprintf("| -------- | %s |", ShaderCompilerString('-', 32).cstr());
  434. for([[maybe_unused]] ShaderType s : EnumBitsIterable<ShaderType, ShaderTypeBit>(stagesInUse))
  435. {
  436. lines.pushBackSprintf(" %s |", ShaderCompilerString('-', 13).cstr());
  437. }
  438. lines.pushBack("\n");
  439. count = 0;
  440. for(const ShaderBinaryVariant& variant : binary.m_variants)
  441. {
  442. // Binary indices
  443. for(U32 t = 0; t < binary.m_techniques.getSize(); ++t)
  444. {
  445. if(t == 0)
  446. {
  447. lines.pushBackSprintf("| var%05u | ", count);
  448. }
  449. else
  450. {
  451. lines.pushBack("| | ");
  452. }
  453. lines.pushBackSprintf("%-32s | ", binary.m_techniques[t].m_name.getBegin());
  454. for(ShaderType s : EnumBitsIterable<ShaderType, ShaderTypeBit>(stagesInUse))
  455. {
  456. if(variant.m_techniqueCodeBlocks[t].m_codeBlockIndices[s] < kMaxU32)
  457. {
  458. lines.pushBackSprintf("bin%05u | ", variant.m_techniqueCodeBlocks[t].m_codeBlockIndices[s]);
  459. }
  460. else
  461. {
  462. lines.pushBackSprintf("-%s | ", ShaderCompilerString(' ', 12).cstr());
  463. }
  464. }
  465. lines.pushBack("\n");
  466. }
  467. ++count;
  468. }
  469. // Structs
  470. lines.pushBackSprintf("\n# STRUCTS (%u)\n\n", binary.m_structs.getSize());
  471. if(binary.m_structs.getSize() > 0)
  472. {
  473. for(const ShaderBinaryStruct& s : binary.m_structs)
  474. {
  475. lines.pushBack("```C++\n");
  476. lines.pushBackSprintf("struct %s // size: %u\n", s.m_name.getBegin(), s.m_size);
  477. lines.pushBack("{\n");
  478. for(const ShaderBinaryStructMember& member : s.m_members)
  479. {
  480. const CString typeStr = getShaderVariableDataTypeInfo(member.m_type).m_name;
  481. lines.pushBackSprintf(ANKI_TAB "%5s %s; // offset: %u\n", typeStr.cstr(), member.m_name.getBegin(), member.m_offset);
  482. }
  483. lines.pushBack("};\n");
  484. lines.pushBack("```\n");
  485. }
  486. }
  487. // Stats
  488. if(options.m_maliStats || options.m_amdStats)
  489. {
  490. lines.pushBackSprintf("\n# COMPILER STATS\n\n");
  491. for(ShaderType s : EnumBitsIterable<ShaderType, ShaderTypeBit>(stagesInUse))
  492. {
  493. lines.pushBackSprintf("%s\n```\n", g_shaderTypeNames[s].cstr());
  494. if(options.m_maliStats)
  495. {
  496. maliAverages[s] = maliAverages[s] * (1.0 / F64(shadersPerStage[s]));
  497. lines.pushBackSprintf("Mali avg: %s \n", maliAverages[s].toString().cstr());
  498. lines.pushBackSprintf("Mali max: %s \n", maliMaxes[s].toString().cstr());
  499. }
  500. if(options.m_amdStats)
  501. {
  502. rgaAverages[s] = rgaAverages[s] * (1.0 / F64(shadersPerStage[s]));
  503. lines.pushBackSprintf("AMD avg: %s \n", rgaAverages[s].toString().cstr());
  504. lines.pushBackSprintf("AMD max: %s \n", rgaMaxes[s].toString().cstr());
  505. }
  506. lines.pushBack("```\n");
  507. }
  508. }
  509. lines.join("", humanReadable);
  510. }
  511. #undef ANKI_TAB
  512. } // end namespace anki