1 /****************************************************************************
2 * Copyright (C) 2014-2018 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__) || defined(__APPLE__)
34 #include <pthread.h>
35 #include <sched.h>
36 #include <unistd.h>
37 #endif
38
39 #ifdef __APPLE__
40 #include <sys/types.h>
41 #include <sys/sysctl.h>
42 #endif
43
44 #include "common/os.h"
45 #include "core/api.h"
46 #include "context.h"
47 #include "frontend.h"
48 #include "backend.h"
49 #include "rasterizer.h"
50 #include "rdtsc_core.h"
51 #include "tilemgr.h"
52 #include "tileset.h"
53
54
55 // ThreadId
56 struct Core
57 {
58 uint32_t procGroup = 0;
59 std::vector<uint32_t> threadIds;
60 };
61
62 struct NumaNode
63 {
64 uint32_t numaId;
65 std::vector<Core> cores;
66 };
67
68 typedef std::vector<NumaNode> CPUNumaNodes;
69
CalculateProcessorTopology(CPUNumaNodes & out_nodes,uint32_t & out_numThreadsPerProcGroup)70 void CalculateProcessorTopology(CPUNumaNodes& out_nodes, uint32_t& out_numThreadsPerProcGroup)
71 {
72 out_nodes.clear();
73 out_numThreadsPerProcGroup = 0;
74
75 #if defined(_WIN32)
76
77 std::vector<KAFFINITY> threadMaskPerProcGroup;
78
79 static std::mutex m;
80 std::lock_guard<std::mutex> l(m);
81
82 DWORD bufSize = 0;
83
84 BOOL ret = GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &bufSize);
85 SWR_ASSERT(ret == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
86
87 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBufferMem =
88 (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)malloc(bufSize);
89 SWR_ASSERT(pBufferMem);
90
91 ret = GetLogicalProcessorInformationEx(RelationProcessorCore, pBufferMem, &bufSize);
92 SWR_ASSERT(ret != FALSE, "Failed to get Processor Topology Information");
93
94 uint32_t count = bufSize / pBufferMem->Size;
95 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX pBuffer = pBufferMem;
96
97 for (uint32_t i = 0; i < count; ++i)
98 {
99 SWR_ASSERT(pBuffer->Relationship == RelationProcessorCore);
100 for (uint32_t g = 0; g < pBuffer->Processor.GroupCount; ++g)
101 {
102 auto& gmask = pBuffer->Processor.GroupMask[g];
103 uint32_t threadId = 0;
104 uint32_t procGroup = gmask.Group;
105
106 Core* pCore = nullptr;
107
108 while (BitScanForwardSizeT((unsigned long*)&threadId, gmask.Mask))
109 {
110 // clear mask
111 KAFFINITY threadMask = KAFFINITY(1) << threadId;
112 gmask.Mask &= ~threadMask;
113
114 if (procGroup >= threadMaskPerProcGroup.size())
115 {
116 threadMaskPerProcGroup.resize(procGroup + 1);
117 }
118
119 if (threadMaskPerProcGroup[procGroup] & threadMask)
120 {
121 // Already seen this mask. This means that we are in 32-bit mode and
122 // have seen more than 32 HW threads for this procGroup
123 // Don't use it
124 #if defined(_WIN64)
125 SWR_INVALID("Shouldn't get here in 64-bit mode");
126 #endif
127 continue;
128 }
129
130 threadMaskPerProcGroup[procGroup] |= (KAFFINITY(1) << threadId);
131
132 // Find Numa Node
133 uint32_t numaId = 0;
134 PROCESSOR_NUMBER procNum = {};
135 procNum.Group = WORD(procGroup);
136 procNum.Number = UCHAR(threadId);
137
138 ret = GetNumaProcessorNodeEx(&procNum, (PUSHORT)&numaId);
139 SWR_ASSERT(ret);
140
141 // Store data
142 if (out_nodes.size() <= numaId)
143 {
144 out_nodes.resize(numaId + 1);
145 }
146 auto& numaNode = out_nodes[numaId];
147 numaNode.numaId = numaId;
148
149 if (nullptr == pCore)
150 {
151 numaNode.cores.push_back(Core());
152 pCore = &numaNode.cores.back();
153 pCore->procGroup = procGroup;
154 }
155 pCore->threadIds.push_back(threadId);
156 if (procGroup == 0)
157 {
158 out_numThreadsPerProcGroup++;
159 }
160 }
161 }
162 pBuffer = PtrAdd(pBuffer, pBuffer->Size);
163 }
164
165 free(pBufferMem);
166
167 #elif defined(__linux__) || defined(__gnu_linux__)
168
169 // Parse /proc/cpuinfo to get full topology
170 std::ifstream input("/proc/cpuinfo");
171 std::string line;
172 char* c;
173 uint32_t procId = uint32_t(-1);
174 uint32_t coreId = uint32_t(-1);
175 uint32_t physId = uint32_t(-1);
176
177 while (std::getline(input, line))
178 {
179 if (line.find("processor") != std::string::npos)
180 {
181 auto data_start = line.find(": ") + 2;
182 procId = std::strtoul(&line.c_str()[data_start], &c, 10);
183 continue;
184 }
185 if (line.find("core id") != std::string::npos)
186 {
187 auto data_start = line.find(": ") + 2;
188 coreId = std::strtoul(&line.c_str()[data_start], &c, 10);
189 continue;
190 }
191 if (line.find("physical id") != std::string::npos)
192 {
193 auto data_start = line.find(": ") + 2;
194 physId = std::strtoul(&line.c_str()[data_start], &c, 10);
195 continue;
196 }
197 if (line.length() == 0)
198 {
199 if (physId + 1 > out_nodes.size())
200 out_nodes.resize(physId + 1);
201 auto& numaNode = out_nodes[physId];
202 numaNode.numaId = physId;
203
204 if (coreId + 1 > numaNode.cores.size())
205 numaNode.cores.resize(coreId + 1);
206 auto& core = numaNode.cores[coreId];
207 core.procGroup = coreId;
208 core.threadIds.push_back(procId);
209 }
210 }
211
212 out_numThreadsPerProcGroup = 0;
213 for (auto& node : out_nodes)
214 {
215 for (auto& core : node.cores)
216 {
217 out_numThreadsPerProcGroup += core.threadIds.size();
218 }
219 }
220
221 #elif defined(__APPLE__)
222
223 auto numProcessors = 0;
224 auto numCores = 0;
225 auto numPhysicalIds = 0;
226
227 int value;
228 size_t size = sizeof(value);
229
230 int result = sysctlbyname("hw.packages", &value, &size, NULL, 0);
231 SWR_ASSERT(result == 0);
232 numPhysicalIds = value;
233
234 result = sysctlbyname("hw.logicalcpu", &value, &size, NULL, 0);
235 SWR_ASSERT(result == 0);
236 numProcessors = value;
237
238 result = sysctlbyname("hw.physicalcpu", &value, &size, NULL, 0);
239 SWR_ASSERT(result == 0);
240 numCores = value;
241
242 out_nodes.resize(numPhysicalIds);
243
244 for (auto physId = 0; physId < numPhysicalIds; ++physId)
245 {
246 auto& numaNode = out_nodes[physId];
247 auto procId = 0;
248
249 numaNode.cores.resize(numCores);
250
251 while (procId < numProcessors)
252 {
253 for (auto coreId = 0; coreId < numaNode.cores.size(); ++coreId, ++procId)
254 {
255 auto& core = numaNode.cores[coreId];
256
257 core.procGroup = coreId;
258 core.threadIds.push_back(procId);
259 }
260 }
261 }
262
263 out_numThreadsPerProcGroup = 0;
264
265 for (auto& node : out_nodes)
266 {
267 for (auto& core : node.cores)
268 {
269 out_numThreadsPerProcGroup += core.threadIds.size();
270 }
271 }
272
273 #else
274
275 #error Unsupported platform
276
277 #endif
278
279 // Prune empty cores and numa nodes
280 for (auto node_it = out_nodes.begin(); node_it != out_nodes.end();)
281 {
282 // Erase empty cores (first)
283 for (auto core_it = node_it->cores.begin(); core_it != node_it->cores.end();)
284 {
285 if (core_it->threadIds.size() == 0)
286 {
287 core_it = node_it->cores.erase(core_it);
288 }
289 else
290 {
291 ++core_it;
292 }
293 }
294
295 // Erase empty numa nodes (second)
296 if (node_it->cores.size() == 0)
297 {
298 node_it = out_nodes.erase(node_it);
299 }
300 else
301 {
302 ++node_it;
303 }
304 }
305 }
306
bindThread(SWR_CONTEXT * pContext,uint32_t threadId,uint32_t procGroupId=0,bool bindProcGroup=false)307 void bindThread(SWR_CONTEXT* pContext,
308 uint32_t threadId,
309 uint32_t procGroupId = 0,
310 bool bindProcGroup = false)
311 {
312 // Only bind threads when MAX_WORKER_THREADS isn't set.
313 if (pContext->threadInfo.SINGLE_THREADED ||
314 (pContext->threadInfo.MAX_WORKER_THREADS && bindProcGroup == false))
315 {
316 return;
317 }
318
319 #if defined(_WIN32)
320
321 GROUP_AFFINITY affinity = {};
322 affinity.Group = procGroupId;
323
324 #if !defined(_WIN64)
325 if (threadId >= 32)
326 {
327 // Hopefully we don't get here. Logic in CreateThreadPool should prevent this.
328 SWR_INVALID("Shouldn't get here");
329
330 // In a 32-bit process on Windows it is impossible to bind
331 // to logical processors 32-63 within a processor group.
332 // In this case set the mask to 0 and let the system assign
333 // the processor. Hopefully it will make smart choices.
334 affinity.Mask = 0;
335 }
336 else
337 #endif
338 {
339 // If MAX_WORKER_THREADS is set, only bind to the proc group,
340 // Not the individual HW thread.
341 if (!bindProcGroup && !pContext->threadInfo.MAX_WORKER_THREADS)
342 {
343 affinity.Mask = KAFFINITY(1) << threadId;
344 }
345 else
346 {
347 affinity.Mask = KAFFINITY(0);
348 }
349 }
350
351 if (!SetThreadGroupAffinity(GetCurrentThread(), &affinity, nullptr))
352 {
353 SWR_INVALID("Failed to set Thread Affinity");
354 }
355
356 #elif defined(__linux__) || defined(__gnu_linux__)
357
358 cpu_set_t cpuset;
359 pthread_t thread = pthread_self();
360 CPU_ZERO(&cpuset);
361 CPU_SET(threadId, &cpuset);
362
363 int err = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
364 if (err != 0)
365 {
366 fprintf(stderr, "pthread_setaffinity_np failure for tid %u: %s\n", threadId, strerror(err));
367 }
368
369 #endif
370 }
371
372 INLINE
GetEnqueuedDraw(SWR_CONTEXT * pContext)373 uint32_t GetEnqueuedDraw(SWR_CONTEXT* pContext)
374 {
375 return pContext->dcRing.GetHead();
376 }
377
378 INLINE
GetDC(SWR_CONTEXT * pContext,uint32_t drawId)379 DRAW_CONTEXT* GetDC(SWR_CONTEXT* pContext, uint32_t drawId)
380 {
381 return &pContext->dcRing[(drawId - 1) % pContext->MAX_DRAWS_IN_FLIGHT];
382 }
383
384 INLINE
IDComparesLess(uint32_t a,uint32_t b)385 bool IDComparesLess(uint32_t a, uint32_t b)
386 {
387 // Use signed delta to ensure that wrap-around to 0 is correctly handled.
388 int32_t delta = int32_t(a - b);
389 return (delta < 0);
390 }
391
392 // returns true if dependency not met
393 INLINE
CheckDependency(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC,uint32_t lastRetiredDraw)394 bool CheckDependency(SWR_CONTEXT* pContext, DRAW_CONTEXT* pDC, uint32_t lastRetiredDraw)
395 {
396 return pDC->dependent && IDComparesLess(lastRetiredDraw, pDC->drawId - 1);
397 }
398
CheckDependencyFE(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC,uint32_t lastRetiredDraw)399 bool CheckDependencyFE(SWR_CONTEXT* pContext, DRAW_CONTEXT* pDC, uint32_t lastRetiredDraw)
400 {
401 return pDC->dependentFE && IDComparesLess(lastRetiredDraw, pDC->drawId - 1);
402 }
403
404 //////////////////////////////////////////////////////////////////////////
405 /// @brief Update client stats.
UpdateClientStats(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)406 INLINE void UpdateClientStats(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
407 {
408 if ((pContext->pfnUpdateStats == nullptr) || (GetApiState(pDC).enableStatsBE == false))
409 {
410 return;
411 }
412
413 DRAW_DYNAMIC_STATE& dynState = pDC->dynState;
414 OSALIGNLINE(SWR_STATS) stats{0};
415
416 // Sum up stats across all workers before sending to client.
417 for (uint32_t i = 0; i < pContext->NumWorkerThreads; ++i)
418 {
419 stats.DepthPassCount += dynState.pStats[i].DepthPassCount;
420 stats.PsInvocations += dynState.pStats[i].PsInvocations;
421 stats.CsInvocations += dynState.pStats[i].CsInvocations;
422
423 }
424
425
426 pContext->pfnUpdateStats(GetPrivateState(pDC), &stats);
427 }
428
ExecuteCallbacks(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)429 INLINE void ExecuteCallbacks(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
430 {
431 UpdateClientStats(pContext, workerId, pDC);
432
433 if (pDC->retireCallback.pfnCallbackFunc)
434 {
435 pDC->retireCallback.pfnCallbackFunc(pDC->retireCallback.userData,
436 pDC->retireCallback.userData2,
437 pDC->retireCallback.userData3);
438
439 // Callbacks to external code *could* change floating point control state
440 // Reset our optimal flags
441 SetOptimalVectorCSR();
442 }
443 }
444
445 // inlined-only version
CompleteDrawContextInl(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)446 INLINE int32_t CompleteDrawContextInl(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
447 {
448 int32_t result = static_cast<int32_t>(InterlockedDecrement(&pDC->threadsDone));
449 SWR_ASSERT(result >= 0);
450
451 AR_FLUSH(pDC->drawId);
452
453 if (result == 0)
454 {
455 ExecuteCallbacks(pContext, workerId, pDC);
456
457
458 // Cleanup memory allocations
459 pDC->pArena->Reset(true);
460 if (!pDC->isCompute)
461 {
462 pDC->pTileMgr->initialize();
463 }
464 if (pDC->cleanupState)
465 {
466 pDC->pState->pArena->Reset(true);
467 }
468
469 _ReadWriteBarrier();
470
471 pContext->dcRing.Dequeue(); // Remove from tail
472 }
473
474 return result;
475 }
476
477 // available to other translation modules
CompleteDrawContext(SWR_CONTEXT * pContext,DRAW_CONTEXT * pDC)478 int32_t CompleteDrawContext(SWR_CONTEXT* pContext, DRAW_CONTEXT* pDC)
479 {
480 return CompleteDrawContextInl(pContext, 0, pDC);
481 }
482
FindFirstIncompleteDraw(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawBE,uint32_t & drawEnqueued)483 INLINE bool FindFirstIncompleteDraw(SWR_CONTEXT* pContext,
484 uint32_t workerId,
485 uint32_t& curDrawBE,
486 uint32_t& drawEnqueued)
487 {
488 // increment our current draw id to the first incomplete draw
489 drawEnqueued = GetEnqueuedDraw(pContext);
490 while (IDComparesLess(curDrawBE, drawEnqueued))
491 {
492 DRAW_CONTEXT* pDC = &pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT];
493
494 // If its not compute and FE is not done then break out of loop.
495 if (!pDC->doneFE && !pDC->isCompute)
496 break;
497
498 bool isWorkComplete =
499 pDC->isCompute ? pDC->pDispatch->isWorkComplete() : pDC->pTileMgr->isWorkComplete();
500
501 if (isWorkComplete)
502 {
503 curDrawBE++;
504 CompleteDrawContextInl(pContext, workerId, pDC);
505 }
506 else
507 {
508 break;
509 }
510 }
511
512 // If there are no more incomplete draws then return false.
513 return IDComparesLess(curDrawBE, drawEnqueued);
514 }
515
516 //////////////////////////////////////////////////////////////////////////
517 /// @brief If there is any BE work then go work on it.
518 /// @param pContext - pointer to SWR context.
519 /// @param workerId - The unique worker ID that is assigned to this thread.
520 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker
521 /// thread
522 /// has its own curDrawBE counter and this ensures that each worker processes all
523 /// the draws in order.
524 /// @param lockedTiles - This is the set of tiles locked by other threads. Each thread maintains its
525 /// own set and each time it fails to lock a macrotile, because its already
526 /// locked, then it will add that tile to the lockedTiles set. As a worker
527 /// begins to work on future draws the lockedTiles ensure that it doesn't work
528 /// on tiles that may still have work pending in a previous draw. Additionally,
529 /// the lockedTiles is heuristic that can steer a worker back to the same
530 /// macrotile that it had been working on in a previous draw.
531 /// @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)532 bool WorkOnFifoBE(SWR_CONTEXT* pContext,
533 uint32_t workerId,
534 uint32_t& curDrawBE,
535 TileSet& lockedTiles,
536 uint32_t numaNode,
537 uint32_t numaMask)
538 {
539 bool bShutdown = false;
540
541 // Find the first incomplete draw that has pending work. If no such draw is found then
542 // return. FindFirstIncompleteDraw is responsible for incrementing the curDrawBE.
543 uint32_t drawEnqueued = 0;
544 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false)
545 {
546 return false;
547 }
548
549 uint32_t lastRetiredDraw =
550 pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT].drawId - 1;
551
552 // Reset our history for locked tiles. We'll have to re-learn which tiles are locked.
553 lockedTiles.clear();
554
555 // Try to work on each draw in order of the available draws in flight.
556 // 1. If we're on curDrawBE, we can work on any macrotile that is available.
557 // 2. If we're trying to work on draws after curDrawBE, we are restricted to
558 // working on those macrotiles that are known to be complete in the prior draw to
559 // maintain order. The locked tiles provides the history to ensures this.
560 for (uint32_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i)
561 {
562 DRAW_CONTEXT* pDC = &pContext->dcRing[i % pContext->MAX_DRAWS_IN_FLIGHT];
563
564 if (pDC->isCompute)
565 return false; // We don't look at compute work.
566
567 // First wait for FE to be finished with this draw. This keeps threading model simple
568 // but if there are lots of bubbles between draws then serializing FE and BE may
569 // need to be revisited.
570 if (!pDC->doneFE)
571 return false;
572
573 // If this draw is dependent on a previous draw then we need to bail.
574 if (CheckDependency(pContext, pDC, lastRetiredDraw))
575 {
576 return false;
577 }
578
579 // Grab the list of all dirty macrotiles. A tile is dirty if it has work queued to it.
580 auto& macroTiles = pDC->pTileMgr->getDirtyTiles();
581
582 for (auto tile : macroTiles)
583 {
584 uint32_t tileID = tile->mId;
585
586 // Only work on tiles for this numa node
587 uint32_t x, y;
588 pDC->pTileMgr->getTileIndices(tileID, x, y);
589 if (((x ^ y) & numaMask) != numaNode)
590 {
591 _mm_pause();
592 continue;
593 }
594
595 if (!tile->getNumQueued())
596 {
597 _mm_pause();
598 continue;
599 }
600
601 // can only work on this draw if it's not in use by other threads
602 if (lockedTiles.get(tileID))
603 {
604 _mm_pause();
605 continue;
606 }
607
608 if (tile->tryLock())
609 {
610 BE_WORK* pWork;
611
612 RDTSC_BEGIN(pContext->pBucketMgr, WorkerFoundWork, pDC->drawId);
613
614 uint32_t numWorkItems = tile->getNumQueued();
615 SWR_ASSERT(numWorkItems);
616
617 pWork = tile->peek();
618 SWR_ASSERT(pWork);
619 if (pWork->type == DRAW)
620 {
621 pContext->pHotTileMgr->InitializeHotTiles(pContext, pDC, workerId, tileID);
622 }
623 else if (pWork->type == SHUTDOWN)
624 {
625 bShutdown = true;
626 }
627
628 while ((pWork = tile->peek()) != nullptr)
629 {
630 pWork->pfnWork(pDC, workerId, tileID, &pWork->desc);
631 tile->dequeue();
632 }
633 RDTSC_END(pContext->pBucketMgr, WorkerFoundWork, numWorkItems);
634
635 _ReadWriteBarrier();
636
637 pDC->pTileMgr->markTileComplete(tileID);
638
639 // Optimization: If the draw is complete and we're the last one to have worked on it
640 // then we can reset the locked list as we know that all previous draws before the
641 // next are guaranteed to be complete.
642 if ((curDrawBE == i) && (bShutdown || pDC->pTileMgr->isWorkComplete()))
643 {
644 // We can increment the current BE and safely move to next draw since we know
645 // this draw is complete.
646 curDrawBE++;
647 CompleteDrawContextInl(pContext, workerId, pDC);
648
649 lastRetiredDraw++;
650
651 lockedTiles.clear();
652 break;
653 }
654
655 if (bShutdown)
656 {
657 break;
658 }
659 }
660 else
661 {
662 // This tile is already locked. So let's add it to our locked tiles set. This way we
663 // don't try locking this one again.
664 lockedTiles.set(tileID);
665 _mm_pause();
666 }
667 }
668 }
669
670 return bShutdown;
671 }
672
673 //////////////////////////////////////////////////////////////////////////
674 /// @brief Called when FE work is complete for this DC.
CompleteDrawFE(SWR_CONTEXT * pContext,uint32_t workerId,DRAW_CONTEXT * pDC)675 INLINE void CompleteDrawFE(SWR_CONTEXT* pContext, uint32_t workerId, DRAW_CONTEXT* pDC)
676 {
677 if (pContext->pfnUpdateStatsFE && GetApiState(pDC).enableStatsFE)
678 {
679 SWR_STATS_FE& stats = pDC->dynState.statsFE;
680
681 AR_EVENT(FrontendStatsEvent(pDC->drawId,
682 stats.IaVertices,
683 stats.IaPrimitives,
684 stats.VsInvocations,
685 stats.HsInvocations,
686 stats.DsInvocations,
687 stats.GsInvocations,
688 stats.GsPrimitives,
689 stats.CInvocations,
690 stats.CPrimitives,
691 stats.SoPrimStorageNeeded[0],
692 stats.SoPrimStorageNeeded[1],
693 stats.SoPrimStorageNeeded[2],
694 stats.SoPrimStorageNeeded[3],
695 stats.SoNumPrimsWritten[0],
696 stats.SoNumPrimsWritten[1],
697 stats.SoNumPrimsWritten[2],
698 stats.SoNumPrimsWritten[3]));
699 AR_EVENT(FrontendDrawEndEvent(pDC->drawId));
700
701 pContext->pfnUpdateStatsFE(GetPrivateState(pDC), &stats);
702 }
703
704 if (pContext->pfnUpdateSoWriteOffset)
705 {
706 for (uint32_t i = 0; i < MAX_SO_BUFFERS; ++i)
707 {
708 if ((pDC->dynState.SoWriteOffsetDirty[i]) &&
709 (pDC->pState->state.soBuffer[i].soWriteEnable))
710 {
711 pContext->pfnUpdateSoWriteOffset(
712 GetPrivateState(pDC), i, pDC->dynState.SoWriteOffset[i]);
713 }
714 }
715 }
716
717 if (pContext->pfnUpdateStreamOut)
718 pContext->pfnUpdateStreamOut(GetPrivateState(pDC), pDC->dynState.soPrims);
719
720 // Ensure all streaming writes are globally visible before marking this FE done
721 _mm_mfence();
722 pDC->doneFE = true;
723
724 InterlockedDecrement(&pContext->drawsOutstandingFE);
725 }
726
WorkOnFifoFE(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawFE)727 void WorkOnFifoFE(SWR_CONTEXT* pContext, uint32_t workerId, uint32_t& curDrawFE)
728 {
729 // Try to grab the next DC from the ring
730 uint32_t drawEnqueued = GetEnqueuedDraw(pContext);
731 while (IDComparesLess(curDrawFE, drawEnqueued))
732 {
733 uint32_t dcSlot = curDrawFE % pContext->MAX_DRAWS_IN_FLIGHT;
734 DRAW_CONTEXT* pDC = &pContext->dcRing[dcSlot];
735 if (pDC->isCompute || pDC->doneFE)
736 {
737 CompleteDrawContextInl(pContext, workerId, pDC);
738 curDrawFE++;
739 }
740 else
741 {
742 break;
743 }
744 }
745
746 uint32_t lastRetiredFE = curDrawFE - 1;
747 uint32_t curDraw = curDrawFE;
748 while (IDComparesLess(curDraw, drawEnqueued))
749 {
750 uint32_t dcSlot = curDraw % pContext->MAX_DRAWS_IN_FLIGHT;
751 DRAW_CONTEXT* pDC = &pContext->dcRing[dcSlot];
752
753 if (!pDC->FeLock && !pDC->isCompute)
754 {
755 if (CheckDependencyFE(pContext, pDC, lastRetiredFE))
756 {
757 return;
758 }
759
760 uint32_t initial = InterlockedCompareExchange((volatile uint32_t*)&pDC->FeLock, 1, 0);
761 if (initial == 0)
762 {
763 // successfully grabbed the DC, now run the FE
764 pDC->FeWork.pfnWork(pContext, pDC, workerId, &pDC->FeWork.desc);
765
766 CompleteDrawFE(pContext, workerId, pDC);
767 }
768 else
769 {
770 _mm_pause();
771 }
772 }
773 else
774 {
775 _mm_pause();
776 }
777
778 curDraw++;
779 }
780 }
781
782 //////////////////////////////////////////////////////////////////////////
783 /// @brief If there is any compute work then go work on it.
784 /// @param pContext - pointer to SWR context.
785 /// @param workerId - The unique worker ID that is assigned to this thread.
786 /// @param curDrawBE - This tracks the draw contexts that this thread has processed. Each worker
787 /// thread
788 /// has its own curDrawBE counter and this ensures that each worker processes all
789 /// the draws in order.
WorkOnCompute(SWR_CONTEXT * pContext,uint32_t workerId,uint32_t & curDrawBE)790 void WorkOnCompute(SWR_CONTEXT* pContext, uint32_t workerId, uint32_t& curDrawBE)
791 {
792 uint32_t drawEnqueued = 0;
793 if (FindFirstIncompleteDraw(pContext, workerId, curDrawBE, drawEnqueued) == false)
794 {
795 return;
796 }
797
798 uint32_t lastRetiredDraw =
799 pContext->dcRing[curDrawBE % pContext->MAX_DRAWS_IN_FLIGHT].drawId - 1;
800
801 for (uint64_t i = curDrawBE; IDComparesLess(i, drawEnqueued); ++i)
802 {
803 DRAW_CONTEXT* pDC = &pContext->dcRing[i % pContext->MAX_DRAWS_IN_FLIGHT];
804 if (pDC->isCompute == false)
805 return;
806
807 // check dependencies
808 if (CheckDependency(pContext, pDC, lastRetiredDraw))
809 {
810 return;
811 }
812
813 SWR_ASSERT(pDC->pDispatch != nullptr);
814 DispatchQueue& queue = *pDC->pDispatch;
815
816 // Is there any work remaining?
817 if (queue.getNumQueued() > 0)
818 {
819 void* pSpillFillBuffer = nullptr;
820 void* pScratchSpace = nullptr;
821 uint32_t threadGroupId = 0;
822 while (queue.getWork(threadGroupId))
823 {
824 queue.dispatch(pDC, workerId, threadGroupId, pSpillFillBuffer, pScratchSpace);
825 queue.finishedWork();
826 }
827
828 // Ensure all streaming writes are globally visible before moving onto the next draw
829 _mm_mfence();
830 }
831 }
832 }
833
BindApiThread(SWR_CONTEXT * pContext,uint32_t apiThreadId)834 void BindApiThread(SWR_CONTEXT* pContext, uint32_t apiThreadId)
835 {
836 if (nullptr == pContext)
837 {
838 return;
839 }
840
841 if (apiThreadId >= pContext->threadPool.numReservedThreads)
842 {
843 if (pContext->threadPool.numReservedThreads)
844 {
845 const THREAD_DATA& threadData = pContext->threadPool.pApiThreadData[0];
846 // Just bind to the process group used for API thread 0
847 bindThread(pContext, 0, threadData.procGroupId, true);
848 }
849 return;
850 }
851
852 const THREAD_DATA& threadData = pContext->threadPool.pApiThreadData[apiThreadId];
853
854 bindThread(
855 pContext, threadData.threadId, threadData.procGroupId, threadData.forceBindProcGroup);
856 }
857
858 template <bool IsFEThread, bool IsBEThread>
workerThreadMain(LPVOID pData)859 DWORD workerThreadMain(LPVOID pData)
860 {
861 THREAD_DATA* pThreadData = (THREAD_DATA*)pData;
862 SWR_CONTEXT* pContext = pThreadData->pContext;
863 uint32_t threadId = pThreadData->threadId;
864 uint32_t workerId = pThreadData->workerId;
865
866 bindThread(pContext, threadId, pThreadData->procGroupId, pThreadData->forceBindProcGroup);
867
868 {
869 char threadName[64];
870 sprintf_s(threadName,
871 #if defined(_WIN32)
872 "SWRWorker_%02d_NUMA%d_Core%02d_T%d",
873 #else
874 // linux pthread name limited to 16 chars (including \0)
875 "w%03d-n%d-c%03d-t%d",
876 #endif
877 workerId,
878 pThreadData->numaId,
879 pThreadData->coreId,
880 pThreadData->htId);
881 SetCurrentThreadName(threadName);
882 }
883
884 RDTSC_INIT(pContext->pBucketMgr, threadId);
885
886 // Only need offset numa index from base for correct masking
887 uint32_t numaNode = pThreadData->numaId - pContext->threadInfo.BASE_NUMA_NODE;
888 uint32_t numaMask = pContext->threadPool.numaMask;
889
890 SetOptimalVectorCSR();
891
892 // Track tiles locked by other threads. If we try to lock a macrotile and find its already
893 // locked then we'll add it to this list so that we don't try and lock it again.
894 TileSet lockedTiles;
895
896 // each worker has the ability to work on any of the queued draws as long as certain
897 // conditions are met. the data associated
898 // with a draw is guaranteed to be active as long as a worker hasn't signaled that he
899 // has moved on to the next draw when he determines there is no more work to do. The api
900 // thread will not increment the head of the dc ring until all workers have moved past the
901 // current head.
902 // the logic to determine what to work on is:
903 // 1- try to work on the FE any draw that is queued. For now there are no dependencies
904 // on the FE work, so any worker can grab any FE and process in parallel. Eventually
905 // we'll need dependency tracking to force serialization on FEs. The worker will try
906 // to pick an FE by atomically incrementing a counter in the swr context. he'll keep
907 // trying until he reaches the tail.
908 // 2- BE work must be done in strict order. we accomplish this today by pulling work off
909 // the oldest draw (ie the head) of the dcRing. the worker can determine if there is
910 // any work left by comparing the total # of binned work items and the total # of completed
911 // work items. If they are equal, then there is no more work to do for this draw, and
912 // the worker can safely increment its oldestDraw counter and move on to the next draw.
913 std::unique_lock<std::mutex> lock(pContext->WaitLock, std::defer_lock);
914
915 auto threadHasWork = [&](uint32_t curDraw) { return curDraw != pContext->dcRing.GetHead(); };
916
917 uint32_t curDrawBE = 0;
918 uint32_t curDrawFE = 0;
919
920 bool bShutdown = false;
921
922 while (true)
923 {
924 if (bShutdown && !threadHasWork(curDrawBE))
925 {
926 break;
927 }
928
929 uint32_t loop = 0;
930 while (loop++ < KNOB_WORKER_SPIN_LOOP_COUNT && !threadHasWork(curDrawBE))
931 {
932 _mm_pause();
933 }
934
935 if (!threadHasWork(curDrawBE))
936 {
937 lock.lock();
938
939 // check for thread idle condition again under lock
940 if (threadHasWork(curDrawBE))
941 {
942 lock.unlock();
943 continue;
944 }
945
946 pContext->FifosNotEmpty.wait(lock);
947 lock.unlock();
948 }
949
950 if (IsBEThread)
951 {
952 RDTSC_BEGIN(pContext->pBucketMgr, WorkerWorkOnFifoBE, 0);
953 bShutdown |=
954 WorkOnFifoBE(pContext, workerId, curDrawBE, lockedTiles, numaNode, numaMask);
955 RDTSC_END(pContext->pBucketMgr, WorkerWorkOnFifoBE, 0);
956
957 WorkOnCompute(pContext, workerId, curDrawBE);
958 }
959
960 if (IsFEThread)
961 {
962 WorkOnFifoFE(pContext, workerId, curDrawFE);
963
964 if (!IsBEThread)
965 {
966 curDrawBE = curDrawFE;
967 }
968 }
969 }
970
971 return 0;
972 }
973 template <>
974 DWORD workerThreadMain<false, false>(LPVOID) = delete;
975
976 template <bool IsFEThread, bool IsBEThread>
workerThreadInit(LPVOID pData)977 DWORD workerThreadInit(LPVOID pData)
978 {
979 #if defined(_MSC_VER)
980 __try
981 #endif // _WIN32
982 {
983 return workerThreadMain<IsFEThread, IsBEThread>(pData);
984 }
985
986 #if defined(_MSC_VER)
987 __except (EXCEPTION_CONTINUE_SEARCH)
988 {
989 }
990
991 #endif // _WIN32
992
993 return 1;
994 }
995 template <>
996 DWORD workerThreadInit<false, false>(LPVOID pData) = delete;
997
InitPerThreadStats(SWR_CONTEXT * pContext,uint32_t numThreads)998 static void InitPerThreadStats(SWR_CONTEXT* pContext, uint32_t numThreads)
999 {
1000 // Initialize DRAW_CONTEXT's per-thread stats
1001 for (uint32_t dc = 0; dc < pContext->MAX_DRAWS_IN_FLIGHT; ++dc)
1002 {
1003 pContext->dcRing[dc].dynState.pStats =
1004 (SWR_STATS*)AlignedMalloc(sizeof(SWR_STATS) * numThreads, 64);
1005 memset(pContext->dcRing[dc].dynState.pStats, 0, sizeof(SWR_STATS) * numThreads);
1006 }
1007 }
1008
1009 //////////////////////////////////////////////////////////////////////////
1010 /// @brief Creates thread pool info but doesn't launch threads.
1011 /// @param pContext - pointer to context
1012 /// @param pPool - pointer to thread pool object.
CreateThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)1013 void CreateThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool)
1014 {
1015 CPUNumaNodes nodes;
1016 uint32_t numThreadsPerProcGroup = 0;
1017 CalculateProcessorTopology(nodes, numThreadsPerProcGroup);
1018 assert(numThreadsPerProcGroup > 0);
1019
1020 // Assumption, for asymmetric topologies, multi-threaded cores will appear
1021 // in the list before single-threaded cores. This appears to be true for
1022 // Windows when the total HW threads is limited to 64.
1023 uint32_t numHWNodes = (uint32_t)nodes.size();
1024 uint32_t numHWCoresPerNode = (uint32_t)nodes[0].cores.size();
1025 uint32_t numHWHyperThreads = (uint32_t)nodes[0].cores[0].threadIds.size();
1026
1027 #if defined(_WIN32) && !defined(_WIN64)
1028 if (!pContext->threadInfo.MAX_WORKER_THREADS)
1029 {
1030 // Limit 32-bit windows to bindable HW threads only
1031 if ((numHWCoresPerNode * numHWHyperThreads) > 32)
1032 {
1033 numHWCoresPerNode = 32 / numHWHyperThreads;
1034 }
1035 }
1036 #endif
1037
1038 // Calculate num HW threads. Due to asymmetric topologies, this is not
1039 // a trivial multiplication.
1040 uint32_t numHWThreads = 0;
1041 for (auto const& node : nodes)
1042 {
1043 for (auto const& core : node.cores)
1044 {
1045 numHWThreads += (uint32_t)core.threadIds.size();
1046 }
1047 }
1048
1049 uint32_t numNodes = numHWNodes;
1050 uint32_t numCoresPerNode = numHWCoresPerNode;
1051 uint32_t numHyperThreads = numHWHyperThreads;
1052
1053 // Calc used threads per-core
1054 if (numHyperThreads > pContext->threadInfo.BASE_THREAD)
1055 {
1056 numHyperThreads -= pContext->threadInfo.BASE_THREAD;
1057 }
1058 else
1059 {
1060 SWR_ASSERT(false,
1061 "Cannot use BASE_THREAD value: %d, maxThreads: %d, reverting BASE_THREAD to 0",
1062 pContext->threadInfo.BASE_THREAD,
1063 numHyperThreads);
1064 pContext->threadInfo.BASE_THREAD = 0;
1065 }
1066
1067 if (pContext->threadInfo.MAX_THREADS_PER_CORE)
1068 {
1069 numHyperThreads = std::min(numHyperThreads, pContext->threadInfo.MAX_THREADS_PER_CORE);
1070 }
1071
1072 // Prune any cores that don't support the number of threads
1073 if (numHyperThreads > 1)
1074 {
1075 for (auto& node : nodes)
1076 {
1077 uint32_t numUsableCores = 0;
1078 for (auto& core : node.cores)
1079 {
1080 numUsableCores += (core.threadIds.size() >= numHyperThreads);
1081 }
1082 numCoresPerNode = std::min(numCoresPerNode, numUsableCores);
1083 }
1084 }
1085
1086 // Calc used cores per NUMA node
1087 if (numCoresPerNode > pContext->threadInfo.BASE_CORE)
1088 {
1089 numCoresPerNode -= pContext->threadInfo.BASE_CORE;
1090 }
1091 else
1092 {
1093 SWR_ASSERT(false,
1094 "Cannot use BASE_CORE value: %d, maxCores: %d, reverting BASE_CORE to 0",
1095 pContext->threadInfo.BASE_CORE,
1096 numCoresPerNode);
1097 pContext->threadInfo.BASE_CORE = 0;
1098 }
1099
1100 if (pContext->threadInfo.MAX_CORES_PER_NUMA_NODE)
1101 {
1102 numCoresPerNode = std::min(numCoresPerNode, pContext->threadInfo.MAX_CORES_PER_NUMA_NODE);
1103 }
1104
1105 // Calc used NUMA nodes
1106 if (numNodes > pContext->threadInfo.BASE_NUMA_NODE)
1107 {
1108 numNodes -= pContext->threadInfo.BASE_NUMA_NODE;
1109 }
1110 else
1111 {
1112 SWR_ASSERT(
1113 false,
1114 "Cannot use BASE_NUMA_NODE value: %d, maxNodes: %d, reverting BASE_NUMA_NODE to 0",
1115 pContext->threadInfo.BASE_NUMA_NODE,
1116 numNodes);
1117 pContext->threadInfo.BASE_NUMA_NODE = 0;
1118 }
1119
1120 if (pContext->threadInfo.MAX_NUMA_NODES)
1121 {
1122 numNodes = std::min(numNodes, pContext->threadInfo.MAX_NUMA_NODES);
1123 }
1124
1125 // Calculate numThreads - at this point everything should be symmetric
1126 uint32_t numThreads = numNodes * numCoresPerNode * numHyperThreads;
1127 SWR_REL_ASSERT(numThreads <= numHWThreads);
1128
1129 uint32_t& numAPIReservedThreads = pContext->apiThreadInfo.numAPIReservedThreads;
1130 uint32_t& numAPIThreadsPerCore = pContext->apiThreadInfo.numAPIThreadsPerCore;
1131 uint32_t numRemovedThreads = 0;
1132
1133 if (pContext->threadInfo.SINGLE_THREADED)
1134 {
1135 numAPIReservedThreads = 0;
1136 numThreads = 1;
1137 pContext->NumWorkerThreads = 1;
1138 pContext->NumFEThreads = 1;
1139 pContext->NumBEThreads = 1;
1140 pPool->numThreads = 0;
1141 }
1142 else if (pContext->threadInfo.MAX_WORKER_THREADS)
1143 {
1144 numThreads = std::min(pContext->threadInfo.MAX_WORKER_THREADS, numHWThreads);
1145 pContext->threadInfo.BASE_NUMA_NODE = 0;
1146 pContext->threadInfo.BASE_CORE = 0;
1147 pContext->threadInfo.BASE_THREAD = 0;
1148 numAPIReservedThreads = 0;
1149 }
1150 else
1151 {
1152 if (numAPIReservedThreads >= numThreads)
1153 {
1154 numAPIReservedThreads = 0;
1155 }
1156 else if (numAPIReservedThreads)
1157 {
1158 numAPIThreadsPerCore = std::min(numAPIThreadsPerCore, numHWHyperThreads);
1159
1160 if (0 == numAPIThreadsPerCore)
1161 {
1162 numAPIThreadsPerCore = numHWHyperThreads;
1163 }
1164
1165 numRemovedThreads = numAPIReservedThreads;
1166 if (numAPIThreadsPerCore == 2 && numHyperThreads == 1)
1167 {
1168 // Adjust removed threads to make logic below work
1169 numRemovedThreads =
1170 std::max(1U, (numRemovedThreads + numAPIThreadsPerCore - 1) / 2);
1171 }
1172
1173 numThreads -= numRemovedThreads;
1174 }
1175 }
1176
1177 InitPerThreadStats(pContext, numThreads);
1178
1179 if (pContext->threadInfo.SINGLE_THREADED)
1180 {
1181 numAPIReservedThreads = 0;
1182 numThreads = 1;
1183 }
1184
1185 if (numAPIReservedThreads)
1186 {
1187 pPool->pApiThreadData = new (std::nothrow) THREAD_DATA[numAPIReservedThreads];
1188 SWR_ASSERT(pPool->pApiThreadData);
1189 if (!pPool->pApiThreadData)
1190 {
1191 numAPIReservedThreads = 0;
1192 }
1193 else
1194 {
1195 memset(pPool->pApiThreadData, 0, sizeof(THREAD_DATA) * numAPIReservedThreads);
1196 }
1197 }
1198 pPool->numReservedThreads = numAPIReservedThreads;
1199
1200 pPool->numThreads = numThreads;
1201 pContext->NumWorkerThreads = pPool->numThreads;
1202
1203 pPool->pThreadData = new (std::nothrow) THREAD_DATA[pPool->numThreads];
1204 assert(pPool->pThreadData);
1205 memset(pPool->pThreadData, 0, sizeof(THREAD_DATA) * pPool->numThreads);
1206 pPool->numaMask = 0;
1207
1208 // Allocate worker private data
1209 pPool->pWorkerPrivateDataArray = nullptr;
1210 if (pContext->workerPrivateState.perWorkerPrivateStateSize == 0)
1211 {
1212 pContext->workerPrivateState.perWorkerPrivateStateSize = sizeof(SWR_WORKER_DATA);
1213 pContext->workerPrivateState.pfnInitWorkerData = nullptr;
1214 pContext->workerPrivateState.pfnFinishWorkerData = nullptr;
1215 }
1216
1217 // initialize contents of SWR_WORKER_DATA
1218 size_t perWorkerSize =
1219 AlignUpPow2(pContext->workerPrivateState.perWorkerPrivateStateSize, 64);
1220 size_t totalSize = perWorkerSize * pPool->numThreads;
1221 if (totalSize)
1222 {
1223 pPool->pWorkerPrivateDataArray = AlignedMalloc(totalSize, 64);
1224 SWR_ASSERT(pPool->pWorkerPrivateDataArray);
1225
1226 void* pWorkerData = pPool->pWorkerPrivateDataArray;
1227 for (uint32_t i = 0; i < pPool->numThreads; ++i)
1228 {
1229 pPool->pThreadData[i].pWorkerPrivateData = pWorkerData;
1230 if (pContext->workerPrivateState.pfnInitWorkerData)
1231 {
1232 pContext->workerPrivateState.pfnInitWorkerData(pContext, pWorkerData, i);
1233 }
1234 pWorkerData = PtrAdd(pWorkerData, perWorkerSize);
1235 }
1236 }
1237
1238 if (pContext->threadInfo.SINGLE_THREADED)
1239 {
1240 return;
1241 }
1242
1243 pPool->pThreads = new (std::nothrow) THREAD_PTR[pPool->numThreads];
1244 assert(pPool->pThreads);
1245
1246 if (pContext->threadInfo.MAX_WORKER_THREADS)
1247 {
1248 bool bForceBindProcGroup = (numThreads > numThreadsPerProcGroup);
1249 uint32_t numProcGroups = (numThreads + numThreadsPerProcGroup - 1) / numThreadsPerProcGroup;
1250 // When MAX_WORKER_THREADS is set we don't bother to bind to specific HW threads
1251 // But Windows will still require binding to specific process groups
1252 for (uint32_t workerId = 0; workerId < numThreads; ++workerId)
1253 {
1254 pPool->pThreadData[workerId].workerId = workerId;
1255 pPool->pThreadData[workerId].procGroupId = workerId % numProcGroups;
1256 pPool->pThreadData[workerId].threadId = 0;
1257 pPool->pThreadData[workerId].numaId = 0;
1258 pPool->pThreadData[workerId].coreId = 0;
1259 pPool->pThreadData[workerId].htId = 0;
1260 pPool->pThreadData[workerId].pContext = pContext;
1261 pPool->pThreadData[workerId].forceBindProcGroup = bForceBindProcGroup;
1262
1263 pContext->NumBEThreads++;
1264 pContext->NumFEThreads++;
1265 }
1266 }
1267 else
1268 {
1269 // numa distribution assumes workers on all nodes
1270 bool useNuma = true;
1271 if (numCoresPerNode * numHyperThreads == 1)
1272 {
1273 useNuma = false;
1274 }
1275
1276 if (useNuma)
1277 {
1278 pPool->numaMask = numNodes - 1; // Only works for 2**n numa nodes (1, 2, 4, etc.)
1279 }
1280 else
1281 {
1282 pPool->numaMask = 0;
1283 }
1284
1285 uint32_t workerId = 0;
1286 uint32_t numReservedThreads = numAPIReservedThreads;
1287 for (uint32_t n = 0; n < numNodes; ++n)
1288 {
1289 if ((n + pContext->threadInfo.BASE_NUMA_NODE) >= nodes.size())
1290 {
1291 break;
1292 }
1293 auto& node = nodes[n + pContext->threadInfo.BASE_NUMA_NODE];
1294 uint32_t numCores = numCoresPerNode;
1295 for (uint32_t c = 0; c < numCores; ++c)
1296 {
1297 if ((c + pContext->threadInfo.BASE_CORE) >= node.cores.size())
1298 {
1299 break;
1300 }
1301
1302 auto& core = node.cores[c + pContext->threadInfo.BASE_CORE];
1303 for (uint32_t t = 0; t < numHyperThreads; ++t)
1304 {
1305 if ((t + pContext->threadInfo.BASE_THREAD) >= core.threadIds.size())
1306 {
1307 break;
1308 }
1309
1310 if (numRemovedThreads)
1311 {
1312 --numRemovedThreads;
1313 assert(numReservedThreads);
1314 --numReservedThreads;
1315 pPool->pApiThreadData[numReservedThreads].workerId = 0xFFFFFFFFU;
1316 pPool->pApiThreadData[numReservedThreads].procGroupId = core.procGroup;
1317 pPool->pApiThreadData[numReservedThreads].threadId = core.threadIds[t];
1318 pPool->pApiThreadData[numReservedThreads].numaId =
1319 useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0;
1320 pPool->pApiThreadData[numReservedThreads].coreId =
1321 c + pContext->threadInfo.BASE_CORE;
1322 pPool->pApiThreadData[numReservedThreads].htId =
1323 t + pContext->threadInfo.BASE_THREAD;
1324 pPool->pApiThreadData[numReservedThreads].pContext = pContext;
1325 pPool->pApiThreadData[numReservedThreads].forceBindProcGroup = false;
1326
1327 if (numAPIThreadsPerCore > numHyperThreads && numReservedThreads)
1328 {
1329 --numReservedThreads;
1330 pPool->pApiThreadData[numReservedThreads].workerId = 0xFFFFFFFFU;
1331 pPool->pApiThreadData[numReservedThreads].procGroupId = core.procGroup;
1332 pPool->pApiThreadData[numReservedThreads].threadId =
1333 core.threadIds[t + 1];
1334 pPool->pApiThreadData[numReservedThreads].numaId =
1335 useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0;
1336 pPool->pApiThreadData[numReservedThreads].coreId =
1337 c + pContext->threadInfo.BASE_CORE;
1338 pPool->pApiThreadData[numReservedThreads].htId =
1339 t + pContext->threadInfo.BASE_THREAD;
1340 pPool->pApiThreadData[numReservedThreads].pContext = pContext;
1341 pPool->pApiThreadData[numReservedThreads].forceBindProcGroup = false;
1342 }
1343
1344 continue;
1345 }
1346
1347 SWR_ASSERT(workerId < numThreads);
1348
1349 pPool->pThreadData[workerId].workerId = workerId;
1350 pPool->pThreadData[workerId].procGroupId = core.procGroup;
1351 pPool->pThreadData[workerId].threadId =
1352 core.threadIds[t + pContext->threadInfo.BASE_THREAD];
1353 pPool->pThreadData[workerId].numaId =
1354 useNuma ? (n + pContext->threadInfo.BASE_NUMA_NODE) : 0;
1355 pPool->pThreadData[workerId].coreId = c + pContext->threadInfo.BASE_CORE;
1356 pPool->pThreadData[workerId].htId = t + pContext->threadInfo.BASE_THREAD;
1357 pPool->pThreadData[workerId].pContext = pContext;
1358 pPool->pThreadData[workerId].forceBindProcGroup = false;
1359
1360 pContext->NumBEThreads++;
1361 pContext->NumFEThreads++;
1362
1363 ++workerId;
1364 }
1365 }
1366 }
1367 SWR_ASSERT(workerId == pContext->NumWorkerThreads);
1368 }
1369 }
1370
1371 //////////////////////////////////////////////////////////////////////////
1372 /// @brief Launches worker threads in thread pool.
1373 /// @param pContext - pointer to context
1374 /// @param pPool - pointer to thread pool object.
StartThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)1375 void StartThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool)
1376 {
1377 if (pContext->threadInfo.SINGLE_THREADED)
1378 {
1379 return;
1380 }
1381
1382 for (uint32_t workerId = 0; workerId < pContext->NumWorkerThreads; ++workerId)
1383 {
1384 pPool->pThreads[workerId] =
1385 new std::thread(workerThreadInit<true, true>, &pPool->pThreadData[workerId]);
1386 }
1387 }
1388
1389 //////////////////////////////////////////////////////////////////////////
1390 /// @brief Destroys thread pool.
1391 /// @param pContext - pointer to context
1392 /// @param pPool - pointer to thread pool object.
DestroyThreadPool(SWR_CONTEXT * pContext,THREAD_POOL * pPool)1393 void DestroyThreadPool(SWR_CONTEXT* pContext, THREAD_POOL* pPool)
1394 {
1395 // Wait for all threads to finish
1396 SwrWaitForIdle(pContext);
1397
1398 // Wait for threads to finish and destroy them
1399 for (uint32_t t = 0; t < pPool->numThreads; ++t)
1400 {
1401 if (!pContext->threadInfo.SINGLE_THREADED)
1402 {
1403 // Detach from thread. Cannot join() due to possibility (in Windows) of code
1404 // in some DLLMain(THREAD_DETACH case) blocking the thread until after this returns.
1405 pPool->pThreads[t]->detach();
1406 delete (pPool->pThreads[t]);
1407 }
1408
1409 if (pContext->workerPrivateState.pfnFinishWorkerData)
1410 {
1411 pContext->workerPrivateState.pfnFinishWorkerData(
1412 pContext, pPool->pThreadData[t].pWorkerPrivateData, t);
1413 }
1414 }
1415
1416 delete[] pPool->pThreads;
1417
1418 // Clean up data used by threads
1419 delete[] pPool->pThreadData;
1420 delete[] pPool->pApiThreadData;
1421
1422 AlignedFree(pPool->pWorkerPrivateDataArray);
1423 }
1424