GrWorkGraphs.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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 <Tests/Framework/Framework.h>
  6. #include <Tests/Gr/GrCommon.h>
  7. #include <AnKi/Util/HighRezTimer.h>
  8. #include <AnKi/Gr.h>
  9. using namespace anki;
  10. static void clearSwapchain(CommandBufferPtr cmdb = CommandBufferPtr())
  11. {
  12. const Bool continueCmdb = cmdb.isCreated();
  13. TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
  14. if(!continueCmdb)
  15. {
  16. CommandBufferInitInfo cinit;
  17. cinit.m_flags = CommandBufferFlag::kGeneralWork | CommandBufferFlag::kSmallBatch;
  18. cmdb = GrManager::getSingleton().newCommandBuffer(cinit);
  19. }
  20. const TextureBarrierInfo barrier = {TextureView(presentTex.get(), TextureSubresourceDesc::all()), TextureUsageBit::kNone,
  21. TextureUsageBit::kFramebufferWrite};
  22. cmdb->setPipelineBarrier({&barrier, 1}, {}, {});
  23. RenderTarget rt;
  24. rt.m_textureView = TextureView(presentTex.get(), TextureSubresourceDesc::all());
  25. rt.m_clearValue.m_colorf = {1.0f, F32(rand()) / F32(RAND_MAX), 1.0f, 1.0f};
  26. cmdb->beginRenderPass({rt});
  27. cmdb->endRenderPass();
  28. const TextureBarrierInfo barrier2 = {TextureView(presentTex.get(), TextureSubresourceDesc::all()), TextureUsageBit::kFramebufferWrite,
  29. TextureUsageBit::kPresent};
  30. cmdb->setPipelineBarrier({&barrier2, 1}, {}, {});
  31. if(!continueCmdb)
  32. {
  33. cmdb->endRecording();
  34. GrManager::getSingleton().submit(cmdb.get());
  35. }
  36. }
  37. ANKI_TEST(Gr, WorkGraphHelloWorld)
  38. {
  39. // CVarSet::getSingleton().setMultiple(Array<const Char*, 2>{"Device", "1"});
  40. commonInit();
  41. {
  42. const Char* kSrc = R"(
  43. struct FirstNodeRecord
  44. {
  45. uint3 m_gridSize : SV_DispatchGrid;
  46. uint m_value;
  47. };
  48. struct SecondNodeRecord
  49. {
  50. uint3 m_gridSize : SV_DispatchGrid;
  51. uint m_value;
  52. };
  53. struct ThirdNodeRecord
  54. {
  55. uint m_value;
  56. };
  57. RWStructuredBuffer<uint> g_buff : register(u0);
  58. [Shader("node")] [NodeLaunch("broadcasting")] [NodeIsProgramEntry] [NodeMaxDispatchGrid(1, 1, 1)] [NumThreads(16, 1, 1)]
  59. void main(DispatchNodeInputRecord<FirstNodeRecord> inp, [MaxRecords(2)] NodeOutput<SecondNodeRecord> secondNode, uint svGroupIndex : SV_GroupIndex)
  60. {
  61. GroupNodeOutputRecords<SecondNodeRecord> rec = secondNode.GetGroupNodeOutputRecords(2);
  62. if(svGroupIndex < 2)
  63. {
  64. rec[svGroupIndex].m_gridSize = uint3(16, 1, 1);
  65. rec[svGroupIndex].m_value = inp.Get().m_value;
  66. }
  67. rec.OutputComplete();
  68. }
  69. [Shader("node")] [NodeLaunch("broadcasting")] [NumThreads(16, 1, 1)] [NodeMaxDispatchGrid(16, 1, 1)]
  70. void secondNode(DispatchNodeInputRecord<SecondNodeRecord> inp, [MaxRecords(32)] NodeOutput<ThirdNodeRecord> thirdNode,
  71. uint svGroupIndex : SV_GROUPINDEX)
  72. {
  73. GroupNodeOutputRecords<ThirdNodeRecord> recs = thirdNode.GetGroupNodeOutputRecords(32);
  74. recs[svGroupIndex * 2 + 0].m_value = inp.Get().m_value;
  75. recs[svGroupIndex * 2 + 1].m_value = inp.Get().m_value;
  76. recs.OutputComplete();
  77. }
  78. [Shader("node")] [NodeLaunch("coalescing")] [NumThreads(16, 1, 1)]
  79. void thirdNode([MaxRecords(32)] GroupNodeInputRecords<ThirdNodeRecord> inp, uint svGroupIndex : SV_GroupIndex)
  80. {
  81. if (svGroupIndex * 2 < inp.Count())
  82. InterlockedAdd(g_buff[0], inp[svGroupIndex * 2].m_value);
  83. if (svGroupIndex * 2 + 1 < inp.Count())
  84. InterlockedAdd(g_buff[0], inp[svGroupIndex * 2 + 1].m_value);
  85. }
  86. )";
  87. ShaderPtr shader = createShader(kSrc, ShaderType::kWorkGraph);
  88. ShaderProgramInitInfo progInit;
  89. progInit.m_workGraph.m_shader = shader.get();
  90. WorkGraphNodeSpecialization wgSpecialization = {"main", UVec3(4, 1, 1)};
  91. progInit.m_workGraph.m_nodeSpecializations = ConstWeakArray<WorkGraphNodeSpecialization>(&wgSpecialization, 1);
  92. ShaderProgramPtr prog = GrManager::getSingleton().newShaderProgram(progInit);
  93. BufferPtr counterBuff = createBuffer(BufferUsageBit::kAllStorage | BufferUsageBit::kTransferSource, 0u, 1, "CounterBuffer");
  94. BufferInitInfo scratchInit("scratch");
  95. scratchInit.m_size = prog->getWorkGraphMemoryRequirements();
  96. scratchInit.m_usage = BufferUsageBit::kAllStorage;
  97. BufferPtr scratchBuff = GrManager::getSingleton().newBuffer(scratchInit);
  98. struct FirstNodeRecord
  99. {
  100. UVec3 m_gridSize;
  101. U32 m_value;
  102. };
  103. Array<FirstNodeRecord, 2> records;
  104. for(U32 i = 0; i < records.getSize(); ++i)
  105. {
  106. records[i].m_gridSize = UVec3(4, 1, 1);
  107. records[i].m_value = (i + 1) * 10;
  108. }
  109. CommandBufferPtr cmdb = GrManager::getSingleton().newCommandBuffer(CommandBufferInitInfo(CommandBufferFlag::kSmallBatch));
  110. cmdb->bindShaderProgram(prog.get());
  111. cmdb->bindStorageBuffer(ANKI_REG(u0), BufferView(counterBuff.get()));
  112. cmdb->dispatchGraph(BufferView(scratchBuff.get()), records.getBegin(), records.getSize(), sizeof(records[0]));
  113. cmdb->endRecording();
  114. FencePtr fence;
  115. GrManager::getSingleton().submit(cmdb.get(), {}, &fence);
  116. fence->clientWait(kMaxSecond);
  117. validateBuffer(counterBuff, 122880);
  118. }
  119. commonDestroy();
  120. }
  121. ANKI_TEST(Gr, WorkGraphAmplification)
  122. {
  123. constexpr Bool benchmark = true;
  124. // CVarSet::getSingleton().setMultiple(Array<const Char*, 2>{"Device", "2"});
  125. commonInit(!benchmark);
  126. {
  127. const Char* kSrc = R"(
  128. struct FirstNodeRecord
  129. {
  130. uint3 m_dispatchGrid : SV_DispatchGrid;
  131. };
  132. struct SecondNodeRecord
  133. {
  134. uint3 m_dispatchGrid : SV_DispatchGrid;
  135. uint m_objectIndex;
  136. };
  137. struct Aabb
  138. {
  139. uint m_min;
  140. uint m_max;
  141. };
  142. struct Object
  143. {
  144. uint m_positionsStart; // Points to g_positions
  145. uint m_positionCount;
  146. };
  147. RWStructuredBuffer<Aabb> g_aabbs : register(u0);
  148. StructuredBuffer<Object> g_objects : register(t0);
  149. StructuredBuffer<uint> g_positions : register(t1);
  150. #define THREAD_COUNT 64u
  151. // Operates per object
  152. [Shader("node")] [NodeLaunch("broadcasting")] [NodeIsProgramEntry] [NodeMaxDispatchGrid(1, 1, 1)]
  153. [NumThreads(THREAD_COUNT, 1, 1)]
  154. void main(DispatchNodeInputRecord<FirstNodeRecord> inp, [MaxRecords(THREAD_COUNT)] NodeOutput<SecondNodeRecord> computeAabb,
  155. uint svGroupIndex : SV_GroupIndex, uint svDispatchThreadId : SV_DispatchThreadId)
  156. {
  157. GroupNodeOutputRecords<SecondNodeRecord> recs = computeAabb.GetGroupNodeOutputRecords(THREAD_COUNT);
  158. const Object obj = g_objects[svDispatchThreadId];
  159. recs[svGroupIndex].m_objectIndex = svDispatchThreadId;
  160. recs[svGroupIndex].m_dispatchGrid = uint3((obj.m_positionCount + (THREAD_COUNT - 1)) / THREAD_COUNT, 1, 1);
  161. recs.OutputComplete();
  162. }
  163. groupshared Aabb g_aabb;
  164. // Operates per position
  165. [Shader("node")] [NodeLaunch("broadcasting")] [NodeMaxDispatchGrid(1, 1, 1)] [NumThreads(THREAD_COUNT, 1, 1)]
  166. void computeAabb(DispatchNodeInputRecord<SecondNodeRecord> inp, uint svDispatchThreadId : SV_DispatchThreadId, uint svGroupIndex : SV_GroupIndex)
  167. {
  168. const Object obj = g_objects[inp.Get().m_objectIndex];
  169. svDispatchThreadId = min(svDispatchThreadId, obj.m_positionCount - 1);
  170. if(svGroupIndex == 0)
  171. {
  172. g_aabb.m_min = 0xFFFFFFFF;
  173. g_aabb.m_max = 0;
  174. }
  175. Barrier(GROUP_SHARED_MEMORY, GROUP_SCOPE | GROUP_SYNC);
  176. const uint positionIndex = obj.m_positionsStart + svDispatchThreadId;
  177. const uint pos = g_positions[positionIndex];
  178. InterlockedMin(g_aabb.m_min, pos);
  179. InterlockedMax(g_aabb.m_max, pos);
  180. Barrier(GROUP_SHARED_MEMORY, GROUP_SCOPE | GROUP_SYNC);
  181. InterlockedMin(g_aabbs[inp.Get().m_objectIndex].m_min, g_aabb.m_min);
  182. InterlockedMax(g_aabbs[inp.Get().m_objectIndex].m_max, g_aabb.m_max);
  183. }
  184. )";
  185. const Char* kComputeSrc = R"(
  186. struct Aabb
  187. {
  188. uint m_min;
  189. uint m_max;
  190. };
  191. struct Object
  192. {
  193. uint m_positionsStart; // Points to g_positions
  194. uint m_positionCount;
  195. };
  196. struct PushConsts
  197. {
  198. uint m_objectIndex;
  199. uint m_padding1;
  200. uint m_padding2;
  201. uint m_padding3;
  202. };
  203. RWStructuredBuffer<Aabb> g_aabbs : register(u0);
  204. StructuredBuffer<Object> g_objects : register(t0);
  205. StructuredBuffer<uint> g_positions : register(t1);
  206. #if defined(__spirv__)
  207. [[vk::push_constant]] ConstantBuffer<PushConsts> g_pushConsts;
  208. #else
  209. ConstantBuffer<PushConsts> g_pushConsts : register(b0, space3000);
  210. #endif
  211. #define THREAD_COUNT 64u
  212. groupshared Aabb g_aabb;
  213. [NumThreads(THREAD_COUNT, 1, 1)]
  214. void main(uint svDispatchThreadId : SV_DispatchThreadId, uint svGroupIndex : SV_GroupIndex)
  215. {
  216. const Object obj = g_objects[g_pushConsts.m_objectIndex];
  217. svDispatchThreadId = min(svDispatchThreadId, obj.m_positionCount - 1);
  218. if(svGroupIndex == 0)
  219. {
  220. g_aabb.m_min = 0xFFFFFFFF;
  221. g_aabb.m_max = 0;
  222. }
  223. Barrier(GROUP_SHARED_MEMORY, GROUP_SCOPE | GROUP_SYNC);
  224. const uint positionIndex = obj.m_positionsStart + svDispatchThreadId;
  225. const uint pos = g_positions[positionIndex];
  226. InterlockedMin(g_aabb.m_min, pos);
  227. InterlockedMax(g_aabb.m_max, pos);
  228. Barrier(GROUP_SHARED_MEMORY, GROUP_SCOPE | GROUP_SYNC);
  229. InterlockedMin(g_aabbs[g_pushConsts.m_objectIndex].m_min, g_aabb.m_min);
  230. InterlockedMax(g_aabbs[g_pushConsts.m_objectIndex].m_max, g_aabb.m_max);
  231. }
  232. )";
  233. constexpr U32 kObjectCount = 4000 * 64;
  234. constexpr U32 kPositionsPerObject = 10 * 64; // 1 * 1024;
  235. constexpr U32 kThreadCount = 64;
  236. constexpr Bool useWorkgraphs = true;
  237. ShaderProgramPtr prog;
  238. if(useWorkgraphs)
  239. {
  240. ShaderPtr shader = createShader(kSrc, ShaderType::kWorkGraph);
  241. ShaderProgramInitInfo progInit;
  242. Array<WorkGraphNodeSpecialization, 2> specializations = {
  243. {{"main", UVec3((kObjectCount + kThreadCount - 1) / kThreadCount, 1, 1)},
  244. {"computeAabb", UVec3((kPositionsPerObject + (kThreadCount - 1)) / kThreadCount, 1, 1)}}};
  245. progInit.m_workGraph.m_nodeSpecializations = specializations;
  246. progInit.m_workGraph.m_shader = shader.get();
  247. prog = GrManager::getSingleton().newShaderProgram(progInit);
  248. }
  249. else
  250. {
  251. ShaderPtr shader = createShader(kComputeSrc, ShaderType::kCompute);
  252. ShaderProgramInitInfo progInit;
  253. progInit.m_computeShader = shader.get();
  254. prog = GrManager::getSingleton().newShaderProgram(progInit);
  255. }
  256. struct Aabb
  257. {
  258. U32 m_min = kMaxU32;
  259. U32 m_max = 0;
  260. Bool operator==(const Aabb&) const = default;
  261. };
  262. struct Object
  263. {
  264. U32 m_positionsStart; // Points to g_positions
  265. U32 m_positionCount;
  266. };
  267. // Objects
  268. DynamicArray<Object> objects;
  269. objects.resize(kObjectCount);
  270. U32 positionCount = 0;
  271. for(Object& obj : objects)
  272. {
  273. obj.m_positionsStart = positionCount;
  274. obj.m_positionCount = kPositionsPerObject;
  275. positionCount += obj.m_positionCount;
  276. }
  277. printf("Obj count %u, pos count %u\n", kObjectCount, positionCount);
  278. BufferPtr objBuff = createBuffer(BufferUsageBit::kStorageComputeRead, ConstWeakArray(objects), "Objects");
  279. // AABBs
  280. BufferPtr aabbsBuff = createBuffer(BufferUsageBit::kStorageComputeWrite, Aabb(), kObjectCount, "AABBs");
  281. // Positions
  282. GrDynamicArray<U32> positions;
  283. positions.resize(positionCount);
  284. positionCount = 0;
  285. for(U32 iobj = 0; iobj < kObjectCount; ++iobj)
  286. {
  287. const Object& obj = objects[iobj];
  288. const U32 min = getRandomRange<U32>(0, kMaxU32 / 2 - 1);
  289. const U32 max = getRandomRange<U32>(kMaxU32 / 2, kMaxU32);
  290. for(U32 ipos = obj.m_positionsStart; ipos < obj.m_positionsStart + obj.m_positionCount; ++ipos)
  291. {
  292. positions[ipos] = getRandomRange<U32>(min, max);
  293. positions[ipos] = iobj;
  294. }
  295. positionCount += obj.m_positionCount;
  296. }
  297. BufferPtr posBuff = createBuffer(BufferUsageBit::kStorageComputeRead, ConstWeakArray(positions), "Positions");
  298. // Execute
  299. for(U32 i = 0; i < ((benchmark) ? 200 : 1); ++i)
  300. {
  301. [[maybe_unused]] const Error err = Input::getSingleton().handleEvents();
  302. BufferPtr scratchBuff;
  303. if(useWorkgraphs)
  304. {
  305. BufferInitInfo scratchInit("scratch");
  306. scratchInit.m_size = prog->getWorkGraphMemoryRequirements();
  307. scratchInit.m_usage = BufferUsageBit::kAllStorage;
  308. scratchBuff = GrManager::getSingleton().newBuffer(scratchInit);
  309. }
  310. const Second timeA = HighRezTimer::getCurrentTime();
  311. CommandBufferPtr cmdb;
  312. if(useWorkgraphs)
  313. {
  314. struct FirstNodeRecord
  315. {
  316. UVec3 m_gridSize;
  317. };
  318. Array<FirstNodeRecord, 1> records;
  319. records[0].m_gridSize = UVec3((objects.getSize() + kThreadCount - 1) / kThreadCount, 1, 1);
  320. cmdb = GrManager::getSingleton().newCommandBuffer(
  321. CommandBufferInitInfo(CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork));
  322. cmdb->bindShaderProgram(prog.get());
  323. cmdb->bindStorageBuffer(ANKI_REG(u0), BufferView(aabbsBuff.get()));
  324. cmdb->bindStorageBuffer(ANKI_REG(t0), BufferView(objBuff.get()));
  325. cmdb->bindStorageBuffer(ANKI_REG(t1), BufferView(posBuff.get()));
  326. cmdb->dispatchGraph(BufferView(scratchBuff.get()), records.getBegin(), records.getSize(), sizeof(records[0]));
  327. }
  328. else
  329. {
  330. cmdb = GrManager::getSingleton().newCommandBuffer(CommandBufferInitInfo(CommandBufferFlag::kGeneralWork));
  331. cmdb->bindShaderProgram(prog.get());
  332. cmdb->bindStorageBuffer(ANKI_REG(u0), BufferView(aabbsBuff.get()));
  333. cmdb->bindStorageBuffer(ANKI_REG(t0), BufferView(objBuff.get()));
  334. cmdb->bindStorageBuffer(ANKI_REG(t1), BufferView(posBuff.get()));
  335. for(U32 iobj = 0; iobj < kObjectCount; ++iobj)
  336. {
  337. const UVec4 pc(iobj);
  338. cmdb->setPushConstants(&pc, sizeof(pc));
  339. cmdb->dispatchCompute((objects[iobj].m_positionCount + kThreadCount - 1) / kThreadCount, 1, 1);
  340. }
  341. }
  342. clearSwapchain(cmdb);
  343. cmdb->endRecording();
  344. const Second timeB = HighRezTimer::getCurrentTime();
  345. FencePtr fence;
  346. GrManager::getSingleton().submit(cmdb.get(), {}, &fence);
  347. fence->clientWait(kMaxSecond);
  348. GrManager::getSingleton().swapBuffers();
  349. const Second timeC = HighRezTimer::getCurrentTime();
  350. printf("GPU time: %fms, cmdb build time: %fms\n", (timeC - timeB) * 1000.0, (timeB - timeA) * 1000.0);
  351. }
  352. // Check
  353. DynamicArray<Aabb> aabbs;
  354. readBuffer(aabbsBuff, aabbs);
  355. for(U32 i = 0; i < kObjectCount; ++i)
  356. {
  357. const Object& obj = objects[i];
  358. Aabb aabb;
  359. for(U32 ipos = obj.m_positionsStart; ipos < obj.m_positionsStart + obj.m_positionCount; ++ipos)
  360. {
  361. aabb.m_min = min(aabb.m_min, positions[ipos]);
  362. aabb.m_max = max(aabb.m_max, positions[ipos]);
  363. }
  364. if(aabb != aabbs[i])
  365. {
  366. printf("%u: %u %u | %u %u\n", i, aabb.m_min, aabbs[i].m_min, aabb.m_max, aabbs[i].m_max);
  367. }
  368. ANKI_TEST_EXPECT_EQ(aabb, aabbs[i]);
  369. }
  370. }
  371. commonDestroy();
  372. }