1 /****************************************************************************
2 * Copyright (C) 2014-2016 Intel Corporation. All Rights Reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 ****************************************************************************/
23
24 #include <stdio.h>
25 #include <thread>
26 #include <algorithm>
27 #include <float.h>
28 #include <vector>
29 #include <utility>
30 #include <fstream>
31 #include <string>
32
33 #if defined(__linux__) || defined(__gnu_linux__)
34 #include <pthread.h>
35 #include <sched.h>
36 #include <unistd.h>
37 #endif
38
39 #include "common/os.h"
40 #include "context.h"
41 #include "frontend.h"
42 #include "backend.h"
43 #include "rasterizer.h"
44 #include "rdtsc_core.h"
45 #include "tilemgr.h"
46
47
48
49
50 // ThreadId
51 struct Core
52 {
53 uint32_t procGroup = 0;
54 std::vector<uint32_t> threadIds;
55 };
56
57 struct NumaNode
58 {
59 std::vector<Core> cores;
60 };
61
62 typedef std::vector<NumaNode> CPUNumaNodes;
63
CalculateProcessorTopology(CPUNumaNodes & out_nodes,uint32_t & out_numThreadsPerProcGroup)64 void CalculateProcessorTopology(CPUNumaNodes& out_nodes, uint32_t& out_numThreadsPerProcGroup)
65 {
66 out_nodes.clear();
67 out_numThreadsPerProcGroup = 0;
68
69 #if defined(_WIN32)
70
71 std::vector<KAFFINITY> threadMaskPerProcGroup;
72
73 static std::mutex m;
74 std::lock_guard<std::mutex> l(m);
75
76 DWORD bufSize = 0;
77
78 BOOL ret = GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &bufSize);
79 SWR_ASSERT(ret == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
80
81 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBufferMem = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(bufSize);
82 SWR_ASSERT(pBufferMem);
83
84 ret = GetLogicalProcessorInformationEx(RelationProcessorCore, pBufferMem, &bufSize);
85 SWR_ASSERT(ret != FALSE, "Failed to get Processor Topology Information");
86
87 uint32_t count = bufSize / pBufferMem->Size;
88 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBuffer = pBufferMem;
89
90 for (uint32_t i = 0; i < count; ++i)
91 {
92 SWR_ASSERT(pBuffer->Relationship == RelationProcessorCore);
93 for (uint32_t g = 0; g < pBuffer->Processor.GroupCount; ++g)
94 {
95 auto& gmask = pBuffer->Processor.GroupMask[g];
96 uint32_t threadId = 0;
97 uint32_t procGroup = gmask.Group;
98
99 Core* pCore = nullptr;
100
101 uint32_t numThreads = (uint32_t)_mm_popcount_sizeT(gmask.Mask);
102
103 while (BitScanForwardSizeT((unsigned long*)&threadId, gmask.Mask))
104 {
105 // clear mask
106 KAFFINITY threadMask = KAFFINITY(1) << threadId;
107 gmask.Mask &= ~threadMask;
108
109 if (procGroup >= threadMaskPerProcGroup.size())
110 {
111 threadMaskPerProcGroup.resize(procGroup + 1);
112 }
113
114 if (threadMaskPerProcGroup[procGroup] & threadMask)
115 {
116 // Already seen this mask. This means that we are in 32-bit mode and
117 // have seen more than 32 HW threads for this procGroup
118 // Don't use it
119 #if defined(_WIN64)
120 SWR_ASSERT(false, "Shouldn't get here in 64-bit mode");
121 #endif
122 continue;
123 }
124
125 threadMaskPerProcGroup[procGroup] |= (KAFFINITY(1) << threadId);
126
127 // Find Numa Node
128 uint32_t numaId = 0;
129 PROCESSOR_NUMBER procNum = {};
130 procNum.Group = WORD(procGroup);
131 procNum.Number = UCHAR(threadId);
132
133 ret = GetNumaProcessorNodeEx(&procNum, (PUSHORT)&numaId);
134 SWR_ASSERT(ret);
135
136 // Store data
137 if (out_nodes.size() <= numaId) out_nodes.resize(numaId + 1);
138 auto& numaNode = out_nodes[numaId];
139
140 uint32_t coreId = 0;
141
142 if (nullptr == pCore)
143 {
144 numaNode.cores.push_back(Core());
145 pCore = &numaNode.cores.back();
146 pCore->procGroup = procGroup;
147 }
148 pCore->threadIds.push_back(threadId);
149 if (procGroup == 0)
150 {
151 out_numThreadsPerProcGroup++;
152 }
153 }
154 }
155 pBuffer = PtrAdd(pBuffer, pBuffer->Size);
156 }
157
158 free(pBufferMem);
159
160
161 #elif defined(__linux__) || defined (__gnu_linux__)
162
163 // Parse /proc/cpuinfo to get full topology
164 std::ifstream input("/proc/cpuinfo");
165 std::string line;
166 char* c;
167 uint32_t threadId = uint32_t(-1);
168 uint32_t coreId = uint32_t(-1);
169 uint32_t numaId = uint32_t(-1);
170
171 while (std::getline(input, line))
172 {
173 if (line.find("processor") != std::string::npos)
174 {
175 if (threadId != uint32_t(-1))
176 {
177 // Save information.
178 if (out_nodes.size() <= numaId) out_nodes.resize(numaId + 1);
179 auto& numaNode = out_nodes[numaId];
180 if (numaNode.cores.size() <= coreId) numaNode.cores.resize(coreId + 1);
181 auto& core = numaNode.cores[coreId];
182
183 core.procGroup = coreId;
184 core.threadIds.push_back(threadId);
185
186 out_numThreadsPerProcGroup++;
187 }
188
189 auto data_start = line.find(": ") + 2;
190 threadId = std::strtoul(&line.c_str()[data_start], &c, 10);
191 continue;
192 }
193 if (line.find("core id") != std::string::npos)
194 {
195 auto data_start = line.find(": ") + 2;
196 coreId = std::strtoul(&line.c_str()[data_start], &c, 10);
197 continue;
198 }
199 if (line.find("physical id") != std::string::npos)
200 {
201 auto data_start = line.find(": ") + 2;
202 numaId = std::strtoul(&line.c_str()[data_start], &c, 10);
203 continue;
204 }
205 }
206
207 if (threadId != uint32_t(-1))
208 {
209 // Save information.
210 if (out_nodes.size() <= numaId) out_nodes.resize(numaId + 1);
211 auto& numaNode = out_nodes[numaId];
212 if (numaNode.cores.size() <= coreId) numaNode.cores.resize(coreId + 1);
213 auto& core = numaNode.cores[coreId];
214
215 core.procGroup = coreId;
216 core.threadIds.push_back(threadId);
217 out_numThreadsPerProcGroup++;
218 }
219
220 /* Prune empty numa nodes */
221 for (auto it = out_nodes.begin(); it != out_nodes.end(); ) {
222 if ((*it).cores.size() == 0)
223 it = out_nodes.erase(it);
224 else
225 ++it;
226 }
227
228 /* Prune empty core nodes */
229 for (uint32_t node = 0; node < out_nodes.size(); node++) {
230 auto& numaNode = out_nodes[node];
231 auto it = numaNode.cores.begin();
232 for ( ; it != numaNode.cores.end(); ) {
233 if (it->threadIds.size() == 0)
234 numaNode.cores.erase(it);
235 else
236 ++it;
237 }
238 }
239
240 #else
241
242 #error Unsupported platform
243
244 #endif
245 }
246
247
bindThread(SWR_CONTEXT * pContext,uint32_t threadId,uint32_t procGroupId=0,bool bindProcGroup=false)248 void bindThread(SWR_CONTEXT* pContext, uint32_t threadId, uint32_t procGroupId = 0, bool bindProcGroup=false)
249 {
250 // Only bind threads when MAX_WORKER_THREADS isn't set.
251 if (pContext->threadInfo.MAX_WORKER_THREADS && bindProcGroup == false)
252 {
253 return;
254 }
255
256 #if defined(_WIN32)
257
258 GROUP_AFFINITY affinity = {};
259 affinity.Group = procGroupId;
260
261 #if !defined(_WIN64)
262 if (threadId >= 32)
263 {
264 // Hopefully we don't get here. Logic in CreateThreadPool should prevent this.
265 SWR_REL_ASSERT(false, "Shouldn't get here");
266
267 // In a 32-bit process on Windows it is impossible to bind
268 // to logical processors 32-63 within a processor group.
269 // In this case set the mask to 0 and let the system assign
270 // the processor. Hopefully it will make smart choices.
271 affinity.Mask = 0;
272 }
273 else
274 #endif
275 {
276 // If MAX_WORKER_THREADS is set, only bind to the proc group,
277 // Not the individual HW thread.
278 if (!pContext->threadInfo.MAX_WORKER_THREADS)
279 {
280 affinity.Mask = KAFFINITY(1) << threadId;
281 }
282 }
283
284 SetThreadGroupAffinity(GetCurrentThread(), &affinity, nullptr);
285
286 #else
287
288 cpu_set_t cpuset;
289 pthread_t thread = pthread_self();
290 CPU_ZERO(&cpuset);
291 CPU_SET(threadId, &cpuset);
292
293 pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
294
295 #endif
296 }
297
298 INLINE
GetEnqueuedDraw(SWR_CONTEXT * pContext)299 uint32_t GetEnqueuedDraw(SWR_CONTEXT *pContext)
300 {
301 return pContext->dcRing.GetHead();
302 }
303
304 INLINE
GetDC(SWR_CONTEXT * pContext,uint32_t drawId)305 DRAW_CONTEXT *GetDC(SWR_CONTEXT *pContext, uint32_t drawId)
306 {
307 return &pContext->dcRing[(drawId-1) % KNOB_MAX_DRAWS_IN_FLIGHT];
308 }
309
310 INLINE
IDComparesLess(uint32_t a,uint32_t b)311 bool IDComparesLess(uint32_t a, uint32_t b)
312 {
313 // Use signed delta to ensure that wrap-around to 0 is correctly handled.
314 int32_t delta = int32_t(a - b);
315 return (delta < 0);
316 }
317
318 // returns true if dependency not met
319 INLINE
CheckDependency(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC,uint32_t lastRetiredDraw)320 bool CheckDependency(SWR_CONTEXT *pContext, DRAW_CONTEXT *pDC, uint32_t lastRetiredDraw)
321 {
322 return pDC->dependent && IDComparesLess(lastRetiredDraw, pDC->drawId - 1);
323 }
324
CheckDependencyFE(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC,uint32_t lastRetiredDraw)325 bool CheckDependencyFE(SWR_CONTEXT *pContext, DRAW_CONTEXT *pDC, uint32_t lastRetiredDraw)
326 {
327 return pDC->dependentFE && IDComparesLess(lastRetiredDraw, pDC->drawId - 1);
328 }
329
330 //////////////////////////////////////////////////////////////////////////
331 /// @brief Update client stats.
UpdateClientStats(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)332 INLINE void UpdateClientStats(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
333 {
334 if ((pContext->pfnUpdateStats == nullptr) || (GetApiState(pDC).enableStatsBE == false))
335 {
336 return;
337 }
338
339 DRAW_DYNAMIC_STATE& dynState = pDC->dynState;
340 SWR_STATS stats{ 0 };
341
342 // Sum up stats across all workers before sending to client.
343 for (uint32_t i = 0; i < pContext->NumWorkerThreads; ++i)
344 {
345 stats.DepthPassCount += dynState.pStats[i].DepthPassCount;
346
347 stats.PsInvocations += dynState.pStats[i].PsInvocations;
348 stats.CsInvocations += dynState.pStats[i].CsInvocations;
349 }
350
351
352 pContext->pfnUpdateStats(GetPrivateState(pDC), &stats);
353 }
354
ExecuteCallbacks(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)355 INLINE void ExecuteCallbacks(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
356 {
357 UpdateClientStats(pContext, workerId, pDC);
358
359 if (pDC->retireCallback.pfnCallbackFunc)
360 {
361 pDC->retireCallback.pfnCallbackFunc(pDC->retireCallback.userData,
362 pDC->retireCallback.userData2,
363 pDC->retireCallback.userData3);
364 }
365 }
366
367 // inlined-only version
CompleteDrawContextInl(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)368 INLINE int32_t CompleteDrawContextInl(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
369 {
370 int32_t result = InterlockedDecrement((volatile LONG*)&pDC->threadsDone);
371 SWR_ASSERT(result >= 0);
372
373 if (result == 0)
374 {
375 ExecuteCallbacks(pContext, workerId, pDC);
376
377 // Cleanup memory allocations
378 pDC->pArena->Reset(true);
379 if (!pDC->isCompute)
380 {
381 pDC->pTileMgr->initialize();
382 }
383 if (pDC->cleanupState)
384 {
385 pDC->pState->pArena->Reset(true);
386 }
387
388 _ReadWriteBarrier();
389
390 pContext->dcRing.Dequeue(); // Remove from tail
391 }
392
393 return result;
394 }
395
396 // available to other translation modules
CompleteDrawContext(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC)397 int32_t CompleteDrawContext(SWR_CONTEXT* pContext, DRAW_CONTEXT* pDC)
398 {
399 return CompleteDrawContextInl(pContext, 0, pDC);
400 }
401
FindFirstIncompleteDraw(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawBE,uint32_t & drawEnqueued)402 INLINE bool FindFirstIncompleteDraw(SWR_CONTEXT* pContext, uint32_t workerId, uint32_t& curDrawBE, uint32_t& drawEnqueued)
403 {
404 // increment our current draw id to the first incomplete draw
405 drawEnqueued = GetEnqueuedDraw(pContext);
406 while (IDComparesLess(curDrawBE, drawEnqueued))
407 {
408 DRAW_CONTEXT *pDC = &pContext->dcRing[curDrawBE % KNOB_MAX_DRAWS_IN_FLIGHT];
409
410 // If its not compute and FE is not done then break out of loop.
411 if (!pDC->doneFE && !pDC->isCompute) break;
412
413 bool isWorkComplete = pDC->isCompute ?
414 pDC->pDispatch->isWorkComplete() :
415 pDC->pTileMgr->isWorkComplete();
416
417 if (isWorkComplete)
418 {
419 curDrawBE++;
420 CompleteDrawContextInl(pContext, workerId, pDC);
421 }
422 else
423 {
424 break;
425 }
426 }
427
428 // If there are no more incomplete draws then return false.
429 return IDComparesLess(curDrawBE, drawEnqueued);
430 }
431
432 //////////////////////////////////////////////////////////////////////////
433 /// @brief If there is any BE work then go work on it.
434 /// @param pContext - pointer to SWR context.
435 /// @param workerId - The unique worker ID that is assigned to this thread.
436 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread
437 /// has its own curDrawBE counter and this ensures that each worker processes all the
438 /// draws in order.
439 /// @param lockedTiles - This is the set of tiles locked by other threads. Each thread maintains its
440 /// own set and each time it fails to lock a macrotile, because its already locked,
441 /// then it will add that tile to the lockedTiles set. As a worker begins to work
442 /// on future draws the lockedTiles ensure that it doesn't work on tiles that may
443 /// still have work pending in a previous draw. Additionally, the lockedTiles is
444 /// hueristic that can steer a worker back to the same macrotile that it had been
445 /// working on in a previous draw.
446 /// @returns true if worker thread should shutdown
WorkOnFifoBE(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawBE,TileSet & lockedTiles,uint32_t numaNode,uint32_t numaMask)447 bool WorkOnFifoBE(
448 SWR_CONTEXT *pContext,
449 uint32_t workerId,
450 uint32_t &curDrawBE,
451 TileSet& lockedTiles,
452 uint32_t numaNode,
453 uint32_t numaMask)
454 {
455 bool bShutdown = false;
456
457 // Find the first incomplete draw that has pending work. If no such draw is found then
458 // return. FindFirstIncompleteDraw is responsible for incrementing the curDrawBE.
459 uint32_t drawEnqueued = 0;
460 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false)
461 {
462 return false;
463 }
464
465 uint32_t lastRetiredDraw = pContext->dcRing[curDrawBE % KNOB_MAX_DRAWS_IN_FLIGHT].drawId - 1;
466
467 // Reset our history for locked tiles. We'll have to re-learn which tiles are locked.
468 lockedTiles.clear();
469
470 // Try to work on each draw in order of the available draws in flight.
471 // 1. If we're on curDrawBE, we can work on any macrotile that is available.
472 // 2. If we're trying to work on draws after curDrawBE, we are restricted to
473 // working on those macrotiles that are known to be complete in the prior draw to
474 // maintain order. The locked tiles provides the history to ensures this.
475 for (uint32_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i)
476 {
477 DRAW_CONTEXT *pDC = &pContext->dcRing[i % KNOB_MAX_DRAWS_IN_FLIGHT];
478
479 if (pDC->isCompute) return false; // We don't look at compute work.
480
481 // First wait for FE to be finished with this draw. This keeps threading model simple
482 // but if there are lots of bubbles between draws then serializing FE and BE may
483 // need to be revisited.
484 if (!pDC->doneFE) return false;
485
486 // If this draw is dependent on a previous draw then we need to bail.
487 if (CheckDependency(pContext, pDC, lastRetiredDraw))
488 {
489 return false;
490 }
491
492 // Grab the list of all dirty macrotiles. A tile is dirty if it has work queued to it.
493 auto ¯oTiles = pDC->pTileMgr->getDirtyTiles();
494
495 for (auto tile : macroTiles)
496 {
497 uint32_t tileID = tile->mId;
498
499 // Only work on tiles for this numa node
500 uint32_t x, y;
501 pDC->pTileMgr->getTileIndices(tileID, x, y);
502 if (((x ^ y) & numaMask) != numaNode)
503 {
504 continue;
505 }
506
507 if (!tile->getNumQueued())
508 {
509 continue;
510 }
511
512 // can only work on this draw if it's not in use by other threads
513 if (lockedTiles.find(tileID) != lockedTiles.end())
514 {
515 continue;
516 }
517
518 if (tile->tryLock())
519 {
520 BE_WORK *pWork;
521
522 AR_BEGIN(WorkerFoundWork, pDC->drawId);
523
524 uint32_t numWorkItems = tile->getNumQueued();
525 SWR_ASSERT(numWorkItems);
526
527 pWork = tile->peek();
528 SWR_ASSERT(pWork);
529 if (pWork->type == DRAW)
530 {
531 pContext->pHotTileMgr->InitializeHotTiles(pContext, pDC, workerId, tileID);
532 }
533 else if (pWork->type == SHUTDOWN)
534 {
535 bShutdown = true;
536 }
537
538 while ((pWork = tile->peek()) != nullptr)
539 {
540 pWork->pfnWork(pDC, workerId, tileID, &pWork->desc);
541 tile->dequeue();
542 }
543 AR_END(WorkerFoundWork, numWorkItems);
544
545 _ReadWriteBarrier();
546
547 pDC->pTileMgr->markTileComplete(tileID);
548
549 // Optimization: If the draw is complete and we're the last one to have worked on it then
550 // we can reset the locked list as we know that all previous draws before the next are guaranteed to be complete.
551 if ((curDrawBE == i) && (bShutdown || pDC->pTileMgr->isWorkComplete()))
552 {
553 // We can increment the current BE and safely move to next draw since we know this draw is complete.
554 curDrawBE++;
555 CompleteDrawContextInl(pContext, workerId, pDC);
556
557 lastRetiredDraw++;
558
559 lockedTiles.clear();
560 break;
561 }
562
563 if (bShutdown)
564 {
565 break;
566 }
567 }
568 else
569 {
570 // This tile is already locked. So let's add it to our locked tiles set. This way we don't try locking this one again.
571 lockedTiles.insert(tileID);
572 }
573 }
574 }
575
576 return bShutdown;
577 }
578
579 //////////////////////////////////////////////////////////////////////////
580 /// @brief Called when FE work is complete for this DC.
CompleteDrawFE(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)581 INLINE void CompleteDrawFE(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
582 {
583 if (pContext->pfnUpdateStatsFE && GetApiState(pDC).enableStatsFE)
584 {
585 SWR_STATS_FE& stats = pDC->dynState.statsFE;
586
587 AR_EVENT(FrontendStatsEvent(pDC->drawId,
588 stats.IaVertices, stats.IaPrimitives, stats.VsInvocations, stats.HsInvocations,
589 stats.DsInvocations, stats.GsInvocations, stats.GsPrimitives, stats.CInvocations, stats.CPrimitives,
590 stats.SoPrimStorageNeeded[0], stats.SoPrimStorageNeeded[1], stats.SoPrimStorageNeeded[2], stats.SoPrimStorageNeeded[3],
591 stats.SoNumPrimsWritten[0], stats.SoNumPrimsWritten[1], stats.SoNumPrimsWritten[2], stats.SoNumPrimsWritten[3]
592 ));
593 AR_EVENT(FrontendDrawEndEvent(pDC->drawId));
594
595 pContext->pfnUpdateStatsFE(GetPrivateState(pDC), &stats);
596 }
597
598 if (pContext->pfnUpdateSoWriteOffset)
599 {
600 for (uint32_t i = 0; i < MAX_SO_BUFFERS; ++i)
601 {
602 if ((pDC->dynState.SoWriteOffsetDirty[i]) &&
603 (pDC->pState->state.soBuffer[i].soWriteEnable))
604 {
605 pContext->pfnUpdateSoWriteOffset(GetPrivateState(pDC), i, pDC->dynState.SoWriteOffset[i]);
606 }
607 }
608 }
609
610 // Ensure all streaming writes are globally visible before marking this FE done
611 _mm_mfence();
612 pDC->doneFE = true;
613
614 InterlockedDecrement((volatile LONG*)&pContext->drawsOutstandingFE);
615 }
616
WorkOnFifoFE(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawFE)617 void WorkOnFifoFE(SWR_CONTEXT *pContext, uint32_t workerId, uint32_t &curDrawFE)
618 {
619 // Try to grab the next DC from the ring
620 uint32_t drawEnqueued = GetEnqueuedDraw(pContext);
621 while (IDComparesLess(curDrawFE, drawEnqueued))
622 {
623 uint32_t dcSlot = curDrawFE % KNOB_MAX_DRAWS_IN_FLIGHT;
624 DRAW_CONTEXT *pDC = &pContext->dcRing[dcSlot];
625 if (pDC->isCompute || pDC->doneFE)
626 {
627 CompleteDrawContextInl(pContext, workerId, pDC);
628 curDrawFE++;
629 }
630 else
631 {
632 break;
633 }
634 }
635
636 uint32_t lastRetiredFE = curDrawFE - 1;
637 uint32_t curDraw = curDrawFE;
638 while (IDComparesLess(curDraw, drawEnqueued))
639 {
640 uint32_t dcSlot = curDraw % KNOB_MAX_DRAWS_IN_FLIGHT;
641 DRAW_CONTEXT *pDC = &pContext->dcRing[dcSlot];
642
643 if (!pDC->isCompute && !pDC->FeLock)
644 {
645 if (CheckDependencyFE(pContext, pDC, lastRetiredFE))
646 {
647 return;
648 }
649
650 uint32_t initial = InterlockedCompareExchange((volatile uint32_t*)&pDC->FeLock, 1, 0);
651 if (initial == 0)
652 {
653 // successfully grabbed the DC, now run the FE
654 pDC->FeWork.pfnWork(pContext, pDC, workerId, &pDC->FeWork.desc);
655
656 CompleteDrawFE(pContext, workerId, pDC);
657 }
658 }
659 curDraw++;
660 }
661 }
662
663 //////////////////////////////////////////////////////////////////////////
664 /// @brief If there is any compute work then go work on it.
665 /// @param pContext - pointer to SWR context.
666 /// @param workerId - The unique worker ID that is assigned to this thread.
667 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker thread
668 /// has its own curDrawBE counter and this ensures that each worker processes all the
669 /// draws in order.
WorkOnCompute(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawBE)670 void WorkOnCompute(
671 SWR_CONTEXT *pContext,
672 uint32_t workerId,
673 uint32_t& curDrawBE)
674 {
675 uint32_t drawEnqueued = 0;
676 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false)
677 {
678 return;
679 }
680
681 uint32_t lastRetiredDraw = pContext->dcRing[curDrawBE % KNOB_MAX_DRAWS_IN_FLIGHT].drawId - 1;
682
683 for (uint64_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i)
684 {
685 DRAW_CONTEXT *pDC = &pContext->dcRing[i % KNOB_MAX_DRAWS_IN_FLIGHT];
686 if (pDC->isCompute == false) return;
687
688 // check dependencies
689 if (CheckDependency(pContext, pDC, lastRetiredDraw))
690 {
691 return;
692 }
693
694 SWR_ASSERT(pDC->pDispatch != nullptr);
695 DispatchQueue& queue = *pDC->pDispatch;
696
697 // Is there any work remaining?
698 if (queue.getNumQueued() > 0)
699 {
700 void* pSpillFillBuffer = nullptr;
701 uint32_t threadGroupId = 0;
702 while (queue.getWork(threadGroupId))
703 {
704 queue.dispatch(pDC, workerId, threadGroupId, pSpillFillBuffer);
705 queue.finishedWork();
706 }
707
708 // Ensure all streaming writes are globally visible before moving onto the next draw
709 _mm_mfence();
710 }
711 }
712 }
713
714 template<bool IsFEThread, bool IsBEThread>
workerThreadMain(LPVOID pData)715 DWORD workerThreadMain(LPVOID pData)
716 {
717 THREAD_DATA *pThreadData = (THREAD_DATA*)pData;
718 SWR_CONTEXT *pContext = pThreadData->pContext;
719 uint32_t threadId = pThreadData->threadId;
720 uint32_t workerId = pThreadData->workerId;
721
722 bindThread(pContext, threadId, pThreadData->procGroupId, pThreadData->forceBindProcGroup);
723
724 RDTSC_INIT(threadId);
725
726 uint32_t numaNode = pThreadData->numaId;
727 uint32_t numaMask = pContext->threadPool.numaMask;
728
729 // flush denormals to 0
730 _mm_setcsr(_mm_getcsr() | _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON);
731
732 // Track tiles locked by other threads. If we try to lock a macrotile and find its already
733 // locked then we'll add it to this list so that we don't try and lock it again.
734 TileSet lockedTiles;
735
736 // each worker has the ability to work on any of the queued draws as long as certain
737 // conditions are met. the data associated
738 // with a draw is guaranteed to be active as long as a worker hasn't signaled that he
739 // has moved on to the next draw when he determines there is no more work to do. The api
740 // thread will not increment the head of the dc ring until all workers have moved past the
741 // current head.
742 // the logic to determine what to work on is:
743 // 1- try to work on the FE any draw that is queued. For now there are no dependencies
744 // on the FE work, so any worker can grab any FE and process in parallel. Eventually
745 // we'll need dependency tracking to force serialization on FEs. The worker will try
746 // to pick an FE by atomically incrementing a counter in the swr context. he'll keep
747 // trying until he reaches the tail.
748 // 2- BE work must be done in strict order. we accomplish this today by pulling work off
749 // the oldest draw (ie the head) of the dcRing. the worker can determine if there is
750 // any work left by comparing the total # of binned work items and the total # of completed
751 // work items. If they are equal, then there is no more work to do for this draw, and
752 // the worker can safely increment its oldestDraw counter and move on to the next draw.
753 std::unique_lock<std::mutex> lock(pContext->WaitLock, std::defer_lock);
754
755 auto threadHasWork = [&](uint32_t curDraw) { return curDraw != pContext->dcRing.GetHead(); };
756
757 uint32_t curDrawBE = 0;
758 uint32_t curDrawFE = 0;
759
760 bool bShutdown = false;
761
762 while (true)
763 {
764 if (bShutdown && !threadHasWork(curDrawBE))
765 {
766 break;
767 }
768
769 uint32_t loop = 0;
770 while (loop++ < KNOB_WORKER_SPIN_LOOP_COUNT && !threadHasWork(curDrawBE))
771 {
772 _mm_pause();
773 }
774
775 if (!threadHasWork(curDrawBE))
776 {
777 lock.lock();
778
779 // check for thread idle condition again under lock
780 if (threadHasWork(curDrawBE))
781 {
782 lock.unlock();
783 continue;
784 }
785
786 pContext->FifosNotEmpty.wait(lock);
787 lock.unlock();
788 }
789
790 if (IsBEThread)
791 {
792 AR_BEGIN(WorkerWorkOnFifoBE, 0);
793 bShutdown |= WorkOnFifoBE(pContext, workerId, curDrawBE, lockedTiles, numaNode, numaMask);
794 AR_END(WorkerWorkOnFifoBE, 0);
795
796 WorkOnCompute(pContext, workerId, curDrawBE);
797 }
798
799 if (IsFEThread)
800 {
801 WorkOnFifoFE(pContext, workerId, curDrawFE);
802
803 if (!IsBEThread)
804 {
805 curDrawBE = curDrawFE;
806 }
807 }
808 }
809
810 return 0;
811 }
812 template<> DWORD workerThreadMain<false, false>(LPVOID) = delete;
813
814 template <bool IsFEThread, bool IsBEThread>
workerThreadInit(LPVOID pData)815 DWORD workerThreadInit(LPVOID pData)
816 {
817 #if defined(_WIN32)
818 __try
819 #endif // _WIN32
820 {
821 return workerThreadMain<IsFEThread, IsBEThread>(pData);
822 }
823
824 #if defined(_WIN32)
825 __except(EXCEPTION_CONTINUE_SEARCH)
826 {
827 }
828
829 #endif // _WIN32
830
831 return 1;
832 }
833 template<> DWORD workerThreadInit<false, false>(LPVOID pData) = delete;
834
835 //////////////////////////////////////////////////////////////////////////
836 /// @brief Creates thread pool info but doesn't launch threads.
837 /// @param pContext - pointer to context
838 /// @param pPool - pointer to thread pool object.
CreateThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)839 void CreateThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool)
840 {
841 bindThread(pContext, 0);
842
843 CPUNumaNodes nodes;
844 uint32_t numThreadsPerProcGroup = 0;
845 CalculateProcessorTopology(nodes, numThreadsPerProcGroup);
846
847 uint32_t numHWNodes = (uint32_t)nodes.size();
848 uint32_t numHWCoresPerNode = (uint32_t)nodes[0].cores.size();
849 uint32_t numHWHyperThreads = (uint32_t)nodes[0].cores[0].threadIds.size();
850
851 // Calculate num HW threads. Due to asymmetric topologies, this is not
852 // a trivial multiplication.
853 uint32_t numHWThreads = 0;
854 for (auto& node : nodes)
855 {
856 for (auto& core : node.cores)
857 {
858 numHWThreads += (uint32_t)core.threadIds.size();
859 }
860 }
861
862 uint32_t numNodes = numHWNodes;
863 uint32_t numCoresPerNode = numHWCoresPerNode;
864 uint32_t numHyperThreads = numHWHyperThreads;
865
866 if (pContext->threadInfo.MAX_NUMA_NODES)
867 {
868 numNodes = std::min(numNodes, pContext->threadInfo.MAX_NUMA_NODES);
869 }
870
871 if (pContext->threadInfo.MAX_CORES_PER_NUMA_NODE)
872 {
873 numCoresPerNode = std::min(numCoresPerNode, pContext->threadInfo.MAX_CORES_PER_NUMA_NODE);
874 }
875
876 if (pContext->threadInfo.MAX_THREADS_PER_CORE)
877 {
878 numHyperThreads = std::min(numHyperThreads, pContext->threadInfo.MAX_THREADS_PER_CORE);
879 }
880
881 #if defined(_WIN32) && !defined(_WIN64)
882 if (!pContext->threadInfo.MAX_WORKER_THREADS)
883 {
884 // Limit 32-bit windows to bindable HW threads only
885 if ((numCoresPerNode * numHWHyperThreads) > 32)
886 {
887 numCoresPerNode = 32 / numHWHyperThreads;
888 }
889 }
890 #endif
891
892 // Calculate numThreads
893 uint32_t numThreads = numNodes * numCoresPerNode * numHyperThreads;
894 numThreads = std::min(numThreads, numHWThreads);
895
896 if (pContext->threadInfo.MAX_WORKER_THREADS)
897 {
898 uint32_t maxHWThreads = numHWNodes * numHWCoresPerNode * numHWHyperThreads;
899 numThreads = std::min(pContext->threadInfo.MAX_WORKER_THREADS, maxHWThreads);
900 }
901
902 uint32_t numAPIReservedThreads = 1;
903
904
905 if (numThreads == 1)
906 {
907 // If only 1 worker threads, try to move it to an available
908 // HW thread. If that fails, use the API thread.
909 if (numCoresPerNode < numHWCoresPerNode)
910 {
911 numCoresPerNode++;
912 }
913 else if (numHyperThreads < numHWHyperThreads)
914 {
915 numHyperThreads++;
916 }
917 else if (numNodes < numHWNodes)
918 {
919 numNodes++;
920 }
921 else
922 {
923 pContext->threadInfo.SINGLE_THREADED = true;
924 }
925 }
926 else
927 {
928 // Save HW threads for the API if we can
929 if (numThreads > numAPIReservedThreads)
930 {
931 numThreads -= numAPIReservedThreads;
932 }
933 else
934 {
935 numAPIReservedThreads = 0;
936 }
937 }
938
939 if (pContext->threadInfo.SINGLE_THREADED)
940 {
941 numThreads = 1;
942 }
943
944 // Initialize DRAW_CONTEXT's per-thread stats
945 for (uint32_t dc = 0; dc < KNOB_MAX_DRAWS_IN_FLIGHT; ++dc)
946 {
947 pContext->dcRing[dc].dynState.pStats = new SWR_STATS[numThreads];
948 memset(pContext->dcRing[dc].dynState.pStats, 0, sizeof(SWR_STATS) * numThreads);
949 }
950
951 if (pContext->threadInfo.SINGLE_THREADED)
952 {
953 pContext->NumWorkerThreads = 1;
954 pContext->NumFEThreads = 1;
955 pContext->NumBEThreads = 1;
956 pPool->numThreads = 0;
957
958 return;
959 }
960
961 pPool->numThreads = numThreads;
962 pContext->NumWorkerThreads = pPool->numThreads;
963
964 pPool->pThreadData = (THREAD_DATA *)malloc(pPool->numThreads * sizeof(THREAD_DATA));
965 pPool->numaMask = 0;
966
967 pPool->pThreads = new THREAD_PTR[pPool->numThreads];
968
969 if (pContext->threadInfo.MAX_WORKER_THREADS)
970 {
971 bool bForceBindProcGroup = (numThreads > numThreadsPerProcGroup);
972 uint32_t numProcGroups = (numThreads + numThreadsPerProcGroup - 1) / numThreadsPerProcGroup;
973 // When MAX_WORKER_THREADS is set we don't bother to bind to specific HW threads
974 // But Windows will still require binding to specific process groups
975 for (uint32_t workerId = 0; workerId < numThreads; ++workerId)
976 {
977 pPool->pThreadData[workerId].workerId = workerId;
978 pPool->pThreadData[workerId].procGroupId = workerId % numProcGroups;
979 pPool->pThreadData[workerId].threadId = 0;
980 pPool->pThreadData[workerId].numaId = 0;
981 pPool->pThreadData[workerId].coreId = 0;
982 pPool->pThreadData[workerId].htId = 0;
983 pPool->pThreadData[workerId].pContext = pContext;
984 pPool->pThreadData[workerId].forceBindProcGroup = bForceBindProcGroup;
985
986 pContext->NumBEThreads++;
987 pContext->NumFEThreads++;
988 }
989 }
990 else
991 {
992 pPool->numaMask = numNodes - 1; // Only works for 2**n numa nodes (1, 2, 4, etc.)
993
994 uint32_t workerId = 0;
995 for (uint32_t n = 0; n < numNodes; ++n)
996 {
997 auto& node = nodes[n];
998 uint32_t numCores = numCoresPerNode;
999 for (uint32_t c = 0; c < numCores; ++c)
1000 {
1001 if (c >= node.cores.size())
1002 {
1003 break;
1004 }
1005
1006 auto& core = node.cores[c];
1007 for (uint32_t t = 0; t < numHyperThreads; ++t)
1008 {
1009 if (t >= core.threadIds.size())
1010 {
1011 break;
1012 }
1013
1014 if (numAPIReservedThreads)
1015 {
1016 --numAPIReservedThreads;
1017 continue;
1018 }
1019
1020 SWR_ASSERT(workerId < numThreads);
1021
1022 pPool->pThreadData[workerId].workerId = workerId;
1023 pPool->pThreadData[workerId].procGroupId = core.procGroup;
1024 pPool->pThreadData[workerId].threadId = core.threadIds[t];
1025 pPool->pThreadData[workerId].numaId = n;
1026 pPool->pThreadData[workerId].coreId = c;
1027 pPool->pThreadData[workerId].htId = t;
1028 pPool->pThreadData[workerId].pContext = pContext;
1029
1030 pContext->NumBEThreads++;
1031 pContext->NumFEThreads++;
1032
1033 ++workerId;
1034 }
1035 }
1036 }
1037 SWR_ASSERT(workerId == pContext->NumWorkerThreads);
1038 }
1039 }
1040
1041 //////////////////////////////////////////////////////////////////////////
1042 /// @brief Launches worker threads in thread pool.
1043 /// @param pContext - pointer to context
1044 /// @param pPool - pointer to thread pool object.
StartThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)1045 void StartThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool)
1046 {
1047 if (pContext->threadInfo.SINGLE_THREADED)
1048 {
1049 return;
1050 }
1051
1052 for (uint32_t workerId = 0; workerId < pContext->NumWorkerThreads; ++workerId)
1053 {
1054 pPool->pThreads[workerId] = new std::thread(workerThreadInit<true, true>, &pPool->pThreadData[workerId]);
1055 }
1056 }
1057
1058 //////////////////////////////////////////////////////////////////////////
1059 /// @brief Destroys thread pool.
1060 /// @param pContext - pointer to context
1061 /// @param pPool - pointer to thread pool object.
DestroyThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)1062 void DestroyThreadPool(SWR_CONTEXT *pContext, THREAD_POOL *pPool)
1063 {
1064 if (!pContext->threadInfo.SINGLE_THREADED)
1065 {
1066 // Wait for all threads to finish
1067 SwrWaitForIdle(pContext);
1068
1069 // Wait for threads to finish and destroy them
1070 for (uint32_t t = 0; t < pPool->numThreads; ++t)
1071 {
1072 // Detach from thread. Cannot join() due to possibility (in Windows) of code
1073 // in some DLLMain(THREAD_DETATCH case) blocking the thread until after this returns.
1074 pPool->pThreads[t]->detach();
1075 delete(pPool->pThreads[t]);
1076 }
1077
1078 delete [] pPool->pThreads;
1079
1080 // Clean up data used by threads
1081 free(pPool->pThreadData);
1082 }
1083 }
1084